workshop viewlets are collapsible
[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 */
f6e8b318
DM
46 const ALLOCATION_EXISTS = -1;
47 const ALLOCATION_ERROR = -2;
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
65ba104c 61 /** @var stdClass course module record */
a39d7d87
DM
62 public $cm = null;
63
65ba104c 64 /** @var stdClass course record */
a39d7d87 65 public $course = null;
6e309973 66
f6e8b318
DM
67 /** @var stdClass context object */
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 *
b13142da 88 * @param stdClass $dbrecord Workshop instance data from {workshop} table
06d73dd5
DM
89 * @param stdClass $cm Course module record as returned by {@link get_coursemodule_from_id()}
90 * @param stdClass $course Course record from {course} table
4efd7b5d 91 * @param stdClass $context The context of the workshop instance
0dc47fb9 92 */
4efd7b5d 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 *
138 * @param stdClass $context
139 * @return array
140 */
141 public static function instruction_editors_options(stdClass $context) {
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.
65ba104c 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.
65ba104c 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 *
65ba104c
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
385 * @return stdClass
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
DM
405 * @param int $id author id
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 example submissions for this workshop
426 *
427 * @return array of records or an empty array
428 */
429 public function get_examples() {
430 global $DB;
431 return $DB->get_records('workshop_submissions', array('workshopid' => $this->id, 'example' => 1), 'title', 'id,title');
432 }
433
434 /**
435 * Returns full record of the given example submission
436 *
437 * @param int $id example submission od
438 * @return object
439 */
440 public function get_example_by_id($id) {
441 global $DB;
442 return $DB->get_record('workshop_submissions',
443 array('id' => $id, 'workshopid' => $this->id, 'example' => 1), '*', MUST_EXIST);
444 }
445
446 /**
447 * Removes the submission and all relevant data
448 *
449 * @param stdClass $submission record to delete
450 * @return void
451 */
452 public function delete_submission(stdClass $submission) {
453 global $DB;
454 $assessments = $DB->get_records('workshop_assessments', array('submissionid' => $submission->id), '', 'id');
455 $this->delete_assessment(array_keys($assessments));
456 $DB->delete_records('workshop_submissions', array('id' => $submission->id));
457 }
458
6e309973 459 /**
3dc78e5b 460 * Returns the list of all assessments in the workshop with some data added
6e309973
DM
461 *
462 * Fetches data from {workshop_assessments} and adds some useful information from other
3dc78e5b
DM
463 * tables. The returned object does not contain textual fields (ie comments) to prevent memory
464 * lack issues.
465 *
466 * @return array [assessmentid] => assessment stdClass
6e309973 467 */
3dc78e5b 468 public function get_all_assessments() {
6e309973 469 global $DB;
53fad4b9 470
f6e8b318 471 $sql = 'SELECT a.id, a.submissionid, a.reviewerid, a.timecreated, a.timemodified,
3dc78e5b 472 a.grade, a.gradinggrade, a.gradinggradeover, a.gradinggradeoverby,
3d2924e9
DM
473 reviewer.id AS reviewerid,reviewer.firstname AS reviewerfirstname,reviewer.lastname as reviewerlastname,
474 s.title,
ddb59c77 475 author.id AS authorid, author.firstname AS authorfirstname,author.lastname AS authorlastname
3d2924e9 476 FROM {workshop_assessments} a
00aca3c1 477 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id)
3d2924e9 478 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
00aca3c1 479 INNER JOIN {user} author ON (s.authorid = author.id)
3dc78e5b
DM
480 WHERE s.workshopid = :workshopid AND s.example = 0
481 ORDER BY reviewer.lastname, reviewer.firstname';
3d2924e9
DM
482 $params = array('workshopid' => $this->id);
483
3dc78e5b 484 return $DB->get_records_sql($sql, $params);
53fad4b9
DM
485 }
486
487 /**
3dc78e5b 488 * Get the complete information about the given assessment
53fad4b9
DM
489 *
490 * @param int $id Assessment ID
65ba104c 491 * @return mixed false if not found, stdClass otherwise
53fad4b9
DM
492 */
493 public function get_assessment_by_id($id) {
3dc78e5b
DM
494 global $DB;
495
496 $sql = 'SELECT a.*,
497 reviewer.id AS reviewerid,reviewer.firstname AS reviewerfirstname,reviewer.lastname as reviewerlastname,
498 s.title,
499 author.id AS authorid, author.firstname AS authorfirstname,author.lastname as authorlastname
500 FROM {workshop_assessments} a
00aca3c1 501 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id)
3dc78e5b 502 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
00aca3c1 503 INNER JOIN {user} author ON (s.authorid = author.id)
3dc78e5b
DM
504 WHERE a.id = :id AND s.workshopid = :workshopid';
505 $params = array('id' => $id, 'workshopid' => $this->id);
506
507 return $DB->get_record_sql($sql, $params, MUST_EXIST);
53fad4b9
DM
508 }
509
510 /**
3dc78e5b 511 * Get the complete information about all assessments allocated to the given reviewer
53fad4b9 512 *
00aca3c1 513 * @param int $reviewerid
3dc78e5b 514 * @return array
53fad4b9 515 */
00aca3c1 516 public function get_assessments_by_reviewer($reviewerid) {
3dc78e5b
DM
517 global $DB;
518
519 $sql = 'SELECT a.*,
ddb59c77
DM
520 reviewer.id AS reviewerid,reviewer.firstname AS reviewerfirstname,reviewer.lastname AS reviewerlastname,
521 s.id AS submissionid, s.title AS submissiontitle, s.timecreated AS submissioncreated,
522 s.timemodified AS submissionmodified,
523 author.id AS authorid, author.firstname AS authorfirstname,author.lastname AS authorlastname,
524 author.picture AS authorpicture, author.imagealt AS authorimagealt
3dc78e5b 525 FROM {workshop_assessments} a
00aca3c1 526 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id)
3dc78e5b 527 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
00aca3c1
DM
528 INNER JOIN {user} author ON (s.authorid = author.id)
529 WHERE s.example = 0 AND reviewer.id = :reviewerid AND s.workshopid = :workshopid';
530 $params = array('reviewerid' => $reviewerid, 'workshopid' => $this->id);
3dc78e5b
DM
531
532 return $DB->get_records_sql($sql, $params);
53fad4b9 533 }
6e309973 534
6e309973
DM
535 /**
536 * Allocate a submission to a user for review
53fad4b9 537 *
65ba104c 538 * @param stdClass $submission Submission record
6e309973 539 * @param int $reviewerid User ID
53fad4b9 540 * @param bool $bulk repeated inserts into DB expected
becec954 541 * @param int $weight of the new assessment, from 0 to 16
6e309973
DM
542 * @return int ID of the new assessment or an error code
543 */
becec954 544 public function add_allocation(stdClass $submission, $reviewerid, $bulk=false, $weight=1) {
6e309973
DM
545 global $DB;
546
00aca3c1 547 if ($DB->record_exists('workshop_assessments', array('submissionid' => $submission->id, 'reviewerid' => $reviewerid))) {
b761e6d9 548 return self::ALLOCATION_EXISTS;
6e309973
DM
549 }
550
6e309973 551 $now = time();
65ba104c 552 $assessment = new stdClass();
e554671d
DM
553 $assessment->submissionid = $submission->id;
554 $assessment->reviewerid = $reviewerid;
555 $assessment->timecreated = $now;
556 $assessment->timemodified = $now;
becec954 557 $assessment->weight = $weight;
e554671d
DM
558 $assessment->generalcommentformat = FORMAT_HTML; // todo better default handling
559 $assessment->feedbackreviewerformat = FORMAT_HTML; // todo better default handling
6e309973 560
235b31c8 561 return $DB->insert_record('workshop_assessments', $assessment, true, $bulk);
6e309973
DM
562 }
563
6e309973 564 /**
53fad4b9 565 * Delete assessment record or records
6e309973 566 *
53fad4b9
DM
567 * @param mixed $id int|array assessment id or array of assessments ids
568 * @return bool false if $id not a valid parameter, true otherwise
6e309973
DM
569 */
570 public function delete_assessment($id) {
571 global $DB;
572
573 // todo remove all given grades from workshop_grades;
6e309973 574
53fad4b9 575 if (is_array($id)) {
235b31c8 576 return $DB->delete_records_list('workshop_assessments', 'id', $id);
3d2924e9 577 } else {
235b31c8 578 return $DB->delete_records('workshop_assessments', array('id' => $id));
53fad4b9 579 }
53fad4b9 580 }
6e309973
DM
581
582 /**
583 * Returns instance of grading strategy class
53fad4b9 584 *
65ba104c 585 * @return stdClass Instance of a grading strategy
6e309973
DM
586 */
587 public function grading_strategy_instance() {
3d2924e9
DM
588 global $CFG; // because we require other libs here
589
3fd2b0e1 590 if (is_null($this->strategyinstance)) {
f05c168d 591 $strategylib = dirname(__FILE__) . '/form/' . $this->strategy . '/lib.php';
6e309973
DM
592 if (is_readable($strategylib)) {
593 require_once($strategylib);
594 } else {
f05c168d 595 throw new coding_exception('the grading forms subplugin must contain library ' . $strategylib);
6e309973 596 }
0dc47fb9 597 $classname = 'workshop_' . $this->strategy . '_strategy';
3fd2b0e1
DM
598 $this->strategyinstance = new $classname($this);
599 if (!in_array('workshop_strategy', class_implements($this->strategyinstance))) {
b761e6d9 600 throw new coding_exception($classname . ' does not implement workshop_strategy interface');
6e309973
DM
601 }
602 }
3fd2b0e1 603 return $this->strategyinstance;
6e309973
DM
604 }
605
45d24d39
DM
606 /**
607 * Returns instance of grading evaluation class
608 *
609 * @return stdClass Instance of a grading evaluation
610 */
611 public function grading_evaluation_instance() {
612 global $CFG; // because we require other libs here
613
614 if (is_null($this->evaluationinstance)) {
615 $evaluationlib = dirname(__FILE__) . '/eval/' . $this->evaluation . '/lib.php';
616 if (is_readable($evaluationlib)) {
617 require_once($evaluationlib);
618 } else {
619 throw new coding_exception('the grading evaluation subplugin must contain library ' . $evaluationlib);
620 }
621 $classname = 'workshop_' . $this->evaluation . '_evaluation';
622 $this->evaluationinstance = new $classname($this);
623 if (!in_array('workshop_evaluation', class_implements($this->evaluationinstance))) {
624 throw new coding_exception($classname . ' does not implement workshop_evaluation interface');
625 }
626 }
627 return $this->evaluationinstance;
628 }
629
66c9894d
DM
630 /**
631 * Returns instance of submissions allocator
53fad4b9 632 *
130ae619 633 * @param string $method The name of the allocation method, must be PARAM_ALPHA
65ba104c 634 * @return stdClass Instance of submissions allocator
66c9894d
DM
635 */
636 public function allocator_instance($method) {
3d2924e9
DM
637 global $CFG; // because we require other libs here
638
f05c168d 639 $allocationlib = dirname(__FILE__) . '/allocation/' . $method . '/lib.php';
66c9894d
DM
640 if (is_readable($allocationlib)) {
641 require_once($allocationlib);
642 } else {
f05c168d 643 throw new coding_exception('Unable to find the allocation library ' . $allocationlib);
66c9894d
DM
644 }
645 $classname = 'workshop_' . $method . '_allocator';
646 return new $classname($this);
647 }
648
b8ead2e6 649 /**
454e8dd9 650 * @return moodle_url of this workshop's view page
b8ead2e6
DM
651 */
652 public function view_url() {
653 global $CFG;
654 return new moodle_url($CFG->wwwroot . '/mod/workshop/view.php', array('id' => $this->cm->id));
655 }
656
657 /**
454e8dd9 658 * @return moodle_url of the page for editing this workshop's grading form
b8ead2e6
DM
659 */
660 public function editform_url() {
661 global $CFG;
662 return new moodle_url($CFG->wwwroot . '/mod/workshop/editform.php', array('cmid' => $this->cm->id));
663 }
664
665 /**
454e8dd9 666 * @return moodle_url of the page for previewing this workshop's grading form
b8ead2e6
DM
667 */
668 public function previewform_url() {
669 global $CFG;
43b34576 670 return new moodle_url($CFG->wwwroot . '/mod/workshop/editformpreview.php', array('cmid' => $this->cm->id));
b8ead2e6
DM
671 }
672
673 /**
674 * @param int $assessmentid The ID of assessment record
454e8dd9 675 * @return moodle_url of the assessment page
b8ead2e6 676 */
a39d7d87 677 public function assess_url($assessmentid) {
b8ead2e6 678 global $CFG;
454e8dd9 679 $assessmentid = clean_param($assessmentid, PARAM_INT);
a39d7d87 680 return new moodle_url($CFG->wwwroot . '/mod/workshop/assessment.php', array('asid' => $assessmentid));
b8ead2e6
DM
681 }
682
becec954
DM
683 /**
684 * @param int $assessmentid The ID of assessment record
685 * @return moodle_url of the example assessment page
686 */
687 public function exassess_url($assessmentid) {
688 global $CFG;
689 $assessmentid = clean_param($assessmentid, PARAM_INT);
690 return new moodle_url($CFG->wwwroot . '/mod/workshop/exassessment.php', array('asid' => $assessmentid));
691 }
692
39861053 693 /**
67cd00ba 694 * @return moodle_url of the page to view a submission, defaults to the own one
39861053 695 */
67cd00ba 696 public function submission_url($id=null) {
39861053 697 global $CFG;
67cd00ba 698 return new moodle_url($CFG->wwwroot . '/mod/workshop/submission.php', array('cmid' => $this->cm->id, 'id' => $id));
39861053
DM
699 }
700
81eccf0a
DM
701 /**
702 * @param int $id example submission id
703 * @return moodle_url of the page to view an example submission
704 */
becec954 705 public function exsubmission_url($id) {
81eccf0a 706 global $CFG;
becec954 707 return new moodle_url($CFG->wwwroot . '/mod/workshop/exsubmission.php', array('cmid' => $this->cm->id, 'id' => $id));
81eccf0a
DM
708 }
709
da0b1f70 710 /**
454e8dd9 711 * @return moodle_url of the mod_edit form
da0b1f70
DM
712 */
713 public function updatemod_url() {
714 global $CFG;
715 return new moodle_url($CFG->wwwroot . '/course/modedit.php', array('update' => $this->cm->id, 'return' => 1));
716 }
717
454e8dd9
DM
718 /**
719 * @return moodle_url to the allocation page
720 */
da0b1f70
DM
721 public function allocation_url() {
722 global $CFG;
723 return new moodle_url($CFG->wwwroot . '/mod/workshop/allocation.php', array('cmid' => $this->cm->id));
724 }
725
454e8dd9
DM
726 /**
727 * @param int $phasecode The internal phase code
728 * @return moodle_url of the script to change the current phase to $phasecode
729 */
730 public function switchphase_url($phasecode) {
731 global $CFG;
732 $phasecode = clean_param($phasecode, PARAM_INT);
733 return new moodle_url($CFG->wwwroot . '/mod/workshop/switchphase.php', array('cmid' => $this->cm->id, 'phase' => $phasecode));
734 }
735
89c1aa97
DM
736 /**
737 * @return moodle_url to the aggregation page
738 */
739 public function aggregate_url() {
740 global $CFG;
741 return new moodle_url($CFG->wwwroot . '/mod/workshop/aggregate.php', array('cmid' => $this->cm->id));
742 }
743
b8ead2e6 744 /**
407b1e91
DM
745 * Are users allowed to create/edit their submissions?
746 *
747 * TODO: this depends on the workshop phase, phase deadlines, submitting after deadlines possibility
b8ead2e6 748 *
407b1e91 749 * @return bool
b8ead2e6 750 */
407b1e91
DM
751 public function submitting_allowed() {
752 return true;
b8ead2e6
DM
753 }
754
c1e883bb 755 /**
407b1e91 756 * Are reviewers allowed to create/edit their assessments?
c1e883bb 757 *
407b1e91 758 * TODO: this depends on the workshop phase, phase deadlines
c1e883bb
DM
759 *
760 * @return bool
761 */
407b1e91 762 public function assessing_allowed() {
c1e883bb
DM
763 return true;
764 }
765
becec954
DM
766 /**
767 * Are reviewers allowed to create/edit their assessments of the example submissions?
768 *
769 * TODO: this depends on the workshop phase, phase deadlines
770 *
771 * @return bool
772 */
773 public function assessing_examples_allowed() {
774 return true;
775 }
407b1e91 776
3dc78e5b
DM
777 /**
778 * Are the peer-reviews available to the authors?
779 *
780 * TODO: this depends on the workshop phase
781 *
782 * @return bool
783 */
784 public function assessments_available() {
785 return true;
786 }
787
788 /**
789 * Can the given grades be displayed to the authors?
790 *
791 * Grades are not displayed if {@link self::assessments_available()} return false. The returned
f6e8b318 792 * value may be true (if yes, display grades) or false (no, hide grades yet)
3dc78e5b 793 *
f6e8b318 794 * @return bool
3dc78e5b
DM
795 */
796 public function grades_available() {
797 return true;
798 }
799
b761e6d9
DM
800 /**
801 * Prepare an individual workshop plan for the given user.
802 *
f05c168d
DM
803 * @param int $userid whom the plan is prepared for
804 * @param stdClass context of the planned workshop
805 * @return stdClass data object to be passed to the renderer
b761e6d9 806 */
d895c6aa 807 public function prepare_user_plan($userid) {
b761e6d9
DM
808 global $DB;
809
810 $phases = array();
811
812 // Prepare tasks for the setup phase
813 $phase = new stdClass();
814 $phase->title = get_string('phasesetup', 'workshop');
815 $phase->tasks = array();
d895c6aa 816 if (has_capability('moodle/course:manageactivities', $this->context, $userid)) {
da0b1f70
DM
817 $task = new stdClass();
818 $task->title = get_string('taskintro', 'workshop');
819 $task->link = $this->updatemod_url();
820 $task->completed = !(trim(strip_tags($this->intro)) == '');
821 $phase->tasks['intro'] = $task;
822 }
d895c6aa 823 if (has_capability('moodle/course:manageactivities', $this->context, $userid)) {
454e8dd9
DM
824 $task = new stdClass();
825 $task->title = get_string('taskinstructauthors', 'workshop');
826 $task->link = $this->updatemod_url();
827 $task->completed = !(trim(strip_tags($this->instructauthors)) == '');
828 $phase->tasks['instructauthors'] = $task;
829 }
d895c6aa 830 if (has_capability('mod/workshop:editdimensions', $this->context, $userid)) {
b761e6d9 831 $task = new stdClass();
da0b1f70
DM
832 $task->title = get_string('editassessmentform', 'workshop');
833 $task->link = $this->editform_url();
f6e8b318 834 if ($this->grading_strategy_instance()->form_ready()) {
da0b1f70
DM
835 $task->completed = true;
836 } elseif ($this->phase > self::PHASE_SETUP) {
837 $task->completed = false;
838 }
b761e6d9
DM
839 $phase->tasks['editform'] = $task;
840 }
81eccf0a
DM
841 if ($this->useexamples and has_capability('mod/workshop:manageexamples', $this->context, $userid)) {
842 $task = new stdClass();
843 $task->title = get_string('prepareexamples', 'workshop');
844 if ($DB->count_records('workshop_submissions', array('example' => 1, 'workshopid' => $this->id)) > 0) {
845 $task->completed = true;
846 } elseif ($this->phase > self::PHASE_SETUP) {
847 $task->completed = false;
848 }
849 $phase->tasks['prepareexamples'] = $task;
850 }
da0b1f70
DM
851 if (empty($phase->tasks) and $this->phase == self::PHASE_SETUP) {
852 // if we are in the setup phase and there is no task (typical for students), let us
853 // display some explanation what is going on
854 $task = new stdClass();
855 $task->title = get_string('undersetup', 'workshop');
856 $task->completed = 'info';
857 $phase->tasks['setupinfo'] = $task;
858 }
b761e6d9
DM
859 $phases[self::PHASE_SETUP] = $phase;
860
861 // Prepare tasks for the submission phase
862 $phase = new stdClass();
863 $phase->title = get_string('phasesubmission', 'workshop');
864 $phase->tasks = array();
81eccf0a
DM
865 if (($this->usepeerassessment or $this->useselfassessment)
866 and has_capability('moodle/course:manageactivities', $this->context, $userid)) {
b761e6d9 867 $task = new stdClass();
00aca3c1
DM
868 $task->title = get_string('taskinstructreviewers', 'workshop');
869 $task->link = $this->updatemod_url();
870 if (trim(strip_tags($this->instructreviewers))) {
da0b1f70
DM
871 $task->completed = true;
872 } elseif ($this->phase >= self::PHASE_ASSESSMENT) {
873 $task->completed = false;
da0b1f70 874 }
00aca3c1 875 $phase->tasks['instructreviewers'] = $task;
b761e6d9 876 }
d895c6aa 877 if (has_capability('mod/workshop:submit', $this->context, $userid, false)) {
da0b1f70 878 $task = new stdClass();
00aca3c1
DM
879 $task->title = get_string('tasksubmit', 'workshop');
880 $task->link = $this->submission_url();
881 if ($DB->record_exists('workshop_submissions', array('workshopid'=>$this->id, 'example'=>0, 'authorid'=>$userid))) {
da0b1f70
DM
882 $task->completed = true;
883 } elseif ($this->phase >= self::PHASE_ASSESSMENT) {
884 $task->completed = false;
00aca3c1
DM
885 } else {
886 $task->completed = null; // still has a chance to submit
da0b1f70 887 }
00aca3c1 888 $phase->tasks['submit'] = $task;
da0b1f70 889 }
d895c6aa 890 if (has_capability('mod/workshop:allocate', $this->context, $userid)) {
da0b1f70
DM
891 $task = new stdClass();
892 $task->title = get_string('allocate', 'workshop');
893 $task->link = $this->allocation_url();
d895c6aa 894 $numofauthors = count(get_users_by_capability($this->context, 'mod/workshop:submit', 'u.id', '', '', '',
a3610b08
DM
895 '', '', false, true));
896 $numofsubmissions = $DB->count_records('workshop_submissions', array('workshopid'=>$this->id, 'example'=>0));
897 $sql = 'SELECT COUNT(s.id) AS nonallocated
898 FROM {workshop_submissions} s
899 LEFT JOIN {workshop_assessments} a ON (a.submissionid=s.id)
900 WHERE s.workshopid = :workshopid AND s.example=0 AND a.submissionid IS NULL';
901 $params['workshopid'] = $this->id;
902 $numnonallocated = $DB->count_records_sql($sql, $params);
da0b1f70
DM
903 if ($numofsubmissions == 0) {
904 $task->completed = null;
a3610b08 905 } elseif ($numnonallocated == 0) {
da0b1f70
DM
906 $task->completed = true;
907 } elseif ($this->phase > self::PHASE_SUBMISSION) {
908 $task->completed = false;
909 } else {
910 $task->completed = null; // still has a chance to allocate
911 }
912 $a = new stdClass();
3189fb2d
DM
913 $a->expected = $numofauthors;
914 $a->submitted = $numofsubmissions;
a3610b08 915 $a->allocate = $numnonallocated;
3189fb2d 916 $task->details = get_string('allocatedetails', 'workshop', $a);
da0b1f70 917 unset($a);
3189fb2d 918 $phase->tasks['allocate'] = $task;
3dc78e5b
DM
919
920 if ($numofsubmissions < $numofauthors and $this->phase >= self::PHASE_SUBMISSION) {
921 $task = new stdClass();
922 $task->title = get_string('someuserswosubmission', 'workshop');
923 $task->completed = 'info';
924 $phase->tasks['allocateinfo'] = $task;
925 }
da0b1f70 926 }
81eccf0a 927 $phases[self::PHASE_SUBMISSION] = $phase;
b761e6d9
DM
928
929 // Prepare tasks for the peer-assessment phase (includes eventual self-assessments)
930 $phase = new stdClass();
931 $phase->title = get_string('phaseassessment', 'workshop');
932 $phase->tasks = array();
d895c6aa 933 $phase->isreviewer = has_capability('mod/workshop:peerassess', $this->context, $userid);
3dc78e5b 934 $phase->assessments = $this->get_assessments_by_reviewer($userid);
b761e6d9
DM
935 $numofpeers = 0; // number of allocated peer-assessments
936 $numofpeerstodo = 0; // number of peer-assessments to do
937 $numofself = 0; // number of allocated self-assessments - should be 0 or 1
938 $numofselftodo = 0; // number of self-assessments to do - should be 0 or 1
939 foreach ($phase->assessments as $a) {
940 if ($a->authorid == $userid) {
941 $numofself++;
942 if (is_null($a->grade)) {
943 $numofselftodo++;
944 }
945 } else {
946 $numofpeers++;
947 if (is_null($a->grade)) {
948 $numofpeerstodo++;
949 }
950 }
951 }
952 unset($a);
81eccf0a 953 if ($this->usepeerassessment and $numofpeers) {
b761e6d9 954 $task = new stdClass();
3dc78e5b
DM
955 if ($numofpeerstodo == 0) {
956 $task->completed = true;
957 } elseif ($this->phase > self::PHASE_ASSESSMENT) {
958 $task->completed = false;
959 }
b761e6d9
DM
960 $a = new stdClass();
961 $a->total = $numofpeers;
962 $a->todo = $numofpeerstodo;
963 $task->title = get_string('taskassesspeers', 'workshop');
da0b1f70 964 $task->details = get_string('taskassesspeersdetails', 'workshop', $a);
b761e6d9
DM
965 unset($a);
966 $phase->tasks['assesspeers'] = $task;
967 }
81eccf0a 968 if ($this->useselfassessment and $numofself) {
b761e6d9 969 $task = new stdClass();
3dc78e5b
DM
970 if ($numofselftodo == 0) {
971 $task->completed = true;
972 } elseif ($this->phase > self::PHASE_ASSESSMENT) {
973 $task->completed = false;
974 }
b761e6d9
DM
975 $task->title = get_string('taskassessself', 'workshop');
976 $phase->tasks['assessself'] = $task;
977 }
978 $phases[self::PHASE_ASSESSMENT] = $phase;
979
1fed6ce3 980 // Prepare tasks for the grading evaluation phase
b761e6d9
DM
981 $phase = new stdClass();
982 $phase->title = get_string('phaseevaluation', 'workshop');
983 $phase->tasks = array();
1fed6ce3 984 if (has_capability('mod/workshop:overridegrades', $this->context)) {
f27b70fb
DM
985 $expected = count($this->get_potential_authors(false));
986 $calculated = $DB->count_records_select('workshop_submissions',
987 'workshopid = ? AND (grade IS NOT NULL OR gradeover IS NOT NULL)', array($this->id));
1fed6ce3 988 $task = new stdClass();
10bc4bce 989 $task->title = get_string('calculatesubmissiongrades', 'workshop');
1fed6ce3
DM
990 $a = new stdClass();
991 $a->expected = $expected;
f27b70fb 992 $a->calculated = $calculated;
10bc4bce 993 $task->details = get_string('calculatesubmissiongradesdetails', 'workshop', $a);
f27b70fb 994 if ($calculated >= $expected) {
1fed6ce3
DM
995 $task->completed = true;
996 } elseif ($this->phase > self::PHASE_EVALUATION) {
997 $task->completed = false;
998 }
10bc4bce 999 $phase->tasks['calculatesubmissiongrade'] = $task;
f27b70fb
DM
1000
1001 $expected = count($this->get_potential_reviewers(false));
1002 $calculated = $DB->count_records_select('workshop_aggregations',
1003 'workshopid = ? AND gradinggrade IS NOT NULL', array($this->id));
1004 $task = new stdClass();
1005 $task->title = get_string('calculategradinggrades', 'workshop');
1006 $a = new stdClass();
1007 $a->expected = $expected;
1008 $a->calculated = $calculated;
1009 $task->details = get_string('calculategradinggradesdetails', 'workshop', $a);
1010 if ($calculated >= $expected) {
1011 $task->completed = true;
1012 } elseif ($this->phase > self::PHASE_EVALUATION) {
1013 $task->completed = false;
f55650e6 1014 }
f27b70fb
DM
1015 $phase->tasks['calculategradinggrade'] = $task;
1016
d183140d 1017 } elseif ($this->phase == self::PHASE_EVALUATION) {
1fed6ce3
DM
1018 $task = new stdClass();
1019 $task->title = get_string('evaluategradeswait', 'workshop');
1020 $task->completed = 'info';
1021 $phase->tasks['evaluateinfo'] = $task;
1022 }
b761e6d9
DM
1023 $phases[self::PHASE_EVALUATION] = $phase;
1024
1025 // Prepare tasks for the "workshop closed" phase - todo
1026 $phase = new stdClass();
1027 $phase->title = get_string('phaseclosed', 'workshop');
1028 $phase->tasks = array();
1029 $phases[self::PHASE_CLOSED] = $phase;
1030
1031 // Polish data, set default values if not done explicitly
1032 foreach ($phases as $phasecode => $phase) {
1033 $phase->title = isset($phase->title) ? $phase->title : '';
1034 $phase->tasks = isset($phase->tasks) ? $phase->tasks : array();
1035 if ($phasecode == $this->phase) {
1036 $phase->active = true;
1037 } else {
1038 $phase->active = false;
1039 }
454e8dd9
DM
1040 if (!isset($phase->actions)) {
1041 $phase->actions = array();
1042 }
b761e6d9
DM
1043
1044 foreach ($phase->tasks as $taskcode => $task) {
1045 $task->title = isset($task->title) ? $task->title : '';
da0b1f70
DM
1046 $task->link = isset($task->link) ? $task->link : null;
1047 $task->details = isset($task->details) ? $task->details : '';
b761e6d9
DM
1048 $task->completed = isset($task->completed) ? $task->completed : null;
1049 }
1050 }
454e8dd9
DM
1051
1052 // Add phase swithing actions
d895c6aa 1053 if (has_capability('mod/workshop:switchphase', $this->context, $userid)) {
454e8dd9
DM
1054 foreach ($phases as $phasecode => $phase) {
1055 if (! $phase->active) {
1056 $action = new stdClass();
1057 $action->type = 'switchphase';
1058 $action->url = $this->switchphase_url($phasecode);
1059 $phase->actions[] = $action;
1060 }
1061 }
1062 }
1063
b761e6d9
DM
1064 return $phases;
1065 }
1066
454e8dd9
DM
1067 /**
1068 * Switch to a new workshop phase
1069 *
1070 * Modifies the underlying database record. You should terminate the script shortly after calling this.
1071 *
1072 * @param int $newphase new phase code
1073 * @return bool true if success, false otherwise
1074 */
1075 public function switch_phase($newphase) {
1076 global $DB;
1077
365c2cc2 1078 $known = $this->available_phases_list();
454e8dd9
DM
1079 if (!isset($known[$newphase])) {
1080 return false;
1081 }
f6e8b318
DM
1082
1083 if (self::PHASE_CLOSED == $newphase) {
f27b70fb 1084 // push the grades into the gradebook
10bc4bce
DM
1085 $workshop = new stdClass();
1086 foreach ($this as $property => $value) {
1087 $workshop->{$property} = $value;
1088 }
1089 $workshop->course = $this->course->id;
1090 $workshop->cmidnumber = $this->cm->id;
1091 $workshop->modname = 'workshop';
1092 workshop_update_grades($workshop);
f6e8b318
DM
1093 }
1094
454e8dd9
DM
1095 $DB->set_field('workshop', 'phase', $newphase, array('id' => $this->id));
1096 return true;
1097 }
ddb59c77
DM
1098
1099 /**
1100 * Saves a raw grade for submission as calculated from the assessment form fields
1101 *
1102 * @param array $assessmentid assessment record id, must exists
00aca3c1 1103 * @param mixed $grade raw percentual grade from 0.00000 to 100.00000
ddb59c77
DM
1104 * @return false|float the saved grade
1105 */
1106 public function set_peer_grade($assessmentid, $grade) {
1107 global $DB;
1108
1109 if (is_null($grade)) {
1110 return false;
1111 }
1112 $data = new stdClass();
1113 $data->id = $assessmentid;
1114 $data->grade = $grade;
1115 $DB->update_record('workshop_assessments', $data);
1116 return $grade;
1117 }
6516b9e9 1118
29dc43e7
DM
1119 /**
1120 * Prepares data object with all workshop grades to be rendered
1121 *
5e71cefb
DM
1122 * @param int $userid the user we are preparing the report for
1123 * @param mixed $groups single group or array of groups - only show users who are in one of these group(s). Defaults to all
29dc43e7 1124 * @param int $page the current page (for the pagination)
5e71cefb 1125 * @param int $perpage participants per page (for the pagination)
f27b70fb 1126 * @param string $sortby lastname|firstname|submissiontitle|submissiongrade|gradinggrade
5e71cefb 1127 * @param string $sorthow ASC|DESC
29dc43e7
DM
1128 * @return stdClass data for the renderer
1129 */
d895c6aa 1130 public function prepare_grading_report($userid, $groups, $page, $perpage, $sortby, $sorthow) {
29dc43e7
DM
1131 global $DB;
1132
d895c6aa
DM
1133 $canviewall = has_capability('mod/workshop:viewallassessments', $this->context, $userid);
1134 $isparticipant = has_any_capability(array('mod/workshop:submit', 'mod/workshop:peerassess'), $this->context, $userid);
29dc43e7
DM
1135
1136 if (!$canviewall and !$isparticipant) {
1137 // who the hell is this?
1138 return array();
1139 }
1140
f27b70fb 1141 if (!in_array($sortby, array('lastname','firstname','submissiontitle','submissiongrade','gradinggrade'))) {
5e71cefb
DM
1142 $sortby = 'lastname';
1143 }
1144
1145 if (!($sorthow === 'ASC' or $sorthow === 'DESC')) {
1146 $sorthow = 'ASC';
1147 }
1148
1149 // get the list of user ids to be displayed
29dc43e7
DM
1150 if ($canviewall) {
1151 // fetch the list of ids of all workshop participants - this may get really long so fetch just id
d895c6aa 1152 $participants = get_users_by_capability($this->context, array('mod/workshop:submit', 'mod/workshop:peerassess'),
5e71cefb 1153 'u.id', '', '', '', $groups, '', false, false, true);
29dc43e7
DM
1154 } else {
1155 // this is an ordinary workshop participant (aka student) - display the report just for him/her
1156 $participants = array($userid => (object)array('id' => $userid));
1157 }
1158
5e71cefb 1159 // we will need to know the number of all records later for the pagination purposes
29dc43e7
DM
1160 $numofparticipants = count($participants);
1161
d183140d 1162 // load all fields which can be used for sorting and paginate the records
5e71cefb 1163 list($participantids, $params) = $DB->get_in_or_equal(array_keys($participants), SQL_PARAMS_NAMED);
d183140d
DM
1164 $params['workshopid1'] = $this->id;
1165 $params['workshopid2'] = $this->id;
5e71cefb
DM
1166 $sqlsort = $sortby . ' ' . $sorthow . ',u.lastname,u.firstname,u.id';
1167 $sql = "SELECT u.id AS userid,u.firstname,u.lastname,u.picture,u.imagealt,
f27b70fb 1168 s.title AS submissiontitle, s.grade AS submissiongrade, ag.gradinggrade
5e71cefb 1169 FROM {user} u
d183140d
DM
1170 LEFT JOIN {workshop_submissions} s ON (s.authorid = u.id AND s.workshopid = :workshopid1 AND s.example = 0)
1171 LEFT JOIN {workshop_aggregations} ag ON (ag.userid = u.id AND ag.workshopid = :workshopid2)
1172 WHERE u.id $participantids
5e71cefb
DM
1173 ORDER BY $sqlsort";
1174 $participants = $DB->get_records_sql($sql, $params, $page * $perpage, $perpage);
29dc43e7
DM
1175
1176 // this will hold the information needed to display user names and pictures
5e71cefb
DM
1177 $userinfo = array();
1178
1179 // get the user details for all participants to display
1180 foreach ($participants as $participant) {
1181 if (!isset($userinfo[$participant->userid])) {
1182 $userinfo[$participant->userid] = new stdClass();
1183 $userinfo[$participant->userid]->id = $participant->userid;
1184 $userinfo[$participant->userid]->firstname = $participant->firstname;
1185 $userinfo[$participant->userid]->lastname = $participant->lastname;
1186 $userinfo[$participant->userid]->picture = $participant->picture;
1187 $userinfo[$participant->userid]->imagealt = $participant->imagealt;
1188 }
1189 }
29dc43e7 1190
5e71cefb 1191 // load the submissions details
29dc43e7 1192 $submissions = $this->get_submissions(array_keys($participants));
5e71cefb
DM
1193
1194 // get the user details for all moderators (teachers) that have overridden a submission grade
29dc43e7 1195 foreach ($submissions as $submission) {
29dc43e7
DM
1196 if (!isset($userinfo[$submission->gradeoverby])) {
1197 $userinfo[$submission->gradeoverby] = new stdClass();
1198 $userinfo[$submission->gradeoverby]->id = $submission->gradeoverby;
1199 $userinfo[$submission->gradeoverby]->firstname = $submission->overfirstname;
1200 $userinfo[$submission->gradeoverby]->lastname = $submission->overlastname;
1201 $userinfo[$submission->gradeoverby]->picture = $submission->overpicture;
1202 $userinfo[$submission->gradeoverby]->imagealt = $submission->overimagealt;
1203 }
1204 }
1205
5e71cefb 1206 // get the user details for all reviewers of the displayed participants
29dc43e7
DM
1207 $reviewers = array();
1208 if ($submissions) {
1209 list($submissionids, $params) = $DB->get_in_or_equal(array_keys($submissions), SQL_PARAMS_NAMED);
1210 $sql = "SELECT a.id AS assessmentid, a.submissionid, a.grade, a.gradinggrade, a.gradinggradeover,
1211 r.id AS reviewerid, r.lastname, r.firstname, r.picture, r.imagealt,
1212 s.id AS submissionid, s.authorid
1213 FROM {workshop_assessments} a
1214 JOIN {user} r ON (a.reviewerid = r.id)
1215 JOIN {workshop_submissions} s ON (a.submissionid = s.id)
1216 WHERE a.submissionid $submissionids";
1217 $reviewers = $DB->get_records_sql($sql, $params);
1218 foreach ($reviewers as $reviewer) {
1219 if (!isset($userinfo[$reviewer->reviewerid])) {
1220 $userinfo[$reviewer->reviewerid] = new stdClass();
1221 $userinfo[$reviewer->reviewerid]->id = $reviewer->reviewerid;
1222 $userinfo[$reviewer->reviewerid]->firstname = $reviewer->firstname;
1223 $userinfo[$reviewer->reviewerid]->lastname = $reviewer->lastname;
1224 $userinfo[$reviewer->reviewerid]->picture = $reviewer->picture;
1225 $userinfo[$reviewer->reviewerid]->imagealt = $reviewer->imagealt;
1226 }
1227 }
1228 }
1229
5e71cefb 1230 // get the user details for all reviewees of the displayed participants
934329e5
DM
1231 $reviewees = array();
1232 if ($participants) {
1233 list($participantids, $params) = $DB->get_in_or_equal(array_keys($participants), SQL_PARAMS_NAMED);
1234 $params['workshopid'] = $this->id;
d183140d 1235 $sql = "SELECT a.id AS assessmentid, a.submissionid, a.grade, a.gradinggrade, a.gradinggradeover, a.reviewerid,
934329e5
DM
1236 s.id AS submissionid,
1237 e.id AS authorid, e.lastname, e.firstname, e.picture, e.imagealt
1238 FROM {user} u
1239 JOIN {workshop_assessments} a ON (a.reviewerid = u.id)
1240 JOIN {workshop_submissions} s ON (a.submissionid = s.id)
1241 JOIN {user} e ON (s.authorid = e.id)
1242 WHERE u.id $participantids AND s.workshopid = :workshopid";
1243 $reviewees = $DB->get_records_sql($sql, $params);
1244 foreach ($reviewees as $reviewee) {
1245 if (!isset($userinfo[$reviewee->authorid])) {
1246 $userinfo[$reviewee->authorid] = new stdClass();
1247 $userinfo[$reviewee->authorid]->id = $reviewee->authorid;
1248 $userinfo[$reviewee->authorid]->firstname = $reviewee->firstname;
1249 $userinfo[$reviewee->authorid]->lastname = $reviewee->lastname;
1250 $userinfo[$reviewee->authorid]->picture = $reviewee->picture;
1251 $userinfo[$reviewee->authorid]->imagealt = $reviewee->imagealt;
1252 }
29dc43e7
DM
1253 }
1254 }
1255
5e71cefb
DM
1256 // finally populate the object to be rendered
1257 $grades = $participants;
29dc43e7
DM
1258
1259 foreach ($participants as $participant) {
1260 // set up default (null) values
d183140d
DM
1261 $grades[$participant->userid]->submissionid = null;
1262 $grades[$participant->userid]->submissiontitle = null;
1263 $grades[$participant->userid]->submissiongrade = null;
1264 $grades[$participant->userid]->submissiongradeover = null;
1265 $grades[$participant->userid]->submissiongradeoverby = null;
5e71cefb
DM
1266 $grades[$participant->userid]->reviewedby = array();
1267 $grades[$participant->userid]->reviewerof = array();
29dc43e7
DM
1268 }
1269 unset($participants);
1270 unset($participant);
1271
1272 foreach ($submissions as $submission) {
1273 $grades[$submission->authorid]->submissionid = $submission->id;
1274 $grades[$submission->authorid]->submissiontitle = $submission->title;
b4857acb
DM
1275 $grades[$submission->authorid]->submissiongrade = $this->real_grade($submission->grade);
1276 $grades[$submission->authorid]->submissiongradeover = $this->real_grade($submission->gradeover);
29dc43e7
DM
1277 $grades[$submission->authorid]->submissiongradeoverby = $submission->gradeoverby;
1278 }
1279 unset($submissions);
1280 unset($submission);
1281
1282 foreach($reviewers as $reviewer) {
1283 $info = new stdClass();
1284 $info->userid = $reviewer->reviewerid;
1285 $info->assessmentid = $reviewer->assessmentid;
1286 $info->submissionid = $reviewer->submissionid;
b4857acb
DM
1287 $info->grade = $this->real_grade($reviewer->grade);
1288 $info->gradinggrade = $this->real_grading_grade($reviewer->gradinggrade);
1289 $info->gradinggradeover = $this->real_grading_grade($reviewer->gradinggradeover);
29dc43e7
DM
1290 $grades[$reviewer->authorid]->reviewedby[$reviewer->reviewerid] = $info;
1291 }
1292 unset($reviewers);
1293 unset($reviewer);
1294
1295 foreach($reviewees as $reviewee) {
1296 $info = new stdClass();
1297 $info->userid = $reviewee->authorid;
1298 $info->assessmentid = $reviewee->assessmentid;
1299 $info->submissionid = $reviewee->submissionid;
b4857acb
DM
1300 $info->grade = $this->real_grade($reviewee->grade);
1301 $info->gradinggrade = $this->real_grading_grade($reviewee->gradinggrade);
1302 $info->gradinggradeover = $this->real_grading_grade($reviewee->gradinggradeover);
29dc43e7
DM
1303 $grades[$reviewee->reviewerid]->reviewerof[$reviewee->authorid] = $info;
1304 }
1305 unset($reviewees);
1306 unset($reviewee);
1307
b4857acb
DM
1308 foreach ($grades as $grade) {
1309 $grade->gradinggrade = $this->real_grading_grade($grade->gradinggrade);
b4857acb
DM
1310 }
1311
29dc43e7
DM
1312 $data = new stdClass();
1313 $data->grades = $grades;
1314 $data->userinfo = $userinfo;
1315 $data->totalcount = $numofparticipants;
b4857acb
DM
1316 $data->maxgrade = $this->real_grade(100);
1317 $data->maxgradinggrade = $this->real_grading_grade(100);
29dc43e7
DM
1318 return $data;
1319 }
1320
29dc43e7 1321 /**
b4857acb 1322 * Calculates the real value of a grade
29dc43e7 1323 *
b4857acb
DM
1324 * @param float $value percentual value from 0 to 100
1325 * @param float $max the maximal grade
1326 * @return string
1327 */
1328 public function real_grade_value($value, $max) {
1329 $localized = true;
557a1100 1330 if (is_null($value) or $value === '') {
b4857acb
DM
1331 return null;
1332 } elseif ($max == 0) {
1333 return 0;
1334 } else {
1335 return format_float($max * $value / 100, $this->gradedecimals, $localized);
1336 }
1337 }
1338
e554671d
DM
1339 /**
1340 * Calculates the raw (percentual) value from a real grade
1341 *
1342 * This is used in cases when a user wants to give a grade such as 12 of 20 and we need to save
1343 * this value in a raw percentual form into DB
1344 * @param float $value given grade
1345 * @param float $max the maximal grade
1346 * @return float suitable to be stored as numeric(10,5)
1347 */
1348 public function raw_grade_value($value, $max) {
557a1100 1349 if (is_null($value) or $value === '') {
e554671d
DM
1350 return null;
1351 }
1352 if ($max == 0 or $value < 0) {
1353 return 0;
1354 }
1355 $p = $value / $max * 100;
1356 if ($p > 100) {
1357 return $max;
1358 }
1359 return grade_floatval($p);
1360 }
1361
b4857acb
DM
1362 /**
1363 * Calculates the real value of grade for submission
1364 *
1365 * @param float $value percentual value from 0 to 100
1366 * @return string
1367 */
1368 public function real_grade($value) {
1369 return $this->real_grade_value($value, $this->grade);
1370 }
1371
1372 /**
1373 * Calculates the real value of grade for assessment
1374 *
1375 * @param float $value percentual value from 0 to 100
1376 * @return string
1377 */
1378 public function real_grading_grade($value) {
1379 return $this->real_grade_value($value, $this->gradinggrade);
29dc43e7
DM
1380 }
1381
89c1aa97 1382 /**
e9a90e69 1383 * Calculates grades for submission for the given participant(s) and updates it in the database
89c1aa97
DM
1384 *
1385 * @param null|int|array $restrict If null, update all authors, otherwise update just grades for the given author(s)
1386 * @return void
1387 */
8a1ba8ac 1388 public function aggregate_submission_grades($restrict=null) {
89c1aa97
DM
1389 global $DB;
1390
1391 // fetch a recordset with all assessments to process
1696f36c 1392 $sql = 'SELECT s.id AS submissionid, s.grade AS submissiongrade,
89c1aa97
DM
1393 a.weight, a.grade
1394 FROM {workshop_submissions} s
1395 LEFT JOIN {workshop_assessments} a ON (a.submissionid = s.id)
1396 WHERE s.example=0 AND s.workshopid=:workshopid'; // to be cont.
1397 $params = array('workshopid' => $this->id);
1398
1399 if (is_null($restrict)) {
1400 // update all users - no more conditions
1401 } elseif (!empty($restrict)) {
1402 list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED);
1403 $sql .= " AND s.authorid $usql";
1404 $params = array_merge($params, $uparams);
1405 } else {
1406 throw new coding_exception('Empty value is not a valid parameter here');
1407 }
1408
1409 $sql .= ' ORDER BY s.id'; // this is important for bulk processing
89c1aa97 1410
e9a90e69
DM
1411 $rs = $DB->get_recordset_sql($sql, $params);
1412 $batch = array(); // will contain a set of all assessments of a single submission
1413 $previous = null; // a previous record in the recordset
1414
89c1aa97
DM
1415 foreach ($rs as $current) {
1416 if (is_null($previous)) {
1417 // we are processing the very first record in the recordset
1418 $previous = $current;
89c1aa97 1419 }
e9a90e69 1420 if ($current->submissionid == $previous->submissionid) {
89c1aa97 1421 // we are still processing the current submission
e9a90e69
DM
1422 $batch[] = $current;
1423 } else {
1424 // process all the assessments of a sigle submission
1425 $this->aggregate_submission_grades_process($batch);
1426 // and then start to process another submission
1427 $batch = array($current);
1428 $previous = $current;
89c1aa97
DM
1429 }
1430 }
e9a90e69
DM
1431 // do not forget to process the last batch!
1432 $this->aggregate_submission_grades_process($batch);
89c1aa97
DM
1433 $rs->close();
1434 }
1435
1436 /**
1437 * Calculates grades for assessment for the given participant(s)
1438 *
39411930
DM
1439 * Grade for assessment is calculated as a simple mean of all grading grades calculated by the grading evaluator.
1440 * The assessment weight is not taken into account here.
89c1aa97
DM
1441 *
1442 * @param null|int|array $restrict If null, update all reviewers, otherwise update just grades for the given reviewer(s)
1443 * @return void
1444 */
8a1ba8ac 1445 public function aggregate_grading_grades($restrict=null) {
89c1aa97
DM
1446 global $DB;
1447
39411930
DM
1448 // fetch a recordset with all assessments to process
1449 $sql = 'SELECT a.reviewerid, a.gradinggrade, a.gradinggradeover,
1450 ag.id AS aggregationid, ag.gradinggrade AS aggregatedgrade
1451 FROM {workshop_assessments} a
1452 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
1453 LEFT JOIN {workshop_aggregations} ag ON (ag.userid = a.reviewerid AND ag.workshopid = s.workshopid)
1454 WHERE s.example=0 AND s.workshopid=:workshopid'; // to be cont.
1455 $params = array('workshopid' => $this->id);
1456
1457 if (is_null($restrict)) {
1458 // update all users - no more conditions
1459 } elseif (!empty($restrict)) {
1460 list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED);
1461 $sql .= " AND a.reviewerid $usql";
1462 $params = array_merge($params, $uparams);
1463 } else {
1464 throw new coding_exception('Empty value is not a valid parameter here');
1465 }
1466
1467 $sql .= ' ORDER BY a.reviewerid'; // this is important for bulk processing
1468
1469 $rs = $DB->get_recordset_sql($sql, $params);
1470 $batch = array(); // will contain a set of all assessments of a single submission
1471 $previous = null; // a previous record in the recordset
1472
1473 foreach ($rs as $current) {
1474 if (is_null($previous)) {
1475 // we are processing the very first record in the recordset
1476 $previous = $current;
1477 }
1478 if ($current->reviewerid == $previous->reviewerid) {
1479 // we are still processing the current reviewer
1480 $batch[] = $current;
1481 } else {
1482 // process all the assessments of a sigle submission
1483 $this->aggregate_grading_grades_process($batch);
1484 // and then start to process another reviewer
1485 $batch = array($current);
1486 $previous = $current;
1487 }
1488 }
1489 // do not forget to process the last batch!
1490 $this->aggregate_grading_grades_process($batch);
1491 $rs->close();
89c1aa97
DM
1492 }
1493
77f43e7d 1494 /**
f6e8b318 1495 * Returns the mform the teachers use to put a feedback for the reviewer
77f43e7d 1496 *
f6e8b318 1497 * @return workshop_feedbackreviewer_form
77f43e7d 1498 */
e554671d 1499 public function get_feedbackreviewer_form(moodle_url $actionurl, stdClass $assessment, $editable=true) {
77f43e7d
DM
1500 global $CFG;
1501 require_once(dirname(__FILE__) . '/feedbackreviewer_form.php');
1502
e554671d
DM
1503 $current = new stdClass();
1504 $current->asid = $assessment->id;
1505 $current->gradinggrade = $this->real_grading_grade($assessment->gradinggrade);
1506 $current->gradinggradeover = $this->real_grading_grade($assessment->gradinggradeover);
1507 $current->feedbackreviewer = $assessment->feedbackreviewer;
1508 $current->feedbackreviewerformat = $assessment->feedbackreviewerformat;
1509 if (is_null($current->gradinggrade)) {
1510 $current->gradinggrade = get_string('nullgrade', 'workshop');
1511 }
1512
1513 // prepare wysiwyg editor
1514 $current = file_prepare_standard_editor($current, 'feedbackreviewer', array());
1515
77f43e7d 1516 return new workshop_feedbackreviewer_form($actionurl,
e554671d 1517 array('workshop' => $this, 'current' => $current, 'feedbackopts' => array()),
77f43e7d
DM
1518 'post', '', null, $editable);
1519 }
1520
67cd00ba
DM
1521 /**
1522 * Returns the mform the teachers use to put a feedback for the author on their submission
1523 *
1524 * @return workshop_feedbackauthor_form
1525 */
1526 public function get_feedbackauthor_form(moodle_url $actionurl, stdClass $submission, $editable=true) {
1527 global $CFG;
1528 require_once(dirname(__FILE__) . '/feedbackauthor_form.php');
1529
1530 $current = new stdClass();
1531 $current->submissionid = $submission->id;
557a1100
DM
1532 $current->grade = $this->real_grade($submission->grade);
1533 $current->gradeover = $this->real_grade($submission->gradeover);
1534 $current->feedbackauthor = $submission->feedbackauthor;
1535 $current->feedbackauthorformat = $submission->feedbackauthorformat;
67cd00ba
DM
1536 if (is_null($current->grade)) {
1537 $current->grade = get_string('nullgrade', 'workshop');
1538 }
1539
1540 // prepare wysiwyg editor
1541 $current = file_prepare_standard_editor($current, 'feedbackauthor', array());
1542
1543 return new workshop_feedbackauthor_form($actionurl,
1544 array('workshop' => $this, 'current' => $current, 'feedbackopts' => array()),
1545 'post', '', null, $editable);
1546 }
1547
aa40adbf
DM
1548 ////////////////////////////////////////////////////////////////////////////////
1549 // Internal methods (implementation details) //
1550 ////////////////////////////////////////////////////////////////////////////////
6516b9e9 1551
e9a90e69
DM
1552 /**
1553 * Given an array of all assessments of a single submission, calculates the final grade for this submission
1554 *
1555 * This calculates the weighted mean of the passed assessment grades. If, however, the submission grade
1556 * was overridden by a teacher, the gradeover value is returned and the rest of grades are ignored.
1557 *
1558 * @param array $assessments of stdClass(->submissionid ->submissiongrade ->gradeover ->weight ->grade)
1fed6ce3 1559 * @return void
e9a90e69
DM
1560 */
1561 protected function aggregate_submission_grades_process(array $assessments) {
1562 global $DB;
1563
1564 $submissionid = null; // the id of the submission being processed
1565 $current = null; // the grade currently saved in database
1566 $finalgrade = null; // the new grade to be calculated
1567 $sumgrades = 0;
1568 $sumweights = 0;
1569
1570 foreach ($assessments as $assessment) {
1571 if (is_null($submissionid)) {
1572 // the id is the same in all records, fetch it during the first loop cycle
1573 $submissionid = $assessment->submissionid;
1574 }
1575 if (is_null($current)) {
1576 // the currently saved grade is the same in all records, fetch it during the first loop cycle
1577 $current = $assessment->submissiongrade;
1578 }
e9a90e69
DM
1579 if (is_null($assessment->grade)) {
1580 // this was not assessed yet
1581 continue;
1582 }
1583 if ($assessment->weight == 0) {
1584 // this does not influence the calculation
1585 continue;
1586 }
1587 $sumgrades += $assessment->grade * $assessment->weight;
1588 $sumweights += $assessment->weight;
1589 }
1590 if ($sumweights > 0 and is_null($finalgrade)) {
1591 $finalgrade = grade_floatval($sumgrades / $sumweights);
1592 }
1593 // check if the new final grade differs from the one stored in the database
1594 if (grade_floats_different($finalgrade, $current)) {
1595 // we need to save new calculation into the database
10bc4bce
DM
1596 $record = new stdClass();
1597 $record->id = $submissionid;
1598 $record->grade = $finalgrade;
1599 $record->timegraded = time();
1600 $DB->update_record('workshop_submissions', $record);
e9a90e69
DM
1601 }
1602 }
1603
39411930
DM
1604 /**
1605 * Given an array of all assessments done by a single reviewer, calculates the final grading grade
1606 *
1607 * This calculates the simple mean of the passed grading grades. If, however, the grading grade
1608 * was overridden by a teacher, the gradinggradeover value is returned and the rest of grades are ignored.
1609 *
1610 * @param array $assessments of stdClass(->reviewerid ->gradinggrade ->gradinggradeover ->aggregationid ->aggregatedgrade)
1fed6ce3 1611 * @return void
39411930
DM
1612 */
1613 protected function aggregate_grading_grades_process(array $assessments) {
1614 global $DB;
1615
1616 $reviewerid = null; // the id of the reviewer being processed
1617 $current = null; // the gradinggrade currently saved in database
1618 $finalgrade = null; // the new grade to be calculated
1619 $agid = null; // aggregation id
1620 $sumgrades = 0;
1621 $count = 0;
1622
1623 foreach ($assessments as $assessment) {
1624 if (is_null($reviewerid)) {
1625 // the id is the same in all records, fetch it during the first loop cycle
1626 $reviewerid = $assessment->reviewerid;
1627 }
1628 if (is_null($agid)) {
1629 // the id is the same in all records, fetch it during the first loop cycle
1630 $agid = $assessment->aggregationid;
1631 }
1632 if (is_null($current)) {
1633 // the currently saved grade is the same in all records, fetch it during the first loop cycle
1634 $current = $assessment->aggregatedgrade;
1635 }
1636 if (!is_null($assessment->gradinggradeover)) {
1637 // the grading grade for this assessment is overriden by a teacher
1638 $sumgrades += $assessment->gradinggradeover;
1639 $count++;
1640 } else {
1641 if (!is_null($assessment->gradinggrade)) {
1642 $sumgrades += $assessment->gradinggrade;
1643 $count++;
1644 }
1645 }
1646 }
1647 if ($count > 0) {
1648 $finalgrade = grade_floatval($sumgrades / $count);
1649 }
1650 // check if the new final grade differs from the one stored in the database
1651 if (grade_floats_different($finalgrade, $current)) {
1652 // we need to save new calculation into the database
1653 if (is_null($agid)) {
1654 // no aggregation record yet
1655 $record = new stdClass();
1656 $record->workshopid = $this->id;
1657 $record->userid = $reviewerid;
1658 $record->gradinggrade = $finalgrade;
10bc4bce 1659 $record->timegraded = time();
39411930
DM
1660 $DB->insert_record('workshop_aggregations', $record);
1661 } else {
10bc4bce
DM
1662 $record = new stdClass();
1663 $record->id = $agid;
1664 $record->gradinggrade = $finalgrade;
1665 $record->timegraded = time();
1666 $DB->update_record('workshop_aggregations', $record);
39411930
DM
1667 }
1668 }
1669 }
1670
6516b9e9 1671 /**
aa40adbf 1672 * Given a list of user ids, returns the filtered one containing just ids of users with own submission
6516b9e9 1673 *
aa40adbf
DM
1674 * Example submissions are ignored.
1675 *
1676 * @param array $userids
6516b9e9
DM
1677 * @return array
1678 */
aa40adbf
DM
1679 protected function users_with_submission(array $userids) {
1680 global $DB;
1681
1682 if (empty($userids)) {
1683 return array();
1684 }
1685 $userswithsubmission = array();
1686 list($usql, $uparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
00aca3c1 1687 $sql = "SELECT id,authorid
aa40adbf 1688 FROM {workshop_submissions}
00aca3c1 1689 WHERE example = 0 AND workshopid = :workshopid AND authorid $usql";
aa40adbf
DM
1690 $params = array('workshopid' => $this->id);
1691 $params = array_merge($params, $uparams);
1692 $submissions = $DB->get_records_sql($sql, $params);
1693 foreach ($submissions as $submission) {
00aca3c1 1694 $userswithsubmission[$submission->authorid] = true;
aa40adbf
DM
1695 }
1696
1697 return $userswithsubmission;
6516b9e9
DM
1698 }
1699
aa40adbf
DM
1700 /**
1701 * @return array of available workshop phases
1702 */
365c2cc2 1703 protected function available_phases_list() {
aa40adbf
DM
1704 return array(
1705 self::PHASE_SETUP => true,
1706 self::PHASE_SUBMISSION => true,
1707 self::PHASE_ASSESSMENT => true,
1708 self::PHASE_EVALUATION => true,
1709 self::PHASE_CLOSED => true,
1710 );
1711 }
1712
66c9894d 1713}