workshop 2.0: do not register cron job before it is really implemented
[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 *
407b1e91 747 * @return bool
b8ead2e6 748 */
407b1e91 749 public function submitting_allowed() {
74bf8a94
DM
750 if ($this->phase != self::PHASE_SUBMISSION) {
751 // submitting is not allowed but in the submission phase
752 return false;
753 }
754 $now = time();
755 if (!empty($this->submissionstart) and $this->submissionstart > $now) {
756 // if enabled, submitting is not allowed before the date/time defined in the mod_form
757 return false;
758 }
759 if (!empty($this->submissionend) and empty($this->latesubmissions) and $now > $this->submissionend ) {
760 // if enabled, submitting is not allowed after the date/time defined in the mod_form unless late submission is allowed
761 return false;
762 }
763 // here we go, submission is allowed
407b1e91 764 return true;
b8ead2e6
DM
765 }
766
c1e883bb 767 /**
407b1e91 768 * Are reviewers allowed to create/edit their assessments?
c1e883bb 769 *
c1e883bb
DM
770 * @return bool
771 */
407b1e91 772 public function assessing_allowed() {
74bf8a94
DM
773 if ($this->phase != self::PHASE_ASSESSMENT) {
774 // assessing is not allowed but in the assessment phase
775 return false;
776 }
777 $now = time();
778 if (!empty($this->assessmentstart) and $this->assessmentstart > $now) {
779 // if enabled, assessing is not allowed before the date/time defined in the mod_form
780 return false;
781 }
782 if (!empty($this->assessmentend) and $now > $this->assessmentend ) {
783 // if enabled, assessing is not allowed after the date/time defined in the mod_form
784 return false;
785 }
786 // here we go, assessing is allowed
c1e883bb
DM
787 return true;
788 }
789
becec954
DM
790 /**
791 * Are reviewers allowed to create/edit their assessments of the example submissions?
792 *
74bf8a94 793 * Note this does not check other conditions like the number of already submitted examples etc.
becec954 794 *
74bf8a94 795 * @return null|bool
becec954
DM
796 */
797 public function assessing_examples_allowed() {
74bf8a94
DM
798 if (empty($this->useexamples)) {
799 return null;
800 }
801 if (self::EXAMPLES_VOLUNTARY == $this->examplesmode) {
802 return true;
803 }
804 if (self::EXAMPLES_BEFORE_SUBMISSION == $this->examplesmode and self::PHASE_SUBMISSION == $this->phase) {
805 return true;
806 }
807 if (self::EXAMPLES_BEFORE_ASSESSMENT == $this->examplesmode and self::PHASE_ASSESSMENT == $this->phase) {
808 return true;
809 }
810 return false;
becec954 811 }
407b1e91 812
3dc78e5b
DM
813 /**
814 * Are the peer-reviews available to the authors?
815 *
816 * TODO: this depends on the workshop phase
817 *
818 * @return bool
819 */
820 public function assessments_available() {
821 return true;
822 }
823
824 /**
825 * Can the given grades be displayed to the authors?
826 *
827 * Grades are not displayed if {@link self::assessments_available()} return false. The returned
f6e8b318 828 * value may be true (if yes, display grades) or false (no, hide grades yet)
3dc78e5b 829 *
f6e8b318 830 * @return bool
3dc78e5b
DM
831 */
832 public function grades_available() {
833 return true;
834 }
835
b761e6d9
DM
836 /**
837 * Prepare an individual workshop plan for the given user.
838 *
f05c168d
DM
839 * @param int $userid whom the plan is prepared for
840 * @param stdClass context of the planned workshop
841 * @return stdClass data object to be passed to the renderer
b761e6d9 842 */
d895c6aa 843 public function prepare_user_plan($userid) {
b761e6d9
DM
844 global $DB;
845
846 $phases = array();
847
848 // Prepare tasks for the setup phase
849 $phase = new stdClass();
850 $phase->title = get_string('phasesetup', 'workshop');
851 $phase->tasks = array();
d895c6aa 852 if (has_capability('moodle/course:manageactivities', $this->context, $userid)) {
da0b1f70
DM
853 $task = new stdClass();
854 $task->title = get_string('taskintro', 'workshop');
855 $task->link = $this->updatemod_url();
856 $task->completed = !(trim(strip_tags($this->intro)) == '');
857 $phase->tasks['intro'] = $task;
858 }
d895c6aa 859 if (has_capability('moodle/course:manageactivities', $this->context, $userid)) {
454e8dd9
DM
860 $task = new stdClass();
861 $task->title = get_string('taskinstructauthors', 'workshop');
862 $task->link = $this->updatemod_url();
863 $task->completed = !(trim(strip_tags($this->instructauthors)) == '');
864 $phase->tasks['instructauthors'] = $task;
865 }
d895c6aa 866 if (has_capability('mod/workshop:editdimensions', $this->context, $userid)) {
b761e6d9 867 $task = new stdClass();
da0b1f70
DM
868 $task->title = get_string('editassessmentform', 'workshop');
869 $task->link = $this->editform_url();
f6e8b318 870 if ($this->grading_strategy_instance()->form_ready()) {
da0b1f70
DM
871 $task->completed = true;
872 } elseif ($this->phase > self::PHASE_SETUP) {
873 $task->completed = false;
874 }
b761e6d9
DM
875 $phase->tasks['editform'] = $task;
876 }
81eccf0a
DM
877 if ($this->useexamples and has_capability('mod/workshop:manageexamples', $this->context, $userid)) {
878 $task = new stdClass();
879 $task->title = get_string('prepareexamples', 'workshop');
880 if ($DB->count_records('workshop_submissions', array('example' => 1, 'workshopid' => $this->id)) > 0) {
881 $task->completed = true;
882 } elseif ($this->phase > self::PHASE_SETUP) {
883 $task->completed = false;
884 }
885 $phase->tasks['prepareexamples'] = $task;
886 }
da0b1f70
DM
887 if (empty($phase->tasks) and $this->phase == self::PHASE_SETUP) {
888 // if we are in the setup phase and there is no task (typical for students), let us
889 // display some explanation what is going on
890 $task = new stdClass();
891 $task->title = get_string('undersetup', 'workshop');
892 $task->completed = 'info';
893 $phase->tasks['setupinfo'] = $task;
894 }
b761e6d9
DM
895 $phases[self::PHASE_SETUP] = $phase;
896
897 // Prepare tasks for the submission phase
898 $phase = new stdClass();
899 $phase->title = get_string('phasesubmission', 'workshop');
900 $phase->tasks = array();
81eccf0a
DM
901 if (($this->usepeerassessment or $this->useselfassessment)
902 and has_capability('moodle/course:manageactivities', $this->context, $userid)) {
b761e6d9 903 $task = new stdClass();
00aca3c1
DM
904 $task->title = get_string('taskinstructreviewers', 'workshop');
905 $task->link = $this->updatemod_url();
906 if (trim(strip_tags($this->instructreviewers))) {
da0b1f70
DM
907 $task->completed = true;
908 } elseif ($this->phase >= self::PHASE_ASSESSMENT) {
909 $task->completed = false;
da0b1f70 910 }
00aca3c1 911 $phase->tasks['instructreviewers'] = $task;
b761e6d9 912 }
d895c6aa 913 if (has_capability('mod/workshop:submit', $this->context, $userid, false)) {
da0b1f70 914 $task = new stdClass();
00aca3c1
DM
915 $task->title = get_string('tasksubmit', 'workshop');
916 $task->link = $this->submission_url();
917 if ($DB->record_exists('workshop_submissions', array('workshopid'=>$this->id, 'example'=>0, 'authorid'=>$userid))) {
da0b1f70
DM
918 $task->completed = true;
919 } elseif ($this->phase >= self::PHASE_ASSESSMENT) {
920 $task->completed = false;
00aca3c1
DM
921 } else {
922 $task->completed = null; // still has a chance to submit
da0b1f70 923 }
00aca3c1 924 $phase->tasks['submit'] = $task;
da0b1f70 925 }
d895c6aa 926 if (has_capability('mod/workshop:allocate', $this->context, $userid)) {
da0b1f70
DM
927 $task = new stdClass();
928 $task->title = get_string('allocate', 'workshop');
929 $task->link = $this->allocation_url();
d895c6aa 930 $numofauthors = count(get_users_by_capability($this->context, 'mod/workshop:submit', 'u.id', '', '', '',
a3610b08
DM
931 '', '', false, true));
932 $numofsubmissions = $DB->count_records('workshop_submissions', array('workshopid'=>$this->id, 'example'=>0));
933 $sql = 'SELECT COUNT(s.id) AS nonallocated
934 FROM {workshop_submissions} s
935 LEFT JOIN {workshop_assessments} a ON (a.submissionid=s.id)
936 WHERE s.workshopid = :workshopid AND s.example=0 AND a.submissionid IS NULL';
937 $params['workshopid'] = $this->id;
938 $numnonallocated = $DB->count_records_sql($sql, $params);
da0b1f70
DM
939 if ($numofsubmissions == 0) {
940 $task->completed = null;
a3610b08 941 } elseif ($numnonallocated == 0) {
da0b1f70
DM
942 $task->completed = true;
943 } elseif ($this->phase > self::PHASE_SUBMISSION) {
944 $task->completed = false;
945 } else {
946 $task->completed = null; // still has a chance to allocate
947 }
948 $a = new stdClass();
3189fb2d
DM
949 $a->expected = $numofauthors;
950 $a->submitted = $numofsubmissions;
a3610b08 951 $a->allocate = $numnonallocated;
3189fb2d 952 $task->details = get_string('allocatedetails', 'workshop', $a);
da0b1f70 953 unset($a);
3189fb2d 954 $phase->tasks['allocate'] = $task;
3dc78e5b
DM
955
956 if ($numofsubmissions < $numofauthors and $this->phase >= self::PHASE_SUBMISSION) {
957 $task = new stdClass();
958 $task->title = get_string('someuserswosubmission', 'workshop');
959 $task->completed = 'info';
960 $phase->tasks['allocateinfo'] = $task;
961 }
da0b1f70 962 }
81eccf0a 963 $phases[self::PHASE_SUBMISSION] = $phase;
b761e6d9
DM
964
965 // Prepare tasks for the peer-assessment phase (includes eventual self-assessments)
966 $phase = new stdClass();
967 $phase->title = get_string('phaseassessment', 'workshop');
968 $phase->tasks = array();
d895c6aa 969 $phase->isreviewer = has_capability('mod/workshop:peerassess', $this->context, $userid);
3dc78e5b 970 $phase->assessments = $this->get_assessments_by_reviewer($userid);
b761e6d9
DM
971 $numofpeers = 0; // number of allocated peer-assessments
972 $numofpeerstodo = 0; // number of peer-assessments to do
973 $numofself = 0; // number of allocated self-assessments - should be 0 or 1
974 $numofselftodo = 0; // number of self-assessments to do - should be 0 or 1
975 foreach ($phase->assessments as $a) {
976 if ($a->authorid == $userid) {
977 $numofself++;
978 if (is_null($a->grade)) {
979 $numofselftodo++;
980 }
981 } else {
982 $numofpeers++;
983 if (is_null($a->grade)) {
984 $numofpeerstodo++;
985 }
986 }
987 }
988 unset($a);
81eccf0a 989 if ($this->usepeerassessment and $numofpeers) {
b761e6d9 990 $task = new stdClass();
3dc78e5b
DM
991 if ($numofpeerstodo == 0) {
992 $task->completed = true;
993 } elseif ($this->phase > self::PHASE_ASSESSMENT) {
994 $task->completed = false;
995 }
b761e6d9
DM
996 $a = new stdClass();
997 $a->total = $numofpeers;
998 $a->todo = $numofpeerstodo;
999 $task->title = get_string('taskassesspeers', 'workshop');
da0b1f70 1000 $task->details = get_string('taskassesspeersdetails', 'workshop', $a);
b761e6d9
DM
1001 unset($a);
1002 $phase->tasks['assesspeers'] = $task;
1003 }
81eccf0a 1004 if ($this->useselfassessment and $numofself) {
b761e6d9 1005 $task = new stdClass();
3dc78e5b
DM
1006 if ($numofselftodo == 0) {
1007 $task->completed = true;
1008 } elseif ($this->phase > self::PHASE_ASSESSMENT) {
1009 $task->completed = false;
1010 }
b761e6d9
DM
1011 $task->title = get_string('taskassessself', 'workshop');
1012 $phase->tasks['assessself'] = $task;
1013 }
1014 $phases[self::PHASE_ASSESSMENT] = $phase;
1015
1fed6ce3 1016 // Prepare tasks for the grading evaluation phase
b761e6d9
DM
1017 $phase = new stdClass();
1018 $phase->title = get_string('phaseevaluation', 'workshop');
1019 $phase->tasks = array();
1fed6ce3 1020 if (has_capability('mod/workshop:overridegrades', $this->context)) {
f27b70fb
DM
1021 $expected = count($this->get_potential_authors(false));
1022 $calculated = $DB->count_records_select('workshop_submissions',
1023 'workshopid = ? AND (grade IS NOT NULL OR gradeover IS NOT NULL)', array($this->id));
1fed6ce3 1024 $task = new stdClass();
10bc4bce 1025 $task->title = get_string('calculatesubmissiongrades', 'workshop');
1fed6ce3
DM
1026 $a = new stdClass();
1027 $a->expected = $expected;
f27b70fb 1028 $a->calculated = $calculated;
10bc4bce 1029 $task->details = get_string('calculatesubmissiongradesdetails', 'workshop', $a);
f27b70fb 1030 if ($calculated >= $expected) {
1fed6ce3
DM
1031 $task->completed = true;
1032 } elseif ($this->phase > self::PHASE_EVALUATION) {
1033 $task->completed = false;
1034 }
10bc4bce 1035 $phase->tasks['calculatesubmissiongrade'] = $task;
f27b70fb
DM
1036
1037 $expected = count($this->get_potential_reviewers(false));
1038 $calculated = $DB->count_records_select('workshop_aggregations',
1039 'workshopid = ? AND gradinggrade IS NOT NULL', array($this->id));
1040 $task = new stdClass();
1041 $task->title = get_string('calculategradinggrades', 'workshop');
1042 $a = new stdClass();
1043 $a->expected = $expected;
1044 $a->calculated = $calculated;
1045 $task->details = get_string('calculategradinggradesdetails', 'workshop', $a);
1046 if ($calculated >= $expected) {
1047 $task->completed = true;
1048 } elseif ($this->phase > self::PHASE_EVALUATION) {
1049 $task->completed = false;
f55650e6 1050 }
f27b70fb
DM
1051 $phase->tasks['calculategradinggrade'] = $task;
1052
d183140d 1053 } elseif ($this->phase == self::PHASE_EVALUATION) {
1fed6ce3
DM
1054 $task = new stdClass();
1055 $task->title = get_string('evaluategradeswait', 'workshop');
1056 $task->completed = 'info';
1057 $phase->tasks['evaluateinfo'] = $task;
1058 }
b761e6d9
DM
1059 $phases[self::PHASE_EVALUATION] = $phase;
1060
1061 // Prepare tasks for the "workshop closed" phase - todo
1062 $phase = new stdClass();
1063 $phase->title = get_string('phaseclosed', 'workshop');
1064 $phase->tasks = array();
1065 $phases[self::PHASE_CLOSED] = $phase;
1066
1067 // Polish data, set default values if not done explicitly
1068 foreach ($phases as $phasecode => $phase) {
1069 $phase->title = isset($phase->title) ? $phase->title : '';
1070 $phase->tasks = isset($phase->tasks) ? $phase->tasks : array();
1071 if ($phasecode == $this->phase) {
1072 $phase->active = true;
1073 } else {
1074 $phase->active = false;
1075 }
454e8dd9
DM
1076 if (!isset($phase->actions)) {
1077 $phase->actions = array();
1078 }
b761e6d9
DM
1079
1080 foreach ($phase->tasks as $taskcode => $task) {
1081 $task->title = isset($task->title) ? $task->title : '';
da0b1f70
DM
1082 $task->link = isset($task->link) ? $task->link : null;
1083 $task->details = isset($task->details) ? $task->details : '';
b761e6d9
DM
1084 $task->completed = isset($task->completed) ? $task->completed : null;
1085 }
1086 }
454e8dd9
DM
1087
1088 // Add phase swithing actions
d895c6aa 1089 if (has_capability('mod/workshop:switchphase', $this->context, $userid)) {
454e8dd9
DM
1090 foreach ($phases as $phasecode => $phase) {
1091 if (! $phase->active) {
1092 $action = new stdClass();
1093 $action->type = 'switchphase';
1094 $action->url = $this->switchphase_url($phasecode);
1095 $phase->actions[] = $action;
1096 }
1097 }
1098 }
1099
b761e6d9
DM
1100 return $phases;
1101 }
1102
454e8dd9
DM
1103 /**
1104 * Switch to a new workshop phase
1105 *
1106 * Modifies the underlying database record. You should terminate the script shortly after calling this.
1107 *
1108 * @param int $newphase new phase code
1109 * @return bool true if success, false otherwise
1110 */
1111 public function switch_phase($newphase) {
1112 global $DB;
1113
365c2cc2 1114 $known = $this->available_phases_list();
454e8dd9
DM
1115 if (!isset($known[$newphase])) {
1116 return false;
1117 }
f6e8b318
DM
1118
1119 if (self::PHASE_CLOSED == $newphase) {
f27b70fb 1120 // push the grades into the gradebook
10bc4bce
DM
1121 $workshop = new stdClass();
1122 foreach ($this as $property => $value) {
1123 $workshop->{$property} = $value;
1124 }
1125 $workshop->course = $this->course->id;
1126 $workshop->cmidnumber = $this->cm->id;
1127 $workshop->modname = 'workshop';
1128 workshop_update_grades($workshop);
f6e8b318
DM
1129 }
1130
454e8dd9
DM
1131 $DB->set_field('workshop', 'phase', $newphase, array('id' => $this->id));
1132 return true;
1133 }
ddb59c77
DM
1134
1135 /**
1136 * Saves a raw grade for submission as calculated from the assessment form fields
1137 *
1138 * @param array $assessmentid assessment record id, must exists
00aca3c1 1139 * @param mixed $grade raw percentual grade from 0.00000 to 100.00000
ddb59c77
DM
1140 * @return false|float the saved grade
1141 */
1142 public function set_peer_grade($assessmentid, $grade) {
1143 global $DB;
1144
1145 if (is_null($grade)) {
1146 return false;
1147 }
1148 $data = new stdClass();
1149 $data->id = $assessmentid;
1150 $data->grade = $grade;
1151 $DB->update_record('workshop_assessments', $data);
1152 return $grade;
1153 }
6516b9e9 1154
29dc43e7
DM
1155 /**
1156 * Prepares data object with all workshop grades to be rendered
1157 *
5e71cefb
DM
1158 * @param int $userid the user we are preparing the report for
1159 * @param mixed $groups single group or array of groups - only show users who are in one of these group(s). Defaults to all
29dc43e7 1160 * @param int $page the current page (for the pagination)
5e71cefb 1161 * @param int $perpage participants per page (for the pagination)
f27b70fb 1162 * @param string $sortby lastname|firstname|submissiontitle|submissiongrade|gradinggrade
5e71cefb 1163 * @param string $sorthow ASC|DESC
29dc43e7
DM
1164 * @return stdClass data for the renderer
1165 */
d895c6aa 1166 public function prepare_grading_report($userid, $groups, $page, $perpage, $sortby, $sorthow) {
29dc43e7
DM
1167 global $DB;
1168
d895c6aa
DM
1169 $canviewall = has_capability('mod/workshop:viewallassessments', $this->context, $userid);
1170 $isparticipant = has_any_capability(array('mod/workshop:submit', 'mod/workshop:peerassess'), $this->context, $userid);
29dc43e7
DM
1171
1172 if (!$canviewall and !$isparticipant) {
1173 // who the hell is this?
1174 return array();
1175 }
1176
f27b70fb 1177 if (!in_array($sortby, array('lastname','firstname','submissiontitle','submissiongrade','gradinggrade'))) {
5e71cefb
DM
1178 $sortby = 'lastname';
1179 }
1180
1181 if (!($sorthow === 'ASC' or $sorthow === 'DESC')) {
1182 $sorthow = 'ASC';
1183 }
1184
1185 // get the list of user ids to be displayed
29dc43e7
DM
1186 if ($canviewall) {
1187 // fetch the list of ids of all workshop participants - this may get really long so fetch just id
d895c6aa 1188 $participants = get_users_by_capability($this->context, array('mod/workshop:submit', 'mod/workshop:peerassess'),
5e71cefb 1189 'u.id', '', '', '', $groups, '', false, false, true);
29dc43e7
DM
1190 } else {
1191 // this is an ordinary workshop participant (aka student) - display the report just for him/her
1192 $participants = array($userid => (object)array('id' => $userid));
1193 }
1194
5e71cefb 1195 // we will need to know the number of all records later for the pagination purposes
29dc43e7
DM
1196 $numofparticipants = count($participants);
1197
d183140d 1198 // load all fields which can be used for sorting and paginate the records
5e71cefb 1199 list($participantids, $params) = $DB->get_in_or_equal(array_keys($participants), SQL_PARAMS_NAMED);
d183140d
DM
1200 $params['workshopid1'] = $this->id;
1201 $params['workshopid2'] = $this->id;
5e71cefb
DM
1202 $sqlsort = $sortby . ' ' . $sorthow . ',u.lastname,u.firstname,u.id';
1203 $sql = "SELECT u.id AS userid,u.firstname,u.lastname,u.picture,u.imagealt,
f27b70fb 1204 s.title AS submissiontitle, s.grade AS submissiongrade, ag.gradinggrade
5e71cefb 1205 FROM {user} u
d183140d
DM
1206 LEFT JOIN {workshop_submissions} s ON (s.authorid = u.id AND s.workshopid = :workshopid1 AND s.example = 0)
1207 LEFT JOIN {workshop_aggregations} ag ON (ag.userid = u.id AND ag.workshopid = :workshopid2)
1208 WHERE u.id $participantids
5e71cefb
DM
1209 ORDER BY $sqlsort";
1210 $participants = $DB->get_records_sql($sql, $params, $page * $perpage, $perpage);
29dc43e7
DM
1211
1212 // this will hold the information needed to display user names and pictures
5e71cefb
DM
1213 $userinfo = array();
1214
1215 // get the user details for all participants to display
1216 foreach ($participants as $participant) {
1217 if (!isset($userinfo[$participant->userid])) {
1218 $userinfo[$participant->userid] = new stdClass();
1219 $userinfo[$participant->userid]->id = $participant->userid;
1220 $userinfo[$participant->userid]->firstname = $participant->firstname;
1221 $userinfo[$participant->userid]->lastname = $participant->lastname;
1222 $userinfo[$participant->userid]->picture = $participant->picture;
1223 $userinfo[$participant->userid]->imagealt = $participant->imagealt;
1224 }
1225 }
29dc43e7 1226
5e71cefb 1227 // load the submissions details
29dc43e7 1228 $submissions = $this->get_submissions(array_keys($participants));
5e71cefb
DM
1229
1230 // get the user details for all moderators (teachers) that have overridden a submission grade
29dc43e7 1231 foreach ($submissions as $submission) {
29dc43e7
DM
1232 if (!isset($userinfo[$submission->gradeoverby])) {
1233 $userinfo[$submission->gradeoverby] = new stdClass();
1234 $userinfo[$submission->gradeoverby]->id = $submission->gradeoverby;
1235 $userinfo[$submission->gradeoverby]->firstname = $submission->overfirstname;
1236 $userinfo[$submission->gradeoverby]->lastname = $submission->overlastname;
1237 $userinfo[$submission->gradeoverby]->picture = $submission->overpicture;
1238 $userinfo[$submission->gradeoverby]->imagealt = $submission->overimagealt;
1239 }
1240 }
1241
5e71cefb 1242 // get the user details for all reviewers of the displayed participants
29dc43e7
DM
1243 $reviewers = array();
1244 if ($submissions) {
1245 list($submissionids, $params) = $DB->get_in_or_equal(array_keys($submissions), SQL_PARAMS_NAMED);
1246 $sql = "SELECT a.id AS assessmentid, a.submissionid, a.grade, a.gradinggrade, a.gradinggradeover,
1247 r.id AS reviewerid, r.lastname, r.firstname, r.picture, r.imagealt,
1248 s.id AS submissionid, s.authorid
1249 FROM {workshop_assessments} a
1250 JOIN {user} r ON (a.reviewerid = r.id)
1251 JOIN {workshop_submissions} s ON (a.submissionid = s.id)
1252 WHERE a.submissionid $submissionids";
1253 $reviewers = $DB->get_records_sql($sql, $params);
1254 foreach ($reviewers as $reviewer) {
1255 if (!isset($userinfo[$reviewer->reviewerid])) {
1256 $userinfo[$reviewer->reviewerid] = new stdClass();
1257 $userinfo[$reviewer->reviewerid]->id = $reviewer->reviewerid;
1258 $userinfo[$reviewer->reviewerid]->firstname = $reviewer->firstname;
1259 $userinfo[$reviewer->reviewerid]->lastname = $reviewer->lastname;
1260 $userinfo[$reviewer->reviewerid]->picture = $reviewer->picture;
1261 $userinfo[$reviewer->reviewerid]->imagealt = $reviewer->imagealt;
1262 }
1263 }
1264 }
1265
5e71cefb 1266 // get the user details for all reviewees of the displayed participants
934329e5
DM
1267 $reviewees = array();
1268 if ($participants) {
1269 list($participantids, $params) = $DB->get_in_or_equal(array_keys($participants), SQL_PARAMS_NAMED);
1270 $params['workshopid'] = $this->id;
d183140d 1271 $sql = "SELECT a.id AS assessmentid, a.submissionid, a.grade, a.gradinggrade, a.gradinggradeover, a.reviewerid,
934329e5
DM
1272 s.id AS submissionid,
1273 e.id AS authorid, e.lastname, e.firstname, e.picture, e.imagealt
1274 FROM {user} u
1275 JOIN {workshop_assessments} a ON (a.reviewerid = u.id)
1276 JOIN {workshop_submissions} s ON (a.submissionid = s.id)
1277 JOIN {user} e ON (s.authorid = e.id)
1278 WHERE u.id $participantids AND s.workshopid = :workshopid";
1279 $reviewees = $DB->get_records_sql($sql, $params);
1280 foreach ($reviewees as $reviewee) {
1281 if (!isset($userinfo[$reviewee->authorid])) {
1282 $userinfo[$reviewee->authorid] = new stdClass();
1283 $userinfo[$reviewee->authorid]->id = $reviewee->authorid;
1284 $userinfo[$reviewee->authorid]->firstname = $reviewee->firstname;
1285 $userinfo[$reviewee->authorid]->lastname = $reviewee->lastname;
1286 $userinfo[$reviewee->authorid]->picture = $reviewee->picture;
1287 $userinfo[$reviewee->authorid]->imagealt = $reviewee->imagealt;
1288 }
29dc43e7
DM
1289 }
1290 }
1291
5e71cefb
DM
1292 // finally populate the object to be rendered
1293 $grades = $participants;
29dc43e7
DM
1294
1295 foreach ($participants as $participant) {
1296 // set up default (null) values
d183140d
DM
1297 $grades[$participant->userid]->submissionid = null;
1298 $grades[$participant->userid]->submissiontitle = null;
1299 $grades[$participant->userid]->submissiongrade = null;
1300 $grades[$participant->userid]->submissiongradeover = null;
1301 $grades[$participant->userid]->submissiongradeoverby = null;
5e71cefb
DM
1302 $grades[$participant->userid]->reviewedby = array();
1303 $grades[$participant->userid]->reviewerof = array();
29dc43e7
DM
1304 }
1305 unset($participants);
1306 unset($participant);
1307
1308 foreach ($submissions as $submission) {
1309 $grades[$submission->authorid]->submissionid = $submission->id;
1310 $grades[$submission->authorid]->submissiontitle = $submission->title;
b4857acb
DM
1311 $grades[$submission->authorid]->submissiongrade = $this->real_grade($submission->grade);
1312 $grades[$submission->authorid]->submissiongradeover = $this->real_grade($submission->gradeover);
29dc43e7
DM
1313 $grades[$submission->authorid]->submissiongradeoverby = $submission->gradeoverby;
1314 }
1315 unset($submissions);
1316 unset($submission);
1317
1318 foreach($reviewers as $reviewer) {
1319 $info = new stdClass();
1320 $info->userid = $reviewer->reviewerid;
1321 $info->assessmentid = $reviewer->assessmentid;
1322 $info->submissionid = $reviewer->submissionid;
b4857acb
DM
1323 $info->grade = $this->real_grade($reviewer->grade);
1324 $info->gradinggrade = $this->real_grading_grade($reviewer->gradinggrade);
1325 $info->gradinggradeover = $this->real_grading_grade($reviewer->gradinggradeover);
29dc43e7
DM
1326 $grades[$reviewer->authorid]->reviewedby[$reviewer->reviewerid] = $info;
1327 }
1328 unset($reviewers);
1329 unset($reviewer);
1330
1331 foreach($reviewees as $reviewee) {
1332 $info = new stdClass();
1333 $info->userid = $reviewee->authorid;
1334 $info->assessmentid = $reviewee->assessmentid;
1335 $info->submissionid = $reviewee->submissionid;
b4857acb
DM
1336 $info->grade = $this->real_grade($reviewee->grade);
1337 $info->gradinggrade = $this->real_grading_grade($reviewee->gradinggrade);
1338 $info->gradinggradeover = $this->real_grading_grade($reviewee->gradinggradeover);
29dc43e7
DM
1339 $grades[$reviewee->reviewerid]->reviewerof[$reviewee->authorid] = $info;
1340 }
1341 unset($reviewees);
1342 unset($reviewee);
1343
b4857acb
DM
1344 foreach ($grades as $grade) {
1345 $grade->gradinggrade = $this->real_grading_grade($grade->gradinggrade);
b4857acb
DM
1346 }
1347
29dc43e7
DM
1348 $data = new stdClass();
1349 $data->grades = $grades;
1350 $data->userinfo = $userinfo;
1351 $data->totalcount = $numofparticipants;
b4857acb
DM
1352 $data->maxgrade = $this->real_grade(100);
1353 $data->maxgradinggrade = $this->real_grading_grade(100);
29dc43e7
DM
1354 return $data;
1355 }
1356
29dc43e7 1357 /**
b4857acb 1358 * Calculates the real value of a grade
29dc43e7 1359 *
b4857acb
DM
1360 * @param float $value percentual value from 0 to 100
1361 * @param float $max the maximal grade
1362 * @return string
1363 */
1364 public function real_grade_value($value, $max) {
1365 $localized = true;
557a1100 1366 if (is_null($value) or $value === '') {
b4857acb
DM
1367 return null;
1368 } elseif ($max == 0) {
1369 return 0;
1370 } else {
1371 return format_float($max * $value / 100, $this->gradedecimals, $localized);
1372 }
1373 }
1374
e554671d
DM
1375 /**
1376 * Calculates the raw (percentual) value from a real grade
1377 *
1378 * This is used in cases when a user wants to give a grade such as 12 of 20 and we need to save
1379 * this value in a raw percentual form into DB
1380 * @param float $value given grade
1381 * @param float $max the maximal grade
1382 * @return float suitable to be stored as numeric(10,5)
1383 */
1384 public function raw_grade_value($value, $max) {
557a1100 1385 if (is_null($value) or $value === '') {
e554671d
DM
1386 return null;
1387 }
1388 if ($max == 0 or $value < 0) {
1389 return 0;
1390 }
1391 $p = $value / $max * 100;
1392 if ($p > 100) {
1393 return $max;
1394 }
1395 return grade_floatval($p);
1396 }
1397
b4857acb
DM
1398 /**
1399 * Calculates the real value of grade for submission
1400 *
1401 * @param float $value percentual value from 0 to 100
1402 * @return string
1403 */
1404 public function real_grade($value) {
1405 return $this->real_grade_value($value, $this->grade);
1406 }
1407
1408 /**
1409 * Calculates the real value of grade for assessment
1410 *
1411 * @param float $value percentual value from 0 to 100
1412 * @return string
1413 */
1414 public function real_grading_grade($value) {
1415 return $this->real_grade_value($value, $this->gradinggrade);
29dc43e7
DM
1416 }
1417
89c1aa97 1418 /**
e9a90e69 1419 * Calculates grades for submission for the given participant(s) and updates it in the database
89c1aa97
DM
1420 *
1421 * @param null|int|array $restrict If null, update all authors, otherwise update just grades for the given author(s)
1422 * @return void
1423 */
8a1ba8ac 1424 public function aggregate_submission_grades($restrict=null) {
89c1aa97
DM
1425 global $DB;
1426
1427 // fetch a recordset with all assessments to process
1696f36c 1428 $sql = 'SELECT s.id AS submissionid, s.grade AS submissiongrade,
89c1aa97
DM
1429 a.weight, a.grade
1430 FROM {workshop_submissions} s
1431 LEFT JOIN {workshop_assessments} a ON (a.submissionid = s.id)
1432 WHERE s.example=0 AND s.workshopid=:workshopid'; // to be cont.
1433 $params = array('workshopid' => $this->id);
1434
1435 if (is_null($restrict)) {
1436 // update all users - no more conditions
1437 } elseif (!empty($restrict)) {
1438 list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED);
1439 $sql .= " AND s.authorid $usql";
1440 $params = array_merge($params, $uparams);
1441 } else {
1442 throw new coding_exception('Empty value is not a valid parameter here');
1443 }
1444
1445 $sql .= ' ORDER BY s.id'; // this is important for bulk processing
89c1aa97 1446
e9a90e69
DM
1447 $rs = $DB->get_recordset_sql($sql, $params);
1448 $batch = array(); // will contain a set of all assessments of a single submission
1449 $previous = null; // a previous record in the recordset
1450
89c1aa97
DM
1451 foreach ($rs as $current) {
1452 if (is_null($previous)) {
1453 // we are processing the very first record in the recordset
1454 $previous = $current;
89c1aa97 1455 }
e9a90e69 1456 if ($current->submissionid == $previous->submissionid) {
89c1aa97 1457 // we are still processing the current submission
e9a90e69
DM
1458 $batch[] = $current;
1459 } else {
1460 // process all the assessments of a sigle submission
1461 $this->aggregate_submission_grades_process($batch);
1462 // and then start to process another submission
1463 $batch = array($current);
1464 $previous = $current;
89c1aa97
DM
1465 }
1466 }
e9a90e69
DM
1467 // do not forget to process the last batch!
1468 $this->aggregate_submission_grades_process($batch);
89c1aa97
DM
1469 $rs->close();
1470 }
1471
1472 /**
1473 * Calculates grades for assessment for the given participant(s)
1474 *
39411930
DM
1475 * Grade for assessment is calculated as a simple mean of all grading grades calculated by the grading evaluator.
1476 * The assessment weight is not taken into account here.
89c1aa97
DM
1477 *
1478 * @param null|int|array $restrict If null, update all reviewers, otherwise update just grades for the given reviewer(s)
1479 * @return void
1480 */
8a1ba8ac 1481 public function aggregate_grading_grades($restrict=null) {
89c1aa97
DM
1482 global $DB;
1483
39411930
DM
1484 // fetch a recordset with all assessments to process
1485 $sql = 'SELECT a.reviewerid, a.gradinggrade, a.gradinggradeover,
1486 ag.id AS aggregationid, ag.gradinggrade AS aggregatedgrade
1487 FROM {workshop_assessments} a
1488 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
1489 LEFT JOIN {workshop_aggregations} ag ON (ag.userid = a.reviewerid AND ag.workshopid = s.workshopid)
1490 WHERE s.example=0 AND s.workshopid=:workshopid'; // to be cont.
1491 $params = array('workshopid' => $this->id);
1492
1493 if (is_null($restrict)) {
1494 // update all users - no more conditions
1495 } elseif (!empty($restrict)) {
1496 list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED);
1497 $sql .= " AND a.reviewerid $usql";
1498 $params = array_merge($params, $uparams);
1499 } else {
1500 throw new coding_exception('Empty value is not a valid parameter here');
1501 }
1502
1503 $sql .= ' ORDER BY a.reviewerid'; // this is important for bulk processing
1504
1505 $rs = $DB->get_recordset_sql($sql, $params);
1506 $batch = array(); // will contain a set of all assessments of a single submission
1507 $previous = null; // a previous record in the recordset
1508
1509 foreach ($rs as $current) {
1510 if (is_null($previous)) {
1511 // we are processing the very first record in the recordset
1512 $previous = $current;
1513 }
1514 if ($current->reviewerid == $previous->reviewerid) {
1515 // we are still processing the current reviewer
1516 $batch[] = $current;
1517 } else {
1518 // process all the assessments of a sigle submission
1519 $this->aggregate_grading_grades_process($batch);
1520 // and then start to process another reviewer
1521 $batch = array($current);
1522 $previous = $current;
1523 }
1524 }
1525 // do not forget to process the last batch!
1526 $this->aggregate_grading_grades_process($batch);
1527 $rs->close();
89c1aa97
DM
1528 }
1529
77f43e7d 1530 /**
f6e8b318 1531 * Returns the mform the teachers use to put a feedback for the reviewer
77f43e7d 1532 *
f6e8b318 1533 * @return workshop_feedbackreviewer_form
77f43e7d 1534 */
e554671d 1535 public function get_feedbackreviewer_form(moodle_url $actionurl, stdClass $assessment, $editable=true) {
77f43e7d
DM
1536 global $CFG;
1537 require_once(dirname(__FILE__) . '/feedbackreviewer_form.php');
1538
e554671d
DM
1539 $current = new stdClass();
1540 $current->asid = $assessment->id;
1541 $current->gradinggrade = $this->real_grading_grade($assessment->gradinggrade);
1542 $current->gradinggradeover = $this->real_grading_grade($assessment->gradinggradeover);
1543 $current->feedbackreviewer = $assessment->feedbackreviewer;
1544 $current->feedbackreviewerformat = $assessment->feedbackreviewerformat;
1545 if (is_null($current->gradinggrade)) {
1546 $current->gradinggrade = get_string('nullgrade', 'workshop');
1547 }
1548
1549 // prepare wysiwyg editor
1550 $current = file_prepare_standard_editor($current, 'feedbackreviewer', array());
1551
77f43e7d 1552 return new workshop_feedbackreviewer_form($actionurl,
e554671d 1553 array('workshop' => $this, 'current' => $current, 'feedbackopts' => array()),
77f43e7d
DM
1554 'post', '', null, $editable);
1555 }
1556
67cd00ba
DM
1557 /**
1558 * Returns the mform the teachers use to put a feedback for the author on their submission
1559 *
1560 * @return workshop_feedbackauthor_form
1561 */
1562 public function get_feedbackauthor_form(moodle_url $actionurl, stdClass $submission, $editable=true) {
1563 global $CFG;
1564 require_once(dirname(__FILE__) . '/feedbackauthor_form.php');
1565
1566 $current = new stdClass();
1567 $current->submissionid = $submission->id;
557a1100
DM
1568 $current->grade = $this->real_grade($submission->grade);
1569 $current->gradeover = $this->real_grade($submission->gradeover);
1570 $current->feedbackauthor = $submission->feedbackauthor;
1571 $current->feedbackauthorformat = $submission->feedbackauthorformat;
67cd00ba
DM
1572 if (is_null($current->grade)) {
1573 $current->grade = get_string('nullgrade', 'workshop');
1574 }
1575
1576 // prepare wysiwyg editor
1577 $current = file_prepare_standard_editor($current, 'feedbackauthor', array());
1578
1579 return new workshop_feedbackauthor_form($actionurl,
1580 array('workshop' => $this, 'current' => $current, 'feedbackopts' => array()),
1581 'post', '', null, $editable);
1582 }
1583
aa40adbf
DM
1584 ////////////////////////////////////////////////////////////////////////////////
1585 // Internal methods (implementation details) //
1586 ////////////////////////////////////////////////////////////////////////////////
6516b9e9 1587
e9a90e69
DM
1588 /**
1589 * Given an array of all assessments of a single submission, calculates the final grade for this submission
1590 *
1591 * This calculates the weighted mean of the passed assessment grades. If, however, the submission grade
1592 * was overridden by a teacher, the gradeover value is returned and the rest of grades are ignored.
1593 *
1594 * @param array $assessments of stdClass(->submissionid ->submissiongrade ->gradeover ->weight ->grade)
1fed6ce3 1595 * @return void
e9a90e69
DM
1596 */
1597 protected function aggregate_submission_grades_process(array $assessments) {
1598 global $DB;
1599
1600 $submissionid = null; // the id of the submission being processed
1601 $current = null; // the grade currently saved in database
1602 $finalgrade = null; // the new grade to be calculated
1603 $sumgrades = 0;
1604 $sumweights = 0;
1605
1606 foreach ($assessments as $assessment) {
1607 if (is_null($submissionid)) {
1608 // the id is the same in all records, fetch it during the first loop cycle
1609 $submissionid = $assessment->submissionid;
1610 }
1611 if (is_null($current)) {
1612 // the currently saved grade is the same in all records, fetch it during the first loop cycle
1613 $current = $assessment->submissiongrade;
1614 }
e9a90e69
DM
1615 if (is_null($assessment->grade)) {
1616 // this was not assessed yet
1617 continue;
1618 }
1619 if ($assessment->weight == 0) {
1620 // this does not influence the calculation
1621 continue;
1622 }
1623 $sumgrades += $assessment->grade * $assessment->weight;
1624 $sumweights += $assessment->weight;
1625 }
1626 if ($sumweights > 0 and is_null($finalgrade)) {
1627 $finalgrade = grade_floatval($sumgrades / $sumweights);
1628 }
1629 // check if the new final grade differs from the one stored in the database
1630 if (grade_floats_different($finalgrade, $current)) {
1631 // we need to save new calculation into the database
10bc4bce
DM
1632 $record = new stdClass();
1633 $record->id = $submissionid;
1634 $record->grade = $finalgrade;
1635 $record->timegraded = time();
1636 $DB->update_record('workshop_submissions', $record);
e9a90e69
DM
1637 }
1638 }
1639
39411930
DM
1640 /**
1641 * Given an array of all assessments done by a single reviewer, calculates the final grading grade
1642 *
1643 * This calculates the simple mean of the passed grading grades. If, however, the grading grade
1644 * was overridden by a teacher, the gradinggradeover value is returned and the rest of grades are ignored.
1645 *
1646 * @param array $assessments of stdClass(->reviewerid ->gradinggrade ->gradinggradeover ->aggregationid ->aggregatedgrade)
1fed6ce3 1647 * @return void
39411930
DM
1648 */
1649 protected function aggregate_grading_grades_process(array $assessments) {
1650 global $DB;
1651
1652 $reviewerid = null; // the id of the reviewer being processed
1653 $current = null; // the gradinggrade currently saved in database
1654 $finalgrade = null; // the new grade to be calculated
1655 $agid = null; // aggregation id
1656 $sumgrades = 0;
1657 $count = 0;
1658
1659 foreach ($assessments as $assessment) {
1660 if (is_null($reviewerid)) {
1661 // the id is the same in all records, fetch it during the first loop cycle
1662 $reviewerid = $assessment->reviewerid;
1663 }
1664 if (is_null($agid)) {
1665 // the id is the same in all records, fetch it during the first loop cycle
1666 $agid = $assessment->aggregationid;
1667 }
1668 if (is_null($current)) {
1669 // the currently saved grade is the same in all records, fetch it during the first loop cycle
1670 $current = $assessment->aggregatedgrade;
1671 }
1672 if (!is_null($assessment->gradinggradeover)) {
1673 // the grading grade for this assessment is overriden by a teacher
1674 $sumgrades += $assessment->gradinggradeover;
1675 $count++;
1676 } else {
1677 if (!is_null($assessment->gradinggrade)) {
1678 $sumgrades += $assessment->gradinggrade;
1679 $count++;
1680 }
1681 }
1682 }
1683 if ($count > 0) {
1684 $finalgrade = grade_floatval($sumgrades / $count);
1685 }
1686 // check if the new final grade differs from the one stored in the database
1687 if (grade_floats_different($finalgrade, $current)) {
1688 // we need to save new calculation into the database
1689 if (is_null($agid)) {
1690 // no aggregation record yet
1691 $record = new stdClass();
1692 $record->workshopid = $this->id;
1693 $record->userid = $reviewerid;
1694 $record->gradinggrade = $finalgrade;
10bc4bce 1695 $record->timegraded = time();
39411930
DM
1696 $DB->insert_record('workshop_aggregations', $record);
1697 } else {
10bc4bce
DM
1698 $record = new stdClass();
1699 $record->id = $agid;
1700 $record->gradinggrade = $finalgrade;
1701 $record->timegraded = time();
1702 $DB->update_record('workshop_aggregations', $record);
39411930
DM
1703 }
1704 }
1705 }
1706
6516b9e9 1707 /**
aa40adbf 1708 * Given a list of user ids, returns the filtered one containing just ids of users with own submission
6516b9e9 1709 *
aa40adbf
DM
1710 * Example submissions are ignored.
1711 *
1712 * @param array $userids
6516b9e9
DM
1713 * @return array
1714 */
aa40adbf
DM
1715 protected function users_with_submission(array $userids) {
1716 global $DB;
1717
1718 if (empty($userids)) {
1719 return array();
1720 }
1721 $userswithsubmission = array();
1722 list($usql, $uparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
00aca3c1 1723 $sql = "SELECT id,authorid
aa40adbf 1724 FROM {workshop_submissions}
00aca3c1 1725 WHERE example = 0 AND workshopid = :workshopid AND authorid $usql";
aa40adbf
DM
1726 $params = array('workshopid' => $this->id);
1727 $params = array_merge($params, $uparams);
1728 $submissions = $DB->get_records_sql($sql, $params);
1729 foreach ($submissions as $submission) {
00aca3c1 1730 $userswithsubmission[$submission->authorid] = true;
aa40adbf
DM
1731 }
1732
1733 return $userswithsubmission;
6516b9e9
DM
1734 }
1735
aa40adbf
DM
1736 /**
1737 * @return array of available workshop phases
1738 */
365c2cc2 1739 protected function available_phases_list() {
aa40adbf
DM
1740 return array(
1741 self::PHASE_SETUP => true,
1742 self::PHASE_SUBMISSION => true,
1743 self::PHASE_ASSESSMENT => true,
1744 self::PHASE_EVALUATION => true,
1745 self::PHASE_CLOSED => true,
1746 );
1747 }
1748
66c9894d 1749}