workshop coding style - use stdclass instead of stdClass
[moodle.git] / mod / workshop / locallib.php
CommitLineData
de811c0c 1<?php
53fad4b9
DM
2
3// This file is part of Moodle - http://moodle.org/
4//
de811c0c
DM
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
53fad4b9 14//
de811c0c
DM
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
53fad4b9 17
de811c0c 18/**
6e309973 19 * Library of internal classes and functions for module workshop
de811c0c 20 *
53fad4b9 21 * All the workshop specific functions, needed to implement the module
6e309973 22 * logic, should go to here. Instead of having bunch of function named
53fad4b9 23 * workshop_something() taking the workshop instance as the first
a39d7d87 24 * parameter, we use a class workshop that provides all methods.
53fad4b9 25 *
de811c0c
DM
26 * @package mod-workshop
27 * @copyright 2009 David Mudrak <david.mudrak@gmail.com>
28 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
29 */
30
31defined('MOODLE_INTERNAL') || die();
32
89c1aa97
DM
33require_once(dirname(__FILE__).'/lib.php'); // we extend this library here
34require_once($CFG->libdir . '/gradelib.php'); // we use some rounding and comparing routines here
0968b1a3 35
6e309973
DM
36/**
37 * Full-featured workshop API
38 *
a39d7d87 39 * This wraps the workshop database record with a set of methods that are called
6e309973 40 * from the module itself. The class should be initialized right after you get
a39d7d87 41 * $workshop, $cm and $course records at the begining of the script.
6e309973 42 */
a39d7d87
DM
43class workshop {
44
b761e6d9 45 /** return statuses of {@link add_allocation} to be passed to a workshop renderer method */
cbf87967
DM
46 const ALLOCATION_EXISTS = -9999;
47 const ALLOCATION_ERROR = -9998;
b761e6d9
DM
48
49 /** the internal code of the workshop phases as are stored in the database */
f6e8b318
DM
50 const PHASE_SETUP = 10;
51 const PHASE_SUBMISSION = 20;
52 const PHASE_ASSESSMENT = 30;
53 const PHASE_EVALUATION = 40;
54 const PHASE_CLOSED = 50;
55
56 /** the internal code of the examples modes as are stored in the database */
57 const EXAMPLES_VOLUNTARY = 0;
58 const EXAMPLES_BEFORE_SUBMISSION = 1;
59 const EXAMPLES_BEFORE_ASSESSMENT = 2;
b761e6d9 60
7a789aa8 61 /** @var stdclass course module record */
a39d7d87
DM
62 public $cm = null;
63
7a789aa8 64 /** @var stdclass course record */
a39d7d87 65 public $course = null;
6e309973 66
7a789aa8 67 /** @var stdclass context object */
f6e8b318
DM
68 public $context = null;
69
b761e6d9
DM
70 /**
71 * @var workshop_strategy grading strategy instance
72 * Do not use directly, get the instance using {@link workshop::grading_strategy_instance()}
73 */
b13142da
DM
74 protected $strategyinstance = null;
75
45d24d39
DM
76 /**
77 * @var workshop_evaluation grading evaluation instance
78 * Do not use directly, get the instance using {@link workshop::grading_evaluation_instance()}
79 */
80 protected $evaluationinstance = null;
81
6e309973 82 /**
65ba104c 83 * Initializes the workshop API instance using the data from DB
a39d7d87
DM
84 *
85 * Makes deep copy of all passed records properties. Replaces integer $course attribute
86 * with a full database record (course should not be stored in instances table anyway).
6e309973 87 *
7a789aa8
DM
88 * @param stdclass $dbrecord Workshop instance data from {workshop} table
89 * @param stdclass $cm Course module record as returned by {@link get_coursemodule_from_id()}
90 * @param stdclass $course Course record from {course} table
91 * @param stdclass $context The context of the workshop instance
0dc47fb9 92 */
7a789aa8 93 public function __construct(stdclass $dbrecord, stdclass $cm, stdclass $course, stdclass $context=null) {
f05c168d
DM
94 foreach ($dbrecord as $field => $value) {
95 $this->{$field} = $value;
a39d7d87 96 }
45d24d39
DM
97 $this->cm = $cm;
98 $this->course = $course; // beware - this replaces the standard course field in the instance table
99 // this is intentional - IMO there should be no such field as it violates
4efd7b5d
DM
100 // 3rd normal form with no real performance gain
101 if (is_null($context)) {
102 $this->context = get_context_instance(CONTEXT_MODULE, $this->cm->id);
103 } else {
104 $this->context = $context;
105 }
106 $this->evaluation = 'best'; // todo make this configurable although we have no alternatives yet
6e309973
DM
107 }
108
aa40adbf
DM
109 ////////////////////////////////////////////////////////////////////////////////
110 // Static methods //
111 ////////////////////////////////////////////////////////////////////////////////
112
da0b1f70 113 /**
aa40adbf 114 * Return list of available allocation methods
da0b1f70 115 *
aa40adbf 116 * @return array Array ['string' => 'string'] of localized allocation method names
da0b1f70 117 */
aa40adbf
DM
118 public static function installed_allocators() {
119 $installed = get_plugin_list('workshopallocation');
120 $forms = array();
121 foreach ($installed as $allocation => $allocationpath) {
122 if (file_exists($allocationpath . '/lib.php')) {
123 $forms[$allocation] = get_string('pluginname', 'workshopallocation_' . $allocation);
124 }
f05c168d 125 }
aa40adbf
DM
126 // usability - make sure that manual allocation appears the first
127 if (isset($forms['manual'])) {
128 $m = array('manual' => $forms['manual']);
129 unset($forms['manual']);
130 $forms = array_merge($m, $forms);
da0b1f70 131 }
aa40adbf
DM
132 return $forms;
133 }
da0b1f70 134
aa40adbf
DM
135 /**
136 * Returns an array of options for the editors that are used for submitting and assessing instructions
137 *
7a789aa8 138 * @param stdclass $context
aa40adbf
DM
139 * @return array
140 */
7a789aa8 141 public static function instruction_editors_options(stdclass $context) {
aa40adbf
DM
142 return array('subdirs' => 1, 'maxbytes' => 0, 'maxfiles' => EDITOR_UNLIMITED_FILES,
143 'changeformat' => 1, 'context' => $context, 'noclean' => 1, 'trusttext' => 0);
da0b1f70
DM
144 }
145
61b737a5
DM
146 /**
147 * Given the percent and the total, returns the number
148 *
149 * @param float $percent from 0 to 100
150 * @param float $total the 100% value
151 * @return float
152 */
153 public static function percent_to_value($percent, $total) {
154 if ($percent < 0 or $percent > 100) {
155 throw new coding_exception('The percent can not be less than 0 or higher than 100');
156 }
157
158 return $total * $percent / 100;
159 }
160
f6e8b318
DM
161 /**
162 * Returns an array of numeric values that can be used as maximum grades
163 *
164 * @return array Array of integers
165 */
166 public static function available_maxgrades_list() {
167 $grades = array();
168 for ($i=100; $i>=0; $i--) {
169 $grades[$i] = $i;
170 }
171 return $grades;
172 }
173
174 /**
175 * Returns the localized list of supported examples modes
176 *
177 * @return array
178 */
179 public static function available_example_modes_list() {
180 $options = array();
181 $options[self::EXAMPLES_VOLUNTARY] = get_string('examplesvoluntary', 'workshop');
182 $options[self::EXAMPLES_BEFORE_SUBMISSION] = get_string('examplesbeforesubmission', 'workshop');
183 $options[self::EXAMPLES_BEFORE_ASSESSMENT] = get_string('examplesbeforeassessment', 'workshop');
184 return $options;
185 }
186
187 /**
188 * Returns the list of available grading strategy methods
189 *
190 * @return array ['string' => 'string']
191 */
192 public static function available_strategies_list() {
193 $installed = get_plugin_list('workshopform');
194 $forms = array();
195 foreach ($installed as $strategy => $strategypath) {
196 if (file_exists($strategypath . '/lib.php')) {
197 $forms[$strategy] = get_string('pluginname', 'workshopform_' . $strategy);
198 }
199 }
200 return $forms;
201 }
202
203 /**
204 * Return an array of possible values of assessment dimension weight
205 *
206 * @return array of integers 0, 1, 2, ..., 16
207 */
208 public static function available_dimension_weights_list() {
209 $weights = array();
210 for ($i=16; $i>=0; $i--) {
211 $weights[$i] = $i;
212 }
213 return $weights;
214 }
215
216 /**
217 * Helper function returning the greatest common divisor
218 *
219 * @param int $a
220 * @param int $b
221 * @return int
222 */
223 public static function gcd($a, $b) {
224 return ($b == 0) ? ($a):(self::gcd($b, $a % $b));
225 }
226
227 /**
228 * Helper function returning the least common multiple
229 *
230 * @param int $a
231 * @param int $b
232 * @return int
233 */
234 public static function lcm($a, $b) {
235 return ($a / self::gcd($a,$b)) * $b;
236 }
237
aa40adbf
DM
238 ////////////////////////////////////////////////////////////////////////////////
239 // Workshop API //
240 ////////////////////////////////////////////////////////////////////////////////
241
6e309973
DM
242 /**
243 * Fetches all users with the capability mod/workshop:submit in the current context
244 *
3d2924e9 245 * The returned objects contain id, lastname and firstname properties and are ordered by lastname,firstname
53fad4b9 246 *
aa40adbf 247 * @todo handle with limits and groups
53fad4b9 248 * @param bool $musthavesubmission If true, return only users who have already submitted. All possible authors otherwise.
7a789aa8 249 * @return array array[userid] => stdclass{->id ->lastname ->firstname}
6e309973 250 */
d895c6aa
DM
251 public function get_potential_authors($musthavesubmission=true) {
252 $users = get_users_by_capability($this->context, 'mod/workshop:submit',
1fed6ce3 253 'u.id,u.lastname,u.firstname', 'u.lastname,u.firstname,u.id', '', '', '', '', false, false, true);
3d2924e9 254 if ($musthavesubmission) {
da0b1f70 255 $users = array_intersect_key($users, $this->users_with_submission(array_keys($users)));
66c9894d 256 }
da0b1f70 257 return $users;
6e309973
DM
258 }
259
6e309973
DM
260 /**
261 * Fetches all users with the capability mod/workshop:peerassess in the current context
262 *
b13142da 263 * The returned objects contain id, lastname and firstname properties and are ordered by lastname,firstname
53fad4b9 264 *
aa40adbf 265 * @todo handle with limits and groups
53fad4b9 266 * @param bool $musthavesubmission If true, return only users who have already submitted. All possible users otherwise.
7a789aa8 267 * @return array array[userid] => stdclass{->id ->lastname ->firstname}
6e309973 268 */
d895c6aa
DM
269 public function get_potential_reviewers($musthavesubmission=false) {
270 $users = get_users_by_capability($this->context, 'mod/workshop:peerassess',
1fed6ce3 271 'u.id, u.lastname, u.firstname', 'u.lastname,u.firstname,u.id', '', '', '', '', false, false, true);
3d2924e9
DM
272 if ($musthavesubmission) {
273 // users without their own submission can not be reviewers
da0b1f70 274 $users = array_intersect_key($users, $this->users_with_submission(array_keys($users)));
0968b1a3 275 }
da0b1f70 276 return $users;
0968b1a3
DM
277 }
278
b8ead2e6
DM
279 /**
280 * Groups the given users by the group membership
281 *
282 * This takes the module grouping settings into account. If "Available for group members only"
283 * is set, returns only groups withing the course module grouping. Always returns group [0] with
284 * all the given users.
285 *
7a789aa8
DM
286 * @param array $users array[userid] => stdclass{->id ->lastname ->firstname}
287 * @return array array[groupid][userid] => stdclass{->id ->lastname ->firstname}
53fad4b9 288 */
3d2924e9 289 public function get_grouped($users) {
53fad4b9 290 global $DB;
3d2924e9 291 global $CFG;
53fad4b9 292
b8ead2e6
DM
293 $grouped = array(); // grouped users to be returned
294 if (empty($users)) {
295 return $grouped;
a7c5b918 296 }
3d2924e9 297 if (!empty($CFG->enablegroupings) and $this->cm->groupmembersonly) {
53fad4b9
DM
298 // Available for group members only - the workshop is available only
299 // to users assigned to groups within the selected grouping, or to
300 // any group if no grouping is selected.
301 $groupingid = $this->cm->groupingid;
b8ead2e6 302 // All users that are members of at least one group will be
53fad4b9 303 // added into a virtual group id 0
b8ead2e6 304 $grouped[0] = array();
53fad4b9
DM
305 } else {
306 $groupingid = 0;
b8ead2e6
DM
307 // there is no need to be member of a group so $grouped[0] will contain
308 // all users
309 $grouped[0] = $users;
53fad4b9 310 }
b8ead2e6 311 $gmemberships = groups_get_all_groups($this->cm->course, array_keys($users), $groupingid,
53fad4b9
DM
312 'gm.id,gm.groupid,gm.userid');
313 foreach ($gmemberships as $gmembership) {
b8ead2e6
DM
314 if (!isset($grouped[$gmembership->groupid])) {
315 $grouped[$gmembership->groupid] = array();
53fad4b9 316 }
b8ead2e6
DM
317 $grouped[$gmembership->groupid][$gmembership->userid] = $users[$gmembership->userid];
318 $grouped[0][$gmembership->userid] = $users[$gmembership->userid];
53fad4b9 319 }
b8ead2e6 320 return $grouped;
53fad4b9 321 }
6e309973 322
aa40adbf
DM
323 /**
324 * Returns the list of all allocations (it est assigned assessments) in the workshop
325 *
326 * Assessments of example submissions are ignored
327 *
328 * @return array
329 */
330 public function get_allocations() {
331 global $DB;
332
00aca3c1 333 $sql = 'SELECT a.id, a.submissionid, a.reviewerid, s.authorid
aa40adbf
DM
334 FROM {workshop_assessments} a
335 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
336 WHERE s.example = 0 AND s.workshopid = :workshopid';
337 $params = array('workshopid' => $this->id);
338
339 return $DB->get_records_sql($sql, $params);
340 }
341
6e309973
DM
342 /**
343 * Returns submissions from this workshop
344 *
3dc78e5b
DM
345 * Fetches data from {workshop_submissions} and adds some useful information from other
346 * tables. Does not return textual fields to prevent possible memory lack issues.
53fad4b9 347 *
00aca3c1 348 * @param mixed $authorid int|array|'all' If set to [array of] integer, return submission[s] of the given user[s] only
934329e5 349 * @return array of records or an empty array
6e309973 350 */
29dc43e7 351 public function get_submissions($authorid='all') {
6e309973
DM
352 global $DB;
353
00aca3c1
DM
354 $sql = 'SELECT s.id, s.workshopid, s.example, s.authorid, s.timecreated, s.timemodified,
355 s.title, s.grade, s.gradeover, s.gradeoverby,
356 u.lastname AS authorlastname, u.firstname AS authorfirstname,
357 u.picture AS authorpicture, u.imagealt AS authorimagealt,
358 t.lastname AS overlastname, t.firstname AS overfirstname,
359 t.picture AS overpicture, t.imagealt AS overimagealt
3d2924e9 360 FROM {workshop_submissions} s
00aca3c1 361 INNER JOIN {user} u ON (s.authorid = u.id)
29dc43e7
DM
362 LEFT JOIN {user} t ON (s.gradeoverby = t.id)
363 WHERE s.example = 0 AND s.workshopid = :workshopid';
3d2924e9 364 $params = array('workshopid' => $this->id);
6e309973 365
00aca3c1 366 if ('all' === $authorid) {
3d2924e9 367 // no additional conditions
934329e5 368 } elseif (!empty($authorid)) {
00aca3c1
DM
369 list($usql, $uparams) = $DB->get_in_or_equal($authorid, SQL_PARAMS_NAMED);
370 $sql .= " AND authorid $usql";
6e309973 371 $params = array_merge($params, $uparams);
3d2924e9 372 } else {
934329e5
DM
373 // $authorid is empty
374 return array();
6e309973 375 }
3dc78e5b 376 $sql .= ' ORDER BY u.lastname, u.firstname';
6e309973 377
3dc78e5b 378 return $DB->get_records_sql($sql, $params);
6e309973
DM
379 }
380
51508f25
DM
381 /**
382 * Returns a submission record with the author's data
383 *
384 * @param int $id submission id
7a789aa8 385 * @return stdclass
51508f25
DM
386 */
387 public function get_submission_by_id($id) {
388 global $DB;
389
29dc43e7
DM
390 // we intentionally check the workshopid here, too, so the workshop can't touch submissions
391 // from other instances
51508f25
DM
392 $sql = 'SELECT s.*,
393 u.lastname AS authorlastname, u.firstname AS authorfirstname, u.id AS authorid,
394 u.picture AS authorpicture, u.imagealt AS authorimagealt
395 FROM {workshop_submissions} s
00aca3c1 396 INNER JOIN {user} u ON (s.authorid = u.id)
81eccf0a 397 WHERE s.example = 0 AND s.workshopid = :workshopid AND s.id = :id';
51508f25
DM
398 $params = array('workshopid' => $this->id, 'id' => $id);
399 return $DB->get_record_sql($sql, $params, MUST_EXIST);
400 }
401
53fad4b9 402 /**
3dc78e5b 403 * Returns a submission submitted by the given author
53fad4b9 404 *
3dc78e5b 405 * @param int $id author id
7a789aa8 406 * @return stdclass|false
53fad4b9 407 */
00aca3c1 408 public function get_submission_by_author($authorid) {
e9b0f0ab
DM
409 global $DB;
410
00aca3c1 411 if (empty($authorid)) {
53fad4b9
DM
412 return false;
413 }
3dc78e5b
DM
414 $sql = 'SELECT s.*,
415 u.lastname AS authorlastname, u.firstname AS authorfirstname, u.id AS authorid,
416 u.picture AS authorpicture, u.imagealt AS authorimagealt
417 FROM {workshop_submissions} s
00aca3c1
DM
418 INNER JOIN {user} u ON (s.authorid = u.id)
419 WHERE s.example = 0 AND s.workshopid = :workshopid AND s.authorid = :authorid';
420 $params = array('workshopid' => $this->id, 'authorid' => $authorid);
3dc78e5b 421 return $DB->get_record_sql($sql, $params);
53fad4b9 422 }
6e309973 423
81eccf0a
DM
424 /**
425 * Returns full record of the given example submission
426 *
427 * @param int $id example submission od
428 * @return object
429 */
430 public function get_example_by_id($id) {
431 global $DB;
432 return $DB->get_record('workshop_submissions',
433 array('id' => $id, 'workshopid' => $this->id, 'example' => 1), '*', MUST_EXIST);
434 }
435
cbf87967
DM
436 /**
437 * Returns the list of example submissions in this workshop with reference assessments attached
438 *
439 * @return array of objects or an empty array
440 * @see workshop::prepare_example_summary()
441 */
442 public function get_examples_for_manager() {
443 global $DB;
444
445 $sql = 'SELECT s.id, s.title,
446 a.id AS assessmentid, a.weight, a.grade, a.gradinggrade
447 FROM {workshop_submissions} s
448 LEFT JOIN {workshop_assessments} a ON (a.submissionid = s.id AND a.weight = 1)
449 WHERE s.example = 1 AND s.workshopid = :workshopid
450 ORDER BY s.title';
451 return $DB->get_records_sql($sql, array('workshopid' => $this->id));
452 }
453
454 /**
455 * Returns the list of all example submissions in this workshop with the information of assessments done by the given user
456 *
457 * @param int $reviewerid user id
458 * @return array of objects, indexed by example submission id
459 * @see workshop::prepare_example_summary()
460 */
461 public function get_examples_for_reviewer($reviewerid) {
462 global $DB;
463
464 if (empty($reviewerid)) {
465 return false;
466 }
467 $sql = 'SELECT s.id, s.title,
468 a.id AS assessmentid, a.weight, a.grade, a.gradinggrade
469 FROM {workshop_submissions} s
470 LEFT JOIN {workshop_assessments} a ON (a.submissionid = s.id AND a.reviewerid = :reviewerid AND a.weight = 0)
471 WHERE s.example = 1 AND s.workshopid = :workshopid
472 ORDER BY s.title';
473 return $DB->get_records_sql($sql, array('workshopid' => $this->id, 'reviewerid' => $reviewerid));
474 }
475
476 /**
477 * Prepares component containing summary of given example to be rendered
478 *
7a789aa8
DM
479 * @param stdclass $example as returned by {@link workshop::get_examples_for_manager()} or {@link workshop::get_examples_for_reviewer()}
480 * @return stdclass component to be rendered
cbf87967 481 */
7a789aa8 482 public function prepare_example_summary(stdclass $example) {
cbf87967 483
7a789aa8 484 $summary = new stdclass();
cbf87967
DM
485 $summary->example = $example;
486 if (is_null($example->grade)) {
487 $summary->status = 'notgraded';
488 $buttontext = get_string('assess', 'workshop');
489 } else {
490 $summary->status = 'graded';
491 $buttontext = get_string('reassess', 'workshop');
492 }
493
7a789aa8 494 $summary->gradeinfo = new stdclass();
cbf87967
DM
495 $summary->gradeinfo->received = $this->real_grade($example->grade);
496 $summary->gradeinfo->max = $this->real_grade(100);
497
498 $summary->btnform = new html_form();
499 $summary->btnform->method = 'get';
500 $summary->btnform->button->text = $buttontext;
501 $summary->btnform->url = new moodle_url($this->exsubmission_url($example->id), array('assess' => 'on', 'sesskey' => sesskey()));
502
503 return $summary;
504 }
505
81eccf0a
DM
506 /**
507 * Removes the submission and all relevant data
508 *
7a789aa8 509 * @param stdclass $submission record to delete
81eccf0a
DM
510 * @return void
511 */
7a789aa8 512 public function delete_submission(stdclass $submission) {
81eccf0a
DM
513 global $DB;
514 $assessments = $DB->get_records('workshop_assessments', array('submissionid' => $submission->id), '', 'id');
515 $this->delete_assessment(array_keys($assessments));
516 $DB->delete_records('workshop_submissions', array('id' => $submission->id));
517 }
518
6e309973 519 /**
3dc78e5b 520 * Returns the list of all assessments in the workshop with some data added
6e309973
DM
521 *
522 * Fetches data from {workshop_assessments} and adds some useful information from other
3dc78e5b
DM
523 * tables. The returned object does not contain textual fields (ie comments) to prevent memory
524 * lack issues.
525 *
7a789aa8 526 * @return array [assessmentid] => assessment stdclass
6e309973 527 */
3dc78e5b 528 public function get_all_assessments() {
6e309973 529 global $DB;
53fad4b9 530
f6e8b318 531 $sql = 'SELECT a.id, a.submissionid, a.reviewerid, a.timecreated, a.timemodified,
3dc78e5b 532 a.grade, a.gradinggrade, a.gradinggradeover, a.gradinggradeoverby,
3d2924e9
DM
533 reviewer.id AS reviewerid,reviewer.firstname AS reviewerfirstname,reviewer.lastname as reviewerlastname,
534 s.title,
ddb59c77 535 author.id AS authorid, author.firstname AS authorfirstname,author.lastname AS authorlastname
3d2924e9 536 FROM {workshop_assessments} a
00aca3c1 537 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id)
3d2924e9 538 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
00aca3c1 539 INNER JOIN {user} author ON (s.authorid = author.id)
3dc78e5b
DM
540 WHERE s.workshopid = :workshopid AND s.example = 0
541 ORDER BY reviewer.lastname, reviewer.firstname';
3d2924e9
DM
542 $params = array('workshopid' => $this->id);
543
3dc78e5b 544 return $DB->get_records_sql($sql, $params);
53fad4b9
DM
545 }
546
547 /**
3dc78e5b 548 * Get the complete information about the given assessment
53fad4b9
DM
549 *
550 * @param int $id Assessment ID
7a789aa8 551 * @return mixed false if not found, stdclass otherwise
53fad4b9
DM
552 */
553 public function get_assessment_by_id($id) {
3dc78e5b
DM
554 global $DB;
555
556 $sql = 'SELECT a.*,
557 reviewer.id AS reviewerid,reviewer.firstname AS reviewerfirstname,reviewer.lastname as reviewerlastname,
558 s.title,
559 author.id AS authorid, author.firstname AS authorfirstname,author.lastname as authorlastname
560 FROM {workshop_assessments} a
00aca3c1 561 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id)
3dc78e5b 562 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
00aca3c1 563 INNER JOIN {user} author ON (s.authorid = author.id)
3dc78e5b
DM
564 WHERE a.id = :id AND s.workshopid = :workshopid';
565 $params = array('id' => $id, 'workshopid' => $this->id);
566
567 return $DB->get_record_sql($sql, $params, MUST_EXIST);
53fad4b9
DM
568 }
569
570 /**
3dc78e5b 571 * Get the complete information about all assessments allocated to the given reviewer
53fad4b9 572 *
00aca3c1 573 * @param int $reviewerid
3dc78e5b 574 * @return array
53fad4b9 575 */
00aca3c1 576 public function get_assessments_by_reviewer($reviewerid) {
3dc78e5b
DM
577 global $DB;
578
579 $sql = 'SELECT a.*,
ddb59c77
DM
580 reviewer.id AS reviewerid,reviewer.firstname AS reviewerfirstname,reviewer.lastname AS reviewerlastname,
581 s.id AS submissionid, s.title AS submissiontitle, s.timecreated AS submissioncreated,
582 s.timemodified AS submissionmodified,
583 author.id AS authorid, author.firstname AS authorfirstname,author.lastname AS authorlastname,
584 author.picture AS authorpicture, author.imagealt AS authorimagealt
3dc78e5b 585 FROM {workshop_assessments} a
00aca3c1 586 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id)
3dc78e5b 587 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
00aca3c1
DM
588 INNER JOIN {user} author ON (s.authorid = author.id)
589 WHERE s.example = 0 AND reviewer.id = :reviewerid AND s.workshopid = :workshopid';
590 $params = array('reviewerid' => $reviewerid, 'workshopid' => $this->id);
3dc78e5b
DM
591
592 return $DB->get_records_sql($sql, $params);
53fad4b9 593 }
6e309973 594
6e309973
DM
595 /**
596 * Allocate a submission to a user for review
53fad4b9 597 *
7a789aa8 598 * @param stdclass $submission Submission object with at least id property
6e309973 599 * @param int $reviewerid User ID
53fad4b9 600 * @param bool $bulk repeated inserts into DB expected
becec954 601 * @param int $weight of the new assessment, from 0 to 16
6e309973
DM
602 * @return int ID of the new assessment or an error code
603 */
7a789aa8 604 public function add_allocation(stdclass $submission, $reviewerid, $bulk=false, $weight=1) {
6e309973
DM
605 global $DB;
606
00aca3c1 607 if ($DB->record_exists('workshop_assessments', array('submissionid' => $submission->id, 'reviewerid' => $reviewerid))) {
b761e6d9 608 return self::ALLOCATION_EXISTS;
6e309973
DM
609 }
610
6e309973 611 $now = time();
7a789aa8 612 $assessment = new stdclass();
e554671d
DM
613 $assessment->submissionid = $submission->id;
614 $assessment->reviewerid = $reviewerid;
615 $assessment->timecreated = $now;
616 $assessment->timemodified = $now;
becec954 617 $assessment->weight = $weight;
e554671d
DM
618 $assessment->generalcommentformat = FORMAT_HTML; // todo better default handling
619 $assessment->feedbackreviewerformat = FORMAT_HTML; // todo better default handling
6e309973 620
235b31c8 621 return $DB->insert_record('workshop_assessments', $assessment, true, $bulk);
6e309973
DM
622 }
623
6e309973 624 /**
53fad4b9 625 * Delete assessment record or records
6e309973 626 *
53fad4b9
DM
627 * @param mixed $id int|array assessment id or array of assessments ids
628 * @return bool false if $id not a valid parameter, true otherwise
6e309973
DM
629 */
630 public function delete_assessment($id) {
631 global $DB;
632
633 // todo remove all given grades from workshop_grades;
6e309973 634
53fad4b9 635 if (is_array($id)) {
235b31c8 636 return $DB->delete_records_list('workshop_assessments', 'id', $id);
3d2924e9 637 } else {
235b31c8 638 return $DB->delete_records('workshop_assessments', array('id' => $id));
53fad4b9 639 }
53fad4b9 640 }
6e309973
DM
641
642 /**
643 * Returns instance of grading strategy class
53fad4b9 644 *
7a789aa8 645 * @return stdclass Instance of a grading strategy
6e309973
DM
646 */
647 public function grading_strategy_instance() {
3d2924e9
DM
648 global $CFG; // because we require other libs here
649
3fd2b0e1 650 if (is_null($this->strategyinstance)) {
f05c168d 651 $strategylib = dirname(__FILE__) . '/form/' . $this->strategy . '/lib.php';
6e309973
DM
652 if (is_readable($strategylib)) {
653 require_once($strategylib);
654 } else {
f05c168d 655 throw new coding_exception('the grading forms subplugin must contain library ' . $strategylib);
6e309973 656 }
0dc47fb9 657 $classname = 'workshop_' . $this->strategy . '_strategy';
3fd2b0e1
DM
658 $this->strategyinstance = new $classname($this);
659 if (!in_array('workshop_strategy', class_implements($this->strategyinstance))) {
b761e6d9 660 throw new coding_exception($classname . ' does not implement workshop_strategy interface');
6e309973
DM
661 }
662 }
3fd2b0e1 663 return $this->strategyinstance;
6e309973
DM
664 }
665
45d24d39
DM
666 /**
667 * Returns instance of grading evaluation class
668 *
7a789aa8 669 * @return stdclass Instance of a grading evaluation
45d24d39
DM
670 */
671 public function grading_evaluation_instance() {
672 global $CFG; // because we require other libs here
673
674 if (is_null($this->evaluationinstance)) {
675 $evaluationlib = dirname(__FILE__) . '/eval/' . $this->evaluation . '/lib.php';
676 if (is_readable($evaluationlib)) {
677 require_once($evaluationlib);
678 } else {
679 throw new coding_exception('the grading evaluation subplugin must contain library ' . $evaluationlib);
680 }
681 $classname = 'workshop_' . $this->evaluation . '_evaluation';
682 $this->evaluationinstance = new $classname($this);
683 if (!in_array('workshop_evaluation', class_implements($this->evaluationinstance))) {
684 throw new coding_exception($classname . ' does not implement workshop_evaluation interface');
685 }
686 }
687 return $this->evaluationinstance;
688 }
689
66c9894d
DM
690 /**
691 * Returns instance of submissions allocator
53fad4b9 692 *
130ae619 693 * @param string $method The name of the allocation method, must be PARAM_ALPHA
7a789aa8 694 * @return stdclass Instance of submissions allocator
66c9894d
DM
695 */
696 public function allocator_instance($method) {
3d2924e9
DM
697 global $CFG; // because we require other libs here
698
f05c168d 699 $allocationlib = dirname(__FILE__) . '/allocation/' . $method . '/lib.php';
66c9894d
DM
700 if (is_readable($allocationlib)) {
701 require_once($allocationlib);
702 } else {
f05c168d 703 throw new coding_exception('Unable to find the allocation library ' . $allocationlib);
66c9894d
DM
704 }
705 $classname = 'workshop_' . $method . '_allocator';
706 return new $classname($this);
707 }
708
b8ead2e6 709 /**
454e8dd9 710 * @return moodle_url of this workshop's view page
b8ead2e6
DM
711 */
712 public function view_url() {
713 global $CFG;
714 return new moodle_url($CFG->wwwroot . '/mod/workshop/view.php', array('id' => $this->cm->id));
715 }
716
717 /**
454e8dd9 718 * @return moodle_url of the page for editing this workshop's grading form
b8ead2e6
DM
719 */
720 public function editform_url() {
721 global $CFG;
722 return new moodle_url($CFG->wwwroot . '/mod/workshop/editform.php', array('cmid' => $this->cm->id));
723 }
724
725 /**
454e8dd9 726 * @return moodle_url of the page for previewing this workshop's grading form
b8ead2e6
DM
727 */
728 public function previewform_url() {
729 global $CFG;
43b34576 730 return new moodle_url($CFG->wwwroot . '/mod/workshop/editformpreview.php', array('cmid' => $this->cm->id));
b8ead2e6
DM
731 }
732
733 /**
734 * @param int $assessmentid The ID of assessment record
454e8dd9 735 * @return moodle_url of the assessment page
b8ead2e6 736 */
a39d7d87 737 public function assess_url($assessmentid) {
b8ead2e6 738 global $CFG;
454e8dd9 739 $assessmentid = clean_param($assessmentid, PARAM_INT);
a39d7d87 740 return new moodle_url($CFG->wwwroot . '/mod/workshop/assessment.php', array('asid' => $assessmentid));
b8ead2e6
DM
741 }
742
becec954
DM
743 /**
744 * @param int $assessmentid The ID of assessment record
745 * @return moodle_url of the example assessment page
746 */
747 public function exassess_url($assessmentid) {
748 global $CFG;
749 $assessmentid = clean_param($assessmentid, PARAM_INT);
750 return new moodle_url($CFG->wwwroot . '/mod/workshop/exassessment.php', array('asid' => $assessmentid));
751 }
752
39861053 753 /**
67cd00ba 754 * @return moodle_url of the page to view a submission, defaults to the own one
39861053 755 */
67cd00ba 756 public function submission_url($id=null) {
39861053 757 global $CFG;
67cd00ba 758 return new moodle_url($CFG->wwwroot . '/mod/workshop/submission.php', array('cmid' => $this->cm->id, 'id' => $id));
39861053
DM
759 }
760
81eccf0a
DM
761 /**
762 * @param int $id example submission id
763 * @return moodle_url of the page to view an example submission
764 */
becec954 765 public function exsubmission_url($id) {
81eccf0a 766 global $CFG;
becec954 767 return new moodle_url($CFG->wwwroot . '/mod/workshop/exsubmission.php', array('cmid' => $this->cm->id, 'id' => $id));
81eccf0a
DM
768 }
769
cbf87967
DM
770 /**
771 * @param int $sid submission id
772 * @param array $aid of int assessment ids
773 * @return moodle_url of the page to compare assessments of the given submission
774 */
775 public function compare_url($sid, array $aids) {
776 global $CFG;
777
778 $url = new moodle_url($CFG->wwwroot . '/mod/workshop/compare.php', array('cmid' => $this->cm->id, 'sid' => $sid));
779 $i = 0;
780 foreach ($aids as $aid) {
781 $url->param("aid{$i}", $aid);
782 $i++;
783 }
784 return $url;
785 }
786
787 /**
788 * @param int $sid submission id
789 * @param int $aid assessment id
790 * @return moodle_url of the page to compare the reference assessments of the given example submission
791 */
792 public function excompare_url($sid, $aid) {
793 global $CFG;
794 return new moodle_url($CFG->wwwroot . '/mod/workshop/excompare.php', array('cmid' => $this->cm->id, 'sid' => $sid, 'aid' => $aid));
795 }
796
da0b1f70 797 /**
454e8dd9 798 * @return moodle_url of the mod_edit form
da0b1f70
DM
799 */
800 public function updatemod_url() {
801 global $CFG;
802 return new moodle_url($CFG->wwwroot . '/course/modedit.php', array('update' => $this->cm->id, 'return' => 1));
803 }
804
454e8dd9
DM
805 /**
806 * @return moodle_url to the allocation page
807 */
da0b1f70
DM
808 public function allocation_url() {
809 global $CFG;
810 return new moodle_url($CFG->wwwroot . '/mod/workshop/allocation.php', array('cmid' => $this->cm->id));
811 }
812
454e8dd9
DM
813 /**
814 * @param int $phasecode The internal phase code
815 * @return moodle_url of the script to change the current phase to $phasecode
816 */
817 public function switchphase_url($phasecode) {
818 global $CFG;
819 $phasecode = clean_param($phasecode, PARAM_INT);
820 return new moodle_url($CFG->wwwroot . '/mod/workshop/switchphase.php', array('cmid' => $this->cm->id, 'phase' => $phasecode));
821 }
822
89c1aa97
DM
823 /**
824 * @return moodle_url to the aggregation page
825 */
826 public function aggregate_url() {
827 global $CFG;
828 return new moodle_url($CFG->wwwroot . '/mod/workshop/aggregate.php', array('cmid' => $this->cm->id));
829 }
830
b8ead2e6 831 /**
407b1e91
DM
832 * Are users allowed to create/edit their submissions?
833 *
407b1e91 834 * @return bool
b8ead2e6 835 */
407b1e91 836 public function submitting_allowed() {
74bf8a94
DM
837 if ($this->phase != self::PHASE_SUBMISSION) {
838 // submitting is not allowed but in the submission phase
839 return false;
840 }
841 $now = time();
842 if (!empty($this->submissionstart) and $this->submissionstart > $now) {
843 // if enabled, submitting is not allowed before the date/time defined in the mod_form
844 return false;
845 }
846 if (!empty($this->submissionend) and empty($this->latesubmissions) and $now > $this->submissionend ) {
847 // if enabled, submitting is not allowed after the date/time defined in the mod_form unless late submission is allowed
848 return false;
849 }
850 // here we go, submission is allowed
407b1e91 851 return true;
b8ead2e6
DM
852 }
853
c1e883bb 854 /**
407b1e91 855 * Are reviewers allowed to create/edit their assessments?
c1e883bb 856 *
c1e883bb
DM
857 * @return bool
858 */
407b1e91 859 public function assessing_allowed() {
74bf8a94
DM
860 if ($this->phase != self::PHASE_ASSESSMENT) {
861 // assessing is not allowed but in the assessment phase
862 return false;
863 }
864 $now = time();
865 if (!empty($this->assessmentstart) and $this->assessmentstart > $now) {
866 // if enabled, assessing is not allowed before the date/time defined in the mod_form
867 return false;
868 }
869 if (!empty($this->assessmentend) and $now > $this->assessmentend ) {
870 // if enabled, assessing is not allowed after the date/time defined in the mod_form
871 return false;
872 }
873 // here we go, assessing is allowed
c1e883bb
DM
874 return true;
875 }
876
becec954
DM
877 /**
878 * Are reviewers allowed to create/edit their assessments of the example submissions?
879 *
74bf8a94 880 * Note this does not check other conditions like the number of already submitted examples etc.
becec954 881 *
74bf8a94 882 * @return null|bool
becec954
DM
883 */
884 public function assessing_examples_allowed() {
74bf8a94
DM
885 if (empty($this->useexamples)) {
886 return null;
887 }
888 if (self::EXAMPLES_VOLUNTARY == $this->examplesmode) {
889 return true;
890 }
891 if (self::EXAMPLES_BEFORE_SUBMISSION == $this->examplesmode and self::PHASE_SUBMISSION == $this->phase) {
892 return true;
893 }
894 if (self::EXAMPLES_BEFORE_ASSESSMENT == $this->examplesmode and self::PHASE_ASSESSMENT == $this->phase) {
895 return true;
896 }
897 return false;
becec954 898 }
407b1e91 899
3dc78e5b
DM
900 /**
901 * Are the peer-reviews available to the authors?
902 *
903 * TODO: this depends on the workshop phase
904 *
905 * @return bool
906 */
907 public function assessments_available() {
908 return true;
909 }
910
911 /**
912 * Can the given grades be displayed to the authors?
913 *
914 * Grades are not displayed if {@link self::assessments_available()} return false. The returned
f6e8b318 915 * value may be true (if yes, display grades) or false (no, hide grades yet)
3dc78e5b 916 *
f6e8b318 917 * @return bool
3dc78e5b
DM
918 */
919 public function grades_available() {
920 return true;
921 }
922
b761e6d9
DM
923 /**
924 * Prepare an individual workshop plan for the given user.
925 *
f05c168d 926 * @param int $userid whom the plan is prepared for
7a789aa8
DM
927 * @param stdclass context of the planned workshop
928 * @return stdclass data object to be passed to the renderer
b761e6d9 929 */
d895c6aa 930 public function prepare_user_plan($userid) {
b761e6d9
DM
931 global $DB;
932
933 $phases = array();
934
935 // Prepare tasks for the setup phase
7a789aa8 936 $phase = new stdclass();
b761e6d9
DM
937 $phase->title = get_string('phasesetup', 'workshop');
938 $phase->tasks = array();
d895c6aa 939 if (has_capability('moodle/course:manageactivities', $this->context, $userid)) {
7a789aa8 940 $task = new stdclass();
da0b1f70
DM
941 $task->title = get_string('taskintro', 'workshop');
942 $task->link = $this->updatemod_url();
943 $task->completed = !(trim(strip_tags($this->intro)) == '');
944 $phase->tasks['intro'] = $task;
945 }
d895c6aa 946 if (has_capability('moodle/course:manageactivities', $this->context, $userid)) {
7a789aa8 947 $task = new stdclass();
454e8dd9
DM
948 $task->title = get_string('taskinstructauthors', 'workshop');
949 $task->link = $this->updatemod_url();
950 $task->completed = !(trim(strip_tags($this->instructauthors)) == '');
951 $phase->tasks['instructauthors'] = $task;
952 }
d895c6aa 953 if (has_capability('mod/workshop:editdimensions', $this->context, $userid)) {
7a789aa8 954 $task = new stdclass();
da0b1f70
DM
955 $task->title = get_string('editassessmentform', 'workshop');
956 $task->link = $this->editform_url();
f6e8b318 957 if ($this->grading_strategy_instance()->form_ready()) {
da0b1f70
DM
958 $task->completed = true;
959 } elseif ($this->phase > self::PHASE_SETUP) {
960 $task->completed = false;
961 }
b761e6d9
DM
962 $phase->tasks['editform'] = $task;
963 }
81eccf0a 964 if ($this->useexamples and has_capability('mod/workshop:manageexamples', $this->context, $userid)) {
7a789aa8 965 $task = new stdclass();
81eccf0a
DM
966 $task->title = get_string('prepareexamples', 'workshop');
967 if ($DB->count_records('workshop_submissions', array('example' => 1, 'workshopid' => $this->id)) > 0) {
968 $task->completed = true;
969 } elseif ($this->phase > self::PHASE_SETUP) {
970 $task->completed = false;
971 }
972 $phase->tasks['prepareexamples'] = $task;
973 }
da0b1f70
DM
974 if (empty($phase->tasks) and $this->phase == self::PHASE_SETUP) {
975 // if we are in the setup phase and there is no task (typical for students), let us
976 // display some explanation what is going on
7a789aa8 977 $task = new stdclass();
da0b1f70
DM
978 $task->title = get_string('undersetup', 'workshop');
979 $task->completed = 'info';
980 $phase->tasks['setupinfo'] = $task;
981 }
b761e6d9
DM
982 $phases[self::PHASE_SETUP] = $phase;
983
984 // Prepare tasks for the submission phase
7a789aa8 985 $phase = new stdclass();
b761e6d9
DM
986 $phase->title = get_string('phasesubmission', 'workshop');
987 $phase->tasks = array();
81eccf0a
DM
988 if (($this->usepeerassessment or $this->useselfassessment)
989 and has_capability('moodle/course:manageactivities', $this->context, $userid)) {
7a789aa8 990 $task = new stdclass();
00aca3c1
DM
991 $task->title = get_string('taskinstructreviewers', 'workshop');
992 $task->link = $this->updatemod_url();
993 if (trim(strip_tags($this->instructreviewers))) {
da0b1f70
DM
994 $task->completed = true;
995 } elseif ($this->phase >= self::PHASE_ASSESSMENT) {
996 $task->completed = false;
da0b1f70 997 }
00aca3c1 998 $phase->tasks['instructreviewers'] = $task;
b761e6d9 999 }
d895c6aa 1000 if (has_capability('mod/workshop:submit', $this->context, $userid, false)) {
7a789aa8 1001 $task = new stdclass();
00aca3c1
DM
1002 $task->title = get_string('tasksubmit', 'workshop');
1003 $task->link = $this->submission_url();
1004 if ($DB->record_exists('workshop_submissions', array('workshopid'=>$this->id, 'example'=>0, 'authorid'=>$userid))) {
da0b1f70
DM
1005 $task->completed = true;
1006 } elseif ($this->phase >= self::PHASE_ASSESSMENT) {
1007 $task->completed = false;
00aca3c1
DM
1008 } else {
1009 $task->completed = null; // still has a chance to submit
da0b1f70 1010 }
00aca3c1 1011 $phase->tasks['submit'] = $task;
da0b1f70 1012 }
d895c6aa 1013 if (has_capability('mod/workshop:allocate', $this->context, $userid)) {
7a789aa8 1014 $task = new stdclass();
da0b1f70
DM
1015 $task->title = get_string('allocate', 'workshop');
1016 $task->link = $this->allocation_url();
d895c6aa 1017 $numofauthors = count(get_users_by_capability($this->context, 'mod/workshop:submit', 'u.id', '', '', '',
a3610b08
DM
1018 '', '', false, true));
1019 $numofsubmissions = $DB->count_records('workshop_submissions', array('workshopid'=>$this->id, 'example'=>0));
1020 $sql = 'SELECT COUNT(s.id) AS nonallocated
1021 FROM {workshop_submissions} s
1022 LEFT JOIN {workshop_assessments} a ON (a.submissionid=s.id)
1023 WHERE s.workshopid = :workshopid AND s.example=0 AND a.submissionid IS NULL';
1024 $params['workshopid'] = $this->id;
1025 $numnonallocated = $DB->count_records_sql($sql, $params);
da0b1f70
DM
1026 if ($numofsubmissions == 0) {
1027 $task->completed = null;
a3610b08 1028 } elseif ($numnonallocated == 0) {
da0b1f70
DM
1029 $task->completed = true;
1030 } elseif ($this->phase > self::PHASE_SUBMISSION) {
1031 $task->completed = false;
1032 } else {
1033 $task->completed = null; // still has a chance to allocate
1034 }
7a789aa8 1035 $a = new stdclass();
3189fb2d
DM
1036 $a->expected = $numofauthors;
1037 $a->submitted = $numofsubmissions;
a3610b08 1038 $a->allocate = $numnonallocated;
3189fb2d 1039 $task->details = get_string('allocatedetails', 'workshop', $a);
da0b1f70 1040 unset($a);
3189fb2d 1041 $phase->tasks['allocate'] = $task;
3dc78e5b
DM
1042
1043 if ($numofsubmissions < $numofauthors and $this->phase >= self::PHASE_SUBMISSION) {
7a789aa8 1044 $task = new stdclass();
3dc78e5b
DM
1045 $task->title = get_string('someuserswosubmission', 'workshop');
1046 $task->completed = 'info';
1047 $phase->tasks['allocateinfo'] = $task;
1048 }
da0b1f70 1049 }
81eccf0a 1050 $phases[self::PHASE_SUBMISSION] = $phase;
b761e6d9
DM
1051
1052 // Prepare tasks for the peer-assessment phase (includes eventual self-assessments)
7a789aa8 1053 $phase = new stdclass();
b761e6d9
DM
1054 $phase->title = get_string('phaseassessment', 'workshop');
1055 $phase->tasks = array();
d895c6aa 1056 $phase->isreviewer = has_capability('mod/workshop:peerassess', $this->context, $userid);
3dc78e5b 1057 $phase->assessments = $this->get_assessments_by_reviewer($userid);
b761e6d9
DM
1058 $numofpeers = 0; // number of allocated peer-assessments
1059 $numofpeerstodo = 0; // number of peer-assessments to do
1060 $numofself = 0; // number of allocated self-assessments - should be 0 or 1
1061 $numofselftodo = 0; // number of self-assessments to do - should be 0 or 1
1062 foreach ($phase->assessments as $a) {
1063 if ($a->authorid == $userid) {
1064 $numofself++;
1065 if (is_null($a->grade)) {
1066 $numofselftodo++;
1067 }
1068 } else {
1069 $numofpeers++;
1070 if (is_null($a->grade)) {
1071 $numofpeerstodo++;
1072 }
1073 }
1074 }
1075 unset($a);
81eccf0a 1076 if ($this->usepeerassessment and $numofpeers) {
7a789aa8 1077 $task = new stdclass();
3dc78e5b
DM
1078 if ($numofpeerstodo == 0) {
1079 $task->completed = true;
1080 } elseif ($this->phase > self::PHASE_ASSESSMENT) {
1081 $task->completed = false;
1082 }
7a789aa8 1083 $a = new stdclass();
b761e6d9
DM
1084 $a->total = $numofpeers;
1085 $a->todo = $numofpeerstodo;
1086 $task->title = get_string('taskassesspeers', 'workshop');
da0b1f70 1087 $task->details = get_string('taskassesspeersdetails', 'workshop', $a);
b761e6d9
DM
1088 unset($a);
1089 $phase->tasks['assesspeers'] = $task;
1090 }
81eccf0a 1091 if ($this->useselfassessment and $numofself) {
7a789aa8 1092 $task = new stdclass();
3dc78e5b
DM
1093 if ($numofselftodo == 0) {
1094 $task->completed = true;
1095 } elseif ($this->phase > self::PHASE_ASSESSMENT) {
1096 $task->completed = false;
1097 }
b761e6d9
DM
1098 $task->title = get_string('taskassessself', 'workshop');
1099 $phase->tasks['assessself'] = $task;
1100 }
1101 $phases[self::PHASE_ASSESSMENT] = $phase;
1102
1fed6ce3 1103 // Prepare tasks for the grading evaluation phase
7a789aa8 1104 $phase = new stdclass();
b761e6d9
DM
1105 $phase->title = get_string('phaseevaluation', 'workshop');
1106 $phase->tasks = array();
1fed6ce3 1107 if (has_capability('mod/workshop:overridegrades', $this->context)) {
f27b70fb
DM
1108 $expected = count($this->get_potential_authors(false));
1109 $calculated = $DB->count_records_select('workshop_submissions',
1110 'workshopid = ? AND (grade IS NOT NULL OR gradeover IS NOT NULL)', array($this->id));
7a789aa8 1111 $task = new stdclass();
10bc4bce 1112 $task->title = get_string('calculatesubmissiongrades', 'workshop');
7a789aa8 1113 $a = new stdclass();
1fed6ce3 1114 $a->expected = $expected;
f27b70fb 1115 $a->calculated = $calculated;
10bc4bce 1116 $task->details = get_string('calculatesubmissiongradesdetails', 'workshop', $a);
f27b70fb 1117 if ($calculated >= $expected) {
1fed6ce3
DM
1118 $task->completed = true;
1119 } elseif ($this->phase > self::PHASE_EVALUATION) {
1120 $task->completed = false;
1121 }
10bc4bce 1122 $phase->tasks['calculatesubmissiongrade'] = $task;
f27b70fb
DM
1123
1124 $expected = count($this->get_potential_reviewers(false));
1125 $calculated = $DB->count_records_select('workshop_aggregations',
1126 'workshopid = ? AND gradinggrade IS NOT NULL', array($this->id));
7a789aa8 1127 $task = new stdclass();
f27b70fb 1128 $task->title = get_string('calculategradinggrades', 'workshop');
7a789aa8 1129 $a = new stdclass();
f27b70fb
DM
1130 $a->expected = $expected;
1131 $a->calculated = $calculated;
1132 $task->details = get_string('calculategradinggradesdetails', 'workshop', $a);
1133 if ($calculated >= $expected) {
1134 $task->completed = true;
1135 } elseif ($this->phase > self::PHASE_EVALUATION) {
1136 $task->completed = false;
f55650e6 1137 }
f27b70fb
DM
1138 $phase->tasks['calculategradinggrade'] = $task;
1139
d183140d 1140 } elseif ($this->phase == self::PHASE_EVALUATION) {
7a789aa8 1141 $task = new stdclass();
1fed6ce3
DM
1142 $task->title = get_string('evaluategradeswait', 'workshop');
1143 $task->completed = 'info';
1144 $phase->tasks['evaluateinfo'] = $task;
1145 }
b761e6d9
DM
1146 $phases[self::PHASE_EVALUATION] = $phase;
1147
1148 // Prepare tasks for the "workshop closed" phase - todo
7a789aa8 1149 $phase = new stdclass();
b761e6d9
DM
1150 $phase->title = get_string('phaseclosed', 'workshop');
1151 $phase->tasks = array();
1152 $phases[self::PHASE_CLOSED] = $phase;
1153
1154 // Polish data, set default values if not done explicitly
1155 foreach ($phases as $phasecode => $phase) {
1156 $phase->title = isset($phase->title) ? $phase->title : '';
1157 $phase->tasks = isset($phase->tasks) ? $phase->tasks : array();
1158 if ($phasecode == $this->phase) {
1159 $phase->active = true;
1160 } else {
1161 $phase->active = false;
1162 }
454e8dd9
DM
1163 if (!isset($phase->actions)) {
1164 $phase->actions = array();
1165 }
b761e6d9
DM
1166
1167 foreach ($phase->tasks as $taskcode => $task) {
1168 $task->title = isset($task->title) ? $task->title : '';
da0b1f70
DM
1169 $task->link = isset($task->link) ? $task->link : null;
1170 $task->details = isset($task->details) ? $task->details : '';
b761e6d9
DM
1171 $task->completed = isset($task->completed) ? $task->completed : null;
1172 }
1173 }
454e8dd9
DM
1174
1175 // Add phase swithing actions
d895c6aa 1176 if (has_capability('mod/workshop:switchphase', $this->context, $userid)) {
454e8dd9
DM
1177 foreach ($phases as $phasecode => $phase) {
1178 if (! $phase->active) {
7a789aa8 1179 $action = new stdclass();
454e8dd9
DM
1180 $action->type = 'switchphase';
1181 $action->url = $this->switchphase_url($phasecode);
1182 $phase->actions[] = $action;
1183 }
1184 }
1185 }
1186
b761e6d9
DM
1187 return $phases;
1188 }
1189
454e8dd9
DM
1190 /**
1191 * Switch to a new workshop phase
1192 *
1193 * Modifies the underlying database record. You should terminate the script shortly after calling this.
1194 *
1195 * @param int $newphase new phase code
1196 * @return bool true if success, false otherwise
1197 */
1198 public function switch_phase($newphase) {
1199 global $DB;
1200
365c2cc2 1201 $known = $this->available_phases_list();
454e8dd9
DM
1202 if (!isset($known[$newphase])) {
1203 return false;
1204 }
f6e8b318
DM
1205
1206 if (self::PHASE_CLOSED == $newphase) {
f27b70fb 1207 // push the grades into the gradebook
7a789aa8 1208 $workshop = new stdclass();
10bc4bce
DM
1209 foreach ($this as $property => $value) {
1210 $workshop->{$property} = $value;
1211 }
1212 $workshop->course = $this->course->id;
1213 $workshop->cmidnumber = $this->cm->id;
1214 $workshop->modname = 'workshop';
1215 workshop_update_grades($workshop);
f6e8b318
DM
1216 }
1217
454e8dd9
DM
1218 $DB->set_field('workshop', 'phase', $newphase, array('id' => $this->id));
1219 return true;
1220 }
ddb59c77
DM
1221
1222 /**
1223 * Saves a raw grade for submission as calculated from the assessment form fields
1224 *
1225 * @param array $assessmentid assessment record id, must exists
00aca3c1 1226 * @param mixed $grade raw percentual grade from 0.00000 to 100.00000
ddb59c77
DM
1227 * @return false|float the saved grade
1228 */
1229 public function set_peer_grade($assessmentid, $grade) {
1230 global $DB;
1231
1232 if (is_null($grade)) {
1233 return false;
1234 }
7a789aa8 1235 $data = new stdclass();
ddb59c77
DM
1236 $data->id = $assessmentid;
1237 $data->grade = $grade;
1238 $DB->update_record('workshop_assessments', $data);
1239 return $grade;
1240 }
6516b9e9 1241
29dc43e7
DM
1242 /**
1243 * Prepares data object with all workshop grades to be rendered
1244 *
5e71cefb
DM
1245 * @param int $userid the user we are preparing the report for
1246 * @param mixed $groups single group or array of groups - only show users who are in one of these group(s). Defaults to all
29dc43e7 1247 * @param int $page the current page (for the pagination)
5e71cefb 1248 * @param int $perpage participants per page (for the pagination)
f27b70fb 1249 * @param string $sortby lastname|firstname|submissiontitle|submissiongrade|gradinggrade
5e71cefb 1250 * @param string $sorthow ASC|DESC
7a789aa8 1251 * @return stdclass data for the renderer
29dc43e7 1252 */
d895c6aa 1253 public function prepare_grading_report($userid, $groups, $page, $perpage, $sortby, $sorthow) {
29dc43e7
DM
1254 global $DB;
1255
d895c6aa
DM
1256 $canviewall = has_capability('mod/workshop:viewallassessments', $this->context, $userid);
1257 $isparticipant = has_any_capability(array('mod/workshop:submit', 'mod/workshop:peerassess'), $this->context, $userid);
29dc43e7
DM
1258
1259 if (!$canviewall and !$isparticipant) {
1260 // who the hell is this?
1261 return array();
1262 }
1263
f27b70fb 1264 if (!in_array($sortby, array('lastname','firstname','submissiontitle','submissiongrade','gradinggrade'))) {
5e71cefb
DM
1265 $sortby = 'lastname';
1266 }
1267
1268 if (!($sorthow === 'ASC' or $sorthow === 'DESC')) {
1269 $sorthow = 'ASC';
1270 }
1271
1272 // get the list of user ids to be displayed
29dc43e7
DM
1273 if ($canviewall) {
1274 // fetch the list of ids of all workshop participants - this may get really long so fetch just id
d895c6aa 1275 $participants = get_users_by_capability($this->context, array('mod/workshop:submit', 'mod/workshop:peerassess'),
5e71cefb 1276 'u.id', '', '', '', $groups, '', false, false, true);
29dc43e7
DM
1277 } else {
1278 // this is an ordinary workshop participant (aka student) - display the report just for him/her
1279 $participants = array($userid => (object)array('id' => $userid));
1280 }
1281
5e71cefb 1282 // we will need to know the number of all records later for the pagination purposes
29dc43e7
DM
1283 $numofparticipants = count($participants);
1284
deea6e7a
DM
1285 if ($numofparticipants > 0) {
1286 // load all fields which can be used for sorting and paginate the records
1287 list($participantids, $params) = $DB->get_in_or_equal(array_keys($participants), SQL_PARAMS_NAMED);
1288 $params['workshopid1'] = $this->id;
1289 $params['workshopid2'] = $this->id;
1290 $sqlsort = $sortby . ' ' . $sorthow . ',u.lastname,u.firstname,u.id';
1291 $sql = "SELECT u.id AS userid,u.firstname,u.lastname,u.picture,u.imagealt,
1292 s.title AS submissiontitle, s.grade AS submissiongrade, ag.gradinggrade
1293 FROM {user} u
1294 LEFT JOIN {workshop_submissions} s ON (s.authorid = u.id AND s.workshopid = :workshopid1 AND s.example = 0)
1295 LEFT JOIN {workshop_aggregations} ag ON (ag.userid = u.id AND ag.workshopid = :workshopid2)
1296 WHERE u.id $participantids
1297 ORDER BY $sqlsort";
1298 $participants = $DB->get_records_sql($sql, $params, $page * $perpage, $perpage);
1299 } else {
1300 $participants = array();
1301 }
29dc43e7
DM
1302
1303 // this will hold the information needed to display user names and pictures
5e71cefb
DM
1304 $userinfo = array();
1305
1306 // get the user details for all participants to display
1307 foreach ($participants as $participant) {
1308 if (!isset($userinfo[$participant->userid])) {
7a789aa8 1309 $userinfo[$participant->userid] = new stdclass();
5e71cefb
DM
1310 $userinfo[$participant->userid]->id = $participant->userid;
1311 $userinfo[$participant->userid]->firstname = $participant->firstname;
1312 $userinfo[$participant->userid]->lastname = $participant->lastname;
1313 $userinfo[$participant->userid]->picture = $participant->picture;
1314 $userinfo[$participant->userid]->imagealt = $participant->imagealt;
1315 }
1316 }
29dc43e7 1317
5e71cefb 1318 // load the submissions details
29dc43e7 1319 $submissions = $this->get_submissions(array_keys($participants));
5e71cefb
DM
1320
1321 // get the user details for all moderators (teachers) that have overridden a submission grade
29dc43e7 1322 foreach ($submissions as $submission) {
29dc43e7 1323 if (!isset($userinfo[$submission->gradeoverby])) {
7a789aa8 1324 $userinfo[$submission->gradeoverby] = new stdclass();
29dc43e7
DM
1325 $userinfo[$submission->gradeoverby]->id = $submission->gradeoverby;
1326 $userinfo[$submission->gradeoverby]->firstname = $submission->overfirstname;
1327 $userinfo[$submission->gradeoverby]->lastname = $submission->overlastname;
1328 $userinfo[$submission->gradeoverby]->picture = $submission->overpicture;
1329 $userinfo[$submission->gradeoverby]->imagealt = $submission->overimagealt;
1330 }
1331 }
1332
5e71cefb 1333 // get the user details for all reviewers of the displayed participants
29dc43e7
DM
1334 $reviewers = array();
1335 if ($submissions) {
1336 list($submissionids, $params) = $DB->get_in_or_equal(array_keys($submissions), SQL_PARAMS_NAMED);
581878b8 1337 $sql = "SELECT a.id AS assessmentid, a.submissionid, a.grade, a.gradinggrade, a.gradinggradeover, a.weight,
29dc43e7
DM
1338 r.id AS reviewerid, r.lastname, r.firstname, r.picture, r.imagealt,
1339 s.id AS submissionid, s.authorid
1340 FROM {workshop_assessments} a
1341 JOIN {user} r ON (a.reviewerid = r.id)
1342 JOIN {workshop_submissions} s ON (a.submissionid = s.id)
1343 WHERE a.submissionid $submissionids";
1344 $reviewers = $DB->get_records_sql($sql, $params);
1345 foreach ($reviewers as $reviewer) {
1346 if (!isset($userinfo[$reviewer->reviewerid])) {
7a789aa8 1347 $userinfo[$reviewer->reviewerid] = new stdclass();
29dc43e7
DM
1348 $userinfo[$reviewer->reviewerid]->id = $reviewer->reviewerid;
1349 $userinfo[$reviewer->reviewerid]->firstname = $reviewer->firstname;
1350 $userinfo[$reviewer->reviewerid]->lastname = $reviewer->lastname;
1351 $userinfo[$reviewer->reviewerid]->picture = $reviewer->picture;
1352 $userinfo[$reviewer->reviewerid]->imagealt = $reviewer->imagealt;
1353 }
1354 }
1355 }
1356
5e71cefb 1357 // get the user details for all reviewees of the displayed participants
934329e5
DM
1358 $reviewees = array();
1359 if ($participants) {
1360 list($participantids, $params) = $DB->get_in_or_equal(array_keys($participants), SQL_PARAMS_NAMED);
1361 $params['workshopid'] = $this->id;
581878b8 1362 $sql = "SELECT a.id AS assessmentid, a.submissionid, a.grade, a.gradinggrade, a.gradinggradeover, a.reviewerid, a.weight,
934329e5
DM
1363 s.id AS submissionid,
1364 e.id AS authorid, e.lastname, e.firstname, e.picture, e.imagealt
1365 FROM {user} u
1366 JOIN {workshop_assessments} a ON (a.reviewerid = u.id)
1367 JOIN {workshop_submissions} s ON (a.submissionid = s.id)
1368 JOIN {user} e ON (s.authorid = e.id)
1369 WHERE u.id $participantids AND s.workshopid = :workshopid";
1370 $reviewees = $DB->get_records_sql($sql, $params);
1371 foreach ($reviewees as $reviewee) {
1372 if (!isset($userinfo[$reviewee->authorid])) {
7a789aa8 1373 $userinfo[$reviewee->authorid] = new stdclass();
934329e5
DM
1374 $userinfo[$reviewee->authorid]->id = $reviewee->authorid;
1375 $userinfo[$reviewee->authorid]->firstname = $reviewee->firstname;
1376 $userinfo[$reviewee->authorid]->lastname = $reviewee->lastname;
1377 $userinfo[$reviewee->authorid]->picture = $reviewee->picture;
1378 $userinfo[$reviewee->authorid]->imagealt = $reviewee->imagealt;
1379 }
29dc43e7
DM
1380 }
1381 }
1382
5e71cefb
DM
1383 // finally populate the object to be rendered
1384 $grades = $participants;
29dc43e7
DM
1385
1386 foreach ($participants as $participant) {
1387 // set up default (null) values
d183140d
DM
1388 $grades[$participant->userid]->submissionid = null;
1389 $grades[$participant->userid]->submissiontitle = null;
1390 $grades[$participant->userid]->submissiongrade = null;
1391 $grades[$participant->userid]->submissiongradeover = null;
1392 $grades[$participant->userid]->submissiongradeoverby = null;
5e71cefb
DM
1393 $grades[$participant->userid]->reviewedby = array();
1394 $grades[$participant->userid]->reviewerof = array();
29dc43e7
DM
1395 }
1396 unset($participants);
1397 unset($participant);
1398
1399 foreach ($submissions as $submission) {
1400 $grades[$submission->authorid]->submissionid = $submission->id;
1401 $grades[$submission->authorid]->submissiontitle = $submission->title;
b4857acb
DM
1402 $grades[$submission->authorid]->submissiongrade = $this->real_grade($submission->grade);
1403 $grades[$submission->authorid]->submissiongradeover = $this->real_grade($submission->gradeover);
29dc43e7
DM
1404 $grades[$submission->authorid]->submissiongradeoverby = $submission->gradeoverby;
1405 }
1406 unset($submissions);
1407 unset($submission);
1408
1409 foreach($reviewers as $reviewer) {
7a789aa8 1410 $info = new stdclass();
29dc43e7
DM
1411 $info->userid = $reviewer->reviewerid;
1412 $info->assessmentid = $reviewer->assessmentid;
1413 $info->submissionid = $reviewer->submissionid;
b4857acb
DM
1414 $info->grade = $this->real_grade($reviewer->grade);
1415 $info->gradinggrade = $this->real_grading_grade($reviewer->gradinggrade);
1416 $info->gradinggradeover = $this->real_grading_grade($reviewer->gradinggradeover);
581878b8 1417 $info->weight = $reviewer->weight;
29dc43e7
DM
1418 $grades[$reviewer->authorid]->reviewedby[$reviewer->reviewerid] = $info;
1419 }
1420 unset($reviewers);
1421 unset($reviewer);
1422
1423 foreach($reviewees as $reviewee) {
7a789aa8 1424 $info = new stdclass();
29dc43e7
DM
1425 $info->userid = $reviewee->authorid;
1426 $info->assessmentid = $reviewee->assessmentid;
1427 $info->submissionid = $reviewee->submissionid;
b4857acb
DM
1428 $info->grade = $this->real_grade($reviewee->grade);
1429 $info->gradinggrade = $this->real_grading_grade($reviewee->gradinggrade);
1430 $info->gradinggradeover = $this->real_grading_grade($reviewee->gradinggradeover);
581878b8 1431 $info->weight = $reviewee->weight;
29dc43e7
DM
1432 $grades[$reviewee->reviewerid]->reviewerof[$reviewee->authorid] = $info;
1433 }
1434 unset($reviewees);
1435 unset($reviewee);
1436
b4857acb
DM
1437 foreach ($grades as $grade) {
1438 $grade->gradinggrade = $this->real_grading_grade($grade->gradinggrade);
b4857acb
DM
1439 }
1440
7a789aa8 1441 $data = new stdclass();
29dc43e7
DM
1442 $data->grades = $grades;
1443 $data->userinfo = $userinfo;
1444 $data->totalcount = $numofparticipants;
b4857acb
DM
1445 $data->maxgrade = $this->real_grade(100);
1446 $data->maxgradinggrade = $this->real_grading_grade(100);
29dc43e7
DM
1447 return $data;
1448 }
1449
29dc43e7 1450 /**
b4857acb 1451 * Calculates the real value of a grade
29dc43e7 1452 *
b4857acb
DM
1453 * @param float $value percentual value from 0 to 100
1454 * @param float $max the maximal grade
1455 * @return string
1456 */
1457 public function real_grade_value($value, $max) {
1458 $localized = true;
557a1100 1459 if (is_null($value) or $value === '') {
b4857acb
DM
1460 return null;
1461 } elseif ($max == 0) {
1462 return 0;
1463 } else {
1464 return format_float($max * $value / 100, $this->gradedecimals, $localized);
1465 }
1466 }
1467
e554671d
DM
1468 /**
1469 * Calculates the raw (percentual) value from a real grade
1470 *
1471 * This is used in cases when a user wants to give a grade such as 12 of 20 and we need to save
1472 * this value in a raw percentual form into DB
1473 * @param float $value given grade
1474 * @param float $max the maximal grade
1475 * @return float suitable to be stored as numeric(10,5)
1476 */
1477 public function raw_grade_value($value, $max) {
557a1100 1478 if (is_null($value) or $value === '') {
e554671d
DM
1479 return null;
1480 }
1481 if ($max == 0 or $value < 0) {
1482 return 0;
1483 }
1484 $p = $value / $max * 100;
1485 if ($p > 100) {
1486 return $max;
1487 }
1488 return grade_floatval($p);
1489 }
1490
b4857acb
DM
1491 /**
1492 * Calculates the real value of grade for submission
1493 *
1494 * @param float $value percentual value from 0 to 100
1495 * @return string
1496 */
1497 public function real_grade($value) {
1498 return $this->real_grade_value($value, $this->grade);
1499 }
1500
1501 /**
1502 * Calculates the real value of grade for assessment
1503 *
1504 * @param float $value percentual value from 0 to 100
1505 * @return string
1506 */
1507 public function real_grading_grade($value) {
1508 return $this->real_grade_value($value, $this->gradinggrade);
29dc43e7
DM
1509 }
1510
89c1aa97 1511 /**
e9a90e69 1512 * Calculates grades for submission for the given participant(s) and updates it in the database
89c1aa97
DM
1513 *
1514 * @param null|int|array $restrict If null, update all authors, otherwise update just grades for the given author(s)
1515 * @return void
1516 */
8a1ba8ac 1517 public function aggregate_submission_grades($restrict=null) {
89c1aa97
DM
1518 global $DB;
1519
1520 // fetch a recordset with all assessments to process
1696f36c 1521 $sql = 'SELECT s.id AS submissionid, s.grade AS submissiongrade,
89c1aa97
DM
1522 a.weight, a.grade
1523 FROM {workshop_submissions} s
1524 LEFT JOIN {workshop_assessments} a ON (a.submissionid = s.id)
1525 WHERE s.example=0 AND s.workshopid=:workshopid'; // to be cont.
1526 $params = array('workshopid' => $this->id);
1527
1528 if (is_null($restrict)) {
1529 // update all users - no more conditions
1530 } elseif (!empty($restrict)) {
1531 list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED);
1532 $sql .= " AND s.authorid $usql";
1533 $params = array_merge($params, $uparams);
1534 } else {
1535 throw new coding_exception('Empty value is not a valid parameter here');
1536 }
1537
1538 $sql .= ' ORDER BY s.id'; // this is important for bulk processing
89c1aa97 1539
e9a90e69
DM
1540 $rs = $DB->get_recordset_sql($sql, $params);
1541 $batch = array(); // will contain a set of all assessments of a single submission
1542 $previous = null; // a previous record in the recordset
1543
89c1aa97
DM
1544 foreach ($rs as $current) {
1545 if (is_null($previous)) {
1546 // we are processing the very first record in the recordset
1547 $previous = $current;
89c1aa97 1548 }
e9a90e69 1549 if ($current->submissionid == $previous->submissionid) {
89c1aa97 1550 // we are still processing the current submission
e9a90e69
DM
1551 $batch[] = $current;
1552 } else {
1553 // process all the assessments of a sigle submission
1554 $this->aggregate_submission_grades_process($batch);
1555 // and then start to process another submission
1556 $batch = array($current);
1557 $previous = $current;
89c1aa97
DM
1558 }
1559 }
e9a90e69
DM
1560 // do not forget to process the last batch!
1561 $this->aggregate_submission_grades_process($batch);
89c1aa97
DM
1562 $rs->close();
1563 }
1564
1565 /**
1566 * Calculates grades for assessment for the given participant(s)
1567 *
39411930
DM
1568 * Grade for assessment is calculated as a simple mean of all grading grades calculated by the grading evaluator.
1569 * The assessment weight is not taken into account here.
89c1aa97
DM
1570 *
1571 * @param null|int|array $restrict If null, update all reviewers, otherwise update just grades for the given reviewer(s)
1572 * @return void
1573 */
8a1ba8ac 1574 public function aggregate_grading_grades($restrict=null) {
89c1aa97
DM
1575 global $DB;
1576
39411930
DM
1577 // fetch a recordset with all assessments to process
1578 $sql = 'SELECT a.reviewerid, a.gradinggrade, a.gradinggradeover,
1579 ag.id AS aggregationid, ag.gradinggrade AS aggregatedgrade
1580 FROM {workshop_assessments} a
1581 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
1582 LEFT JOIN {workshop_aggregations} ag ON (ag.userid = a.reviewerid AND ag.workshopid = s.workshopid)
1583 WHERE s.example=0 AND s.workshopid=:workshopid'; // to be cont.
1584 $params = array('workshopid' => $this->id);
1585
1586 if (is_null($restrict)) {
1587 // update all users - no more conditions
1588 } elseif (!empty($restrict)) {
1589 list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED);
1590 $sql .= " AND a.reviewerid $usql";
1591 $params = array_merge($params, $uparams);
1592 } else {
1593 throw new coding_exception('Empty value is not a valid parameter here');
1594 }
1595
1596 $sql .= ' ORDER BY a.reviewerid'; // this is important for bulk processing
1597
1598 $rs = $DB->get_recordset_sql($sql, $params);
1599 $batch = array(); // will contain a set of all assessments of a single submission
1600 $previous = null; // a previous record in the recordset
1601
1602 foreach ($rs as $current) {
1603 if (is_null($previous)) {
1604 // we are processing the very first record in the recordset
1605 $previous = $current;
1606 }
1607 if ($current->reviewerid == $previous->reviewerid) {
1608 // we are still processing the current reviewer
1609 $batch[] = $current;
1610 } else {
1611 // process all the assessments of a sigle submission
1612 $this->aggregate_grading_grades_process($batch);
1613 // and then start to process another reviewer
1614 $batch = array($current);
1615 $previous = $current;
1616 }
1617 }
1618 // do not forget to process the last batch!
1619 $this->aggregate_grading_grades_process($batch);
1620 $rs->close();
89c1aa97
DM
1621 }
1622
77f43e7d 1623 /**
f6e8b318 1624 * Returns the mform the teachers use to put a feedback for the reviewer
77f43e7d 1625 *
f6e8b318 1626 * @return workshop_feedbackreviewer_form
77f43e7d 1627 */
7a789aa8 1628 public function get_feedbackreviewer_form(moodle_url $actionurl, stdclass $assessment, $editable=true) {
77f43e7d
DM
1629 global $CFG;
1630 require_once(dirname(__FILE__) . '/feedbackreviewer_form.php');
1631
7a789aa8 1632 $current = new stdclass();
e554671d
DM
1633 $current->asid = $assessment->id;
1634 $current->gradinggrade = $this->real_grading_grade($assessment->gradinggrade);
1635 $current->gradinggradeover = $this->real_grading_grade($assessment->gradinggradeover);
1636 $current->feedbackreviewer = $assessment->feedbackreviewer;
1637 $current->feedbackreviewerformat = $assessment->feedbackreviewerformat;
1638 if (is_null($current->gradinggrade)) {
1639 $current->gradinggrade = get_string('nullgrade', 'workshop');
1640 }
1641
1642 // prepare wysiwyg editor
1643 $current = file_prepare_standard_editor($current, 'feedbackreviewer', array());
1644
77f43e7d 1645 return new workshop_feedbackreviewer_form($actionurl,
e554671d 1646 array('workshop' => $this, 'current' => $current, 'feedbackopts' => array()),
77f43e7d
DM
1647 'post', '', null, $editable);
1648 }
1649
67cd00ba
DM
1650 /**
1651 * Returns the mform the teachers use to put a feedback for the author on their submission
1652 *
1653 * @return workshop_feedbackauthor_form
1654 */
7a789aa8 1655 public function get_feedbackauthor_form(moodle_url $actionurl, stdclass $submission, $editable=true) {
67cd00ba
DM
1656 global $CFG;
1657 require_once(dirname(__FILE__) . '/feedbackauthor_form.php');
1658
7a789aa8 1659 $current = new stdclass();
67cd00ba 1660 $current->submissionid = $submission->id;
557a1100
DM
1661 $current->grade = $this->real_grade($submission->grade);
1662 $current->gradeover = $this->real_grade($submission->gradeover);
1663 $current->feedbackauthor = $submission->feedbackauthor;
1664 $current->feedbackauthorformat = $submission->feedbackauthorformat;
67cd00ba
DM
1665 if (is_null($current->grade)) {
1666 $current->grade = get_string('nullgrade', 'workshop');
1667 }
1668
1669 // prepare wysiwyg editor
1670 $current = file_prepare_standard_editor($current, 'feedbackauthor', array());
1671
1672 return new workshop_feedbackauthor_form($actionurl,
1673 array('workshop' => $this, 'current' => $current, 'feedbackopts' => array()),
1674 'post', '', null, $editable);
1675 }
1676
aa40adbf
DM
1677 ////////////////////////////////////////////////////////////////////////////////
1678 // Internal methods (implementation details) //
1679 ////////////////////////////////////////////////////////////////////////////////
6516b9e9 1680
e9a90e69
DM
1681 /**
1682 * Given an array of all assessments of a single submission, calculates the final grade for this submission
1683 *
1684 * This calculates the weighted mean of the passed assessment grades. If, however, the submission grade
1685 * was overridden by a teacher, the gradeover value is returned and the rest of grades are ignored.
1686 *
7a789aa8 1687 * @param array $assessments of stdclass(->submissionid ->submissiongrade ->gradeover ->weight ->grade)
1fed6ce3 1688 * @return void
e9a90e69
DM
1689 */
1690 protected function aggregate_submission_grades_process(array $assessments) {
1691 global $DB;
1692
1693 $submissionid = null; // the id of the submission being processed
1694 $current = null; // the grade currently saved in database
1695 $finalgrade = null; // the new grade to be calculated
1696 $sumgrades = 0;
1697 $sumweights = 0;
1698
1699 foreach ($assessments as $assessment) {
1700 if (is_null($submissionid)) {
1701 // the id is the same in all records, fetch it during the first loop cycle
1702 $submissionid = $assessment->submissionid;
1703 }
1704 if (is_null($current)) {
1705 // the currently saved grade is the same in all records, fetch it during the first loop cycle
1706 $current = $assessment->submissiongrade;
1707 }
e9a90e69
DM
1708 if (is_null($assessment->grade)) {
1709 // this was not assessed yet
1710 continue;
1711 }
1712 if ($assessment->weight == 0) {
1713 // this does not influence the calculation
1714 continue;
1715 }
1716 $sumgrades += $assessment->grade * $assessment->weight;
1717 $sumweights += $assessment->weight;
1718 }
1719 if ($sumweights > 0 and is_null($finalgrade)) {
1720 $finalgrade = grade_floatval($sumgrades / $sumweights);
1721 }
1722 // check if the new final grade differs from the one stored in the database
1723 if (grade_floats_different($finalgrade, $current)) {
1724 // we need to save new calculation into the database
7a789aa8 1725 $record = new stdclass();
10bc4bce
DM
1726 $record->id = $submissionid;
1727 $record->grade = $finalgrade;
1728 $record->timegraded = time();
1729 $DB->update_record('workshop_submissions', $record);
e9a90e69
DM
1730 }
1731 }
1732
39411930
DM
1733 /**
1734 * Given an array of all assessments done by a single reviewer, calculates the final grading grade
1735 *
1736 * This calculates the simple mean of the passed grading grades. If, however, the grading grade
1737 * was overridden by a teacher, the gradinggradeover value is returned and the rest of grades are ignored.
1738 *
7a789aa8 1739 * @param array $assessments of stdclass(->reviewerid ->gradinggrade ->gradinggradeover ->aggregationid ->aggregatedgrade)
1fed6ce3 1740 * @return void
39411930
DM
1741 */
1742 protected function aggregate_grading_grades_process(array $assessments) {
1743 global $DB;
1744
1745 $reviewerid = null; // the id of the reviewer being processed
1746 $current = null; // the gradinggrade currently saved in database
1747 $finalgrade = null; // the new grade to be calculated
1748 $agid = null; // aggregation id
1749 $sumgrades = 0;
1750 $count = 0;
1751
1752 foreach ($assessments as $assessment) {
1753 if (is_null($reviewerid)) {
1754 // the id is the same in all records, fetch it during the first loop cycle
1755 $reviewerid = $assessment->reviewerid;
1756 }
1757 if (is_null($agid)) {
1758 // the id is the same in all records, fetch it during the first loop cycle
1759 $agid = $assessment->aggregationid;
1760 }
1761 if (is_null($current)) {
1762 // the currently saved grade is the same in all records, fetch it during the first loop cycle
1763 $current = $assessment->aggregatedgrade;
1764 }
1765 if (!is_null($assessment->gradinggradeover)) {
1766 // the grading grade for this assessment is overriden by a teacher
1767 $sumgrades += $assessment->gradinggradeover;
1768 $count++;
1769 } else {
1770 if (!is_null($assessment->gradinggrade)) {
1771 $sumgrades += $assessment->gradinggrade;
1772 $count++;
1773 }
1774 }
1775 }
1776 if ($count > 0) {
1777 $finalgrade = grade_floatval($sumgrades / $count);
1778 }
1779 // check if the new final grade differs from the one stored in the database
1780 if (grade_floats_different($finalgrade, $current)) {
1781 // we need to save new calculation into the database
1782 if (is_null($agid)) {
1783 // no aggregation record yet
7a789aa8 1784 $record = new stdclass();
39411930
DM
1785 $record->workshopid = $this->id;
1786 $record->userid = $reviewerid;
1787 $record->gradinggrade = $finalgrade;
10bc4bce 1788 $record->timegraded = time();
39411930
DM
1789 $DB->insert_record('workshop_aggregations', $record);
1790 } else {
7a789aa8 1791 $record = new stdclass();
10bc4bce
DM
1792 $record->id = $agid;
1793 $record->gradinggrade = $finalgrade;
1794 $record->timegraded = time();
1795 $DB->update_record('workshop_aggregations', $record);
39411930
DM
1796 }
1797 }
1798 }
1799
6516b9e9 1800 /**
aa40adbf 1801 * Given a list of user ids, returns the filtered one containing just ids of users with own submission
6516b9e9 1802 *
aa40adbf
DM
1803 * Example submissions are ignored.
1804 *
1805 * @param array $userids
6516b9e9
DM
1806 * @return array
1807 */
aa40adbf
DM
1808 protected function users_with_submission(array $userids) {
1809 global $DB;
1810
1811 if (empty($userids)) {
1812 return array();
1813 }
1814 $userswithsubmission = array();
1815 list($usql, $uparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
00aca3c1 1816 $sql = "SELECT id,authorid
aa40adbf 1817 FROM {workshop_submissions}
00aca3c1 1818 WHERE example = 0 AND workshopid = :workshopid AND authorid $usql";
aa40adbf
DM
1819 $params = array('workshopid' => $this->id);
1820 $params = array_merge($params, $uparams);
1821 $submissions = $DB->get_records_sql($sql, $params);
1822 foreach ($submissions as $submission) {
00aca3c1 1823 $userswithsubmission[$submission->authorid] = true;
aa40adbf
DM
1824 }
1825
1826 return $userswithsubmission;
6516b9e9
DM
1827 }
1828
aa40adbf
DM
1829 /**
1830 * @return array of available workshop phases
1831 */
365c2cc2 1832 protected function available_phases_list() {
aa40adbf
DM
1833 return array(
1834 self::PHASE_SETUP => true,
1835 self::PHASE_SUBMISSION => true,
1836 self::PHASE_ASSESSMENT => true,
1837 self::PHASE_EVALUATION => true,
1838 self::PHASE_CLOSED => true,
1839 );
1840 }
1841
66c9894d 1842}