quiz MDL-24727 column missed from install.xml
[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 *
65601f04
DM
26 * @package mod
27 * @subpackage workshop
28 * @copyright 2009 David Mudrak <david.mudrak@gmail.com>
29 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
de811c0c
DM
30 */
31
32defined('MOODLE_INTERNAL') || die();
33
89c1aa97
DM
34require_once(dirname(__FILE__).'/lib.php'); // we extend this library here
35require_once($CFG->libdir . '/gradelib.php'); // we use some rounding and comparing routines here
0968b1a3 36
6e309973
DM
37/**
38 * Full-featured workshop API
39 *
a39d7d87 40 * This wraps the workshop database record with a set of methods that are called
6e309973 41 * from the module itself. The class should be initialized right after you get
a39d7d87 42 * $workshop, $cm and $course records at the begining of the script.
6e309973 43 */
a39d7d87
DM
44class workshop {
45
b761e6d9 46 /** return statuses of {@link add_allocation} to be passed to a workshop renderer method */
cbf87967
DM
47 const ALLOCATION_EXISTS = -9999;
48 const ALLOCATION_ERROR = -9998;
b761e6d9
DM
49
50 /** the internal code of the workshop phases as are stored in the database */
f6e8b318
DM
51 const PHASE_SETUP = 10;
52 const PHASE_SUBMISSION = 20;
53 const PHASE_ASSESSMENT = 30;
54 const PHASE_EVALUATION = 40;
55 const PHASE_CLOSED = 50;
56
57 /** the internal code of the examples modes as are stored in the database */
58 const EXAMPLES_VOLUNTARY = 0;
59 const EXAMPLES_BEFORE_SUBMISSION = 1;
60 const EXAMPLES_BEFORE_ASSESSMENT = 2;
b761e6d9 61
7a789aa8 62 /** @var stdclass course module record */
e9ab520f 63 public $cm;
a39d7d87 64
7a789aa8 65 /** @var stdclass course record */
e9ab520f 66 public $course;
6e309973 67
7a789aa8 68 /** @var stdclass context object */
e9ab520f
DM
69 public $context;
70
71 /** @var int workshop instance identifier */
72 public $id;
73
74 /** @var string workshop activity name */
75 public $name;
76
77 /** @var string introduction or description of the activity */
78 public $intro;
79
80 /** @var int format of the {@link $intro} */
81 public $introformat;
82
83 /** @var string instructions for the submission phase */
84 public $instructauthors;
85
86 /** @var int format of the {@link $instructauthors} */
87 public $instructauthorsformat;
88
89 /** @var string instructions for the assessment phase */
90 public $instructreviewers;
91
92 /** @var int format of the {@link $instructreviewers} */
93 public $instructreviewersformat;
94
95 /** @var int timestamp of when the module was modified */
96 public $timemodified;
97
98 /** @var int current phase of workshop, for example {@link workshop::PHASE_SETUP} */
99 public $phase;
100
101 /** @var bool optional feature: students practise evaluating on example submissions from teacher */
102 public $useexamples;
103
104 /** @var bool optional feature: students perform peer assessment of others' work */
105 public $usepeerassessment;
106
107 /** @var bool optional feature: students perform self assessment of their own work */
108 public $useselfassessment;
109
110 /** @var float number (10, 5) unsigned, the maximum grade for submission */
111 public $grade;
112
113 /** @var float number (10, 5) unsigned, the maximum grade for assessment */
114 public $gradinggrade;
115
116 /** @var string type of the current grading strategy used in this workshop, for example 'accumulative' */
117 public $strategy;
118
c2d2eb6e
DM
119 /** @var string the name of the evaluation plugin to use for grading grades calculation */
120 public $evaluation;
121
e9ab520f
DM
122 /** @var int number of digits that should be shown after the decimal point when displaying grades */
123 public $gradedecimals;
124
125 /** @var int number of allowed submission attachments and the files embedded into submission */
126 public $nattachments;
127
128 /** @var bool allow submitting the work after the deadline */
129 public $latesubmissions;
130
131 /** @var int maximum size of the one attached file in bytes */
132 public $maxbytes;
133
134 /** @var int mode of example submissions support, for example {@link workshop::EXAMPLES_VOLUNTARY} */
135 public $examplesmode;
136
137 /** @var int if greater than 0 then the submission is not allowed before this timestamp */
138 public $submissionstart;
139
140 /** @var int if greater than 0 then the submission is not allowed after this timestamp */
141 public $submissionend;
142
143 /** @var int if greater than 0 then the peer assessment is not allowed before this timestamp */
144 public $assessmentstart;
145
146 /** @var int if greater than 0 then the peer assessment is not allowed after this timestamp */
147 public $assessmentend;
148
b761e6d9
DM
149 /**
150 * @var workshop_strategy grading strategy instance
151 * Do not use directly, get the instance using {@link workshop::grading_strategy_instance()}
152 */
b13142da
DM
153 protected $strategyinstance = null;
154
45d24d39
DM
155 /**
156 * @var workshop_evaluation grading evaluation instance
157 * Do not use directly, get the instance using {@link workshop::grading_evaluation_instance()}
158 */
159 protected $evaluationinstance = null;
160
6e309973 161 /**
65ba104c 162 * Initializes the workshop API instance using the data from DB
a39d7d87
DM
163 *
164 * Makes deep copy of all passed records properties. Replaces integer $course attribute
165 * with a full database record (course should not be stored in instances table anyway).
6e309973 166 *
5924db72
PS
167 * @param stdClass $dbrecord Workshop instance data from {workshop} table
168 * @param stdClass $cm Course module record as returned by {@link get_coursemodule_from_id()}
169 * @param stdClass $course Course record from {course} table
170 * @param stdClass $context The context of the workshop instance
0dc47fb9 171 */
7a789aa8 172 public function __construct(stdclass $dbrecord, stdclass $cm, stdclass $course, stdclass $context=null) {
f05c168d 173 foreach ($dbrecord as $field => $value) {
eef46586 174 if (property_exists('workshop', $field)) {
ac239eba
DM
175 $this->{$field} = $value;
176 }
a39d7d87 177 }
45d24d39 178 $this->cm = $cm;
ac239eba 179 $this->course = $course;
4efd7b5d
DM
180 if (is_null($context)) {
181 $this->context = get_context_instance(CONTEXT_MODULE, $this->cm->id);
182 } else {
183 $this->context = $context;
184 }
185 $this->evaluation = 'best'; // todo make this configurable although we have no alternatives yet
6e309973
DM
186 }
187
aa40adbf
DM
188 ////////////////////////////////////////////////////////////////////////////////
189 // Static methods //
190 ////////////////////////////////////////////////////////////////////////////////
191
da0b1f70 192 /**
aa40adbf 193 * Return list of available allocation methods
da0b1f70 194 *
aa40adbf 195 * @return array Array ['string' => 'string'] of localized allocation method names
da0b1f70 196 */
aa40adbf
DM
197 public static function installed_allocators() {
198 $installed = get_plugin_list('workshopallocation');
199 $forms = array();
200 foreach ($installed as $allocation => $allocationpath) {
201 if (file_exists($allocationpath . '/lib.php')) {
202 $forms[$allocation] = get_string('pluginname', 'workshopallocation_' . $allocation);
203 }
f05c168d 204 }
aa40adbf
DM
205 // usability - make sure that manual allocation appears the first
206 if (isset($forms['manual'])) {
207 $m = array('manual' => $forms['manual']);
208 unset($forms['manual']);
209 $forms = array_merge($m, $forms);
da0b1f70 210 }
aa40adbf
DM
211 return $forms;
212 }
da0b1f70 213
aa40adbf
DM
214 /**
215 * Returns an array of options for the editors that are used for submitting and assessing instructions
216 *
5924db72 217 * @param stdClass $context
aa40adbf
DM
218 * @return array
219 */
7a789aa8 220 public static function instruction_editors_options(stdclass $context) {
aa40adbf
DM
221 return array('subdirs' => 1, 'maxbytes' => 0, 'maxfiles' => EDITOR_UNLIMITED_FILES,
222 'changeformat' => 1, 'context' => $context, 'noclean' => 1, 'trusttext' => 0);
da0b1f70
DM
223 }
224
61b737a5
DM
225 /**
226 * Given the percent and the total, returns the number
227 *
228 * @param float $percent from 0 to 100
229 * @param float $total the 100% value
230 * @return float
231 */
232 public static function percent_to_value($percent, $total) {
233 if ($percent < 0 or $percent > 100) {
234 throw new coding_exception('The percent can not be less than 0 or higher than 100');
235 }
236
237 return $total * $percent / 100;
238 }
239
f6e8b318
DM
240 /**
241 * Returns an array of numeric values that can be used as maximum grades
242 *
243 * @return array Array of integers
244 */
245 public static function available_maxgrades_list() {
246 $grades = array();
247 for ($i=100; $i>=0; $i--) {
248 $grades[$i] = $i;
249 }
250 return $grades;
251 }
252
253 /**
254 * Returns the localized list of supported examples modes
255 *
256 * @return array
257 */
258 public static function available_example_modes_list() {
259 $options = array();
260 $options[self::EXAMPLES_VOLUNTARY] = get_string('examplesvoluntary', 'workshop');
261 $options[self::EXAMPLES_BEFORE_SUBMISSION] = get_string('examplesbeforesubmission', 'workshop');
262 $options[self::EXAMPLES_BEFORE_ASSESSMENT] = get_string('examplesbeforeassessment', 'workshop');
263 return $options;
264 }
265
266 /**
267 * Returns the list of available grading strategy methods
268 *
269 * @return array ['string' => 'string']
270 */
271 public static function available_strategies_list() {
272 $installed = get_plugin_list('workshopform');
273 $forms = array();
274 foreach ($installed as $strategy => $strategypath) {
275 if (file_exists($strategypath . '/lib.php')) {
276 $forms[$strategy] = get_string('pluginname', 'workshopform_' . $strategy);
277 }
278 }
279 return $forms;
280 }
281
282 /**
283 * Return an array of possible values of assessment dimension weight
284 *
285 * @return array of integers 0, 1, 2, ..., 16
286 */
287 public static function available_dimension_weights_list() {
288 $weights = array();
289 for ($i=16; $i>=0; $i--) {
290 $weights[$i] = $i;
291 }
292 return $weights;
293 }
294
c6b784f0
DM
295 /**
296 * Return an array of possible values of assessment weight
297 *
298 * Note there is no real reason why the maximum value here is 16. It used to be 10 in
299 * workshop 1.x and I just decided to use the same number as in the maximum weight of
300 * a single assessment dimension.
301 * The value looks reasonable, though. Teachers who would want to assign themselves
302 * higher weight probably do not want peer assessment really...
303 *
304 * @return array of integers 0, 1, 2, ..., 16
305 */
306 public static function available_assessment_weights_list() {
307 $weights = array();
308 for ($i=16; $i>=0; $i--) {
309 $weights[$i] = $i;
310 }
311 return $weights;
312 }
313
f6e8b318
DM
314 /**
315 * Helper function returning the greatest common divisor
316 *
317 * @param int $a
318 * @param int $b
319 * @return int
320 */
321 public static function gcd($a, $b) {
322 return ($b == 0) ? ($a):(self::gcd($b, $a % $b));
323 }
324
325 /**
326 * Helper function returning the least common multiple
327 *
328 * @param int $a
329 * @param int $b
330 * @return int
331 */
332 public static function lcm($a, $b) {
333 return ($a / self::gcd($a,$b)) * $b;
334 }
335
5bab64a3
DM
336 /**
337 * Returns an object suitable for strings containing dates/times
338 *
339 * The returned object contains properties date, datefullshort, datetime, ... containing the given
340 * timestamp formatted using strftimedate, strftimedatefullshort, strftimedatetime, ... from the
341 * current lang's langconfig.php
342 * This allows translators and administrators customize the date/time format.
343 *
344 * @param int $timestamp the timestamp in UTC
345 * @return stdclass
346 */
347 public static function timestamp_formats($timestamp) {
348 $formats = array('date', 'datefullshort', 'dateshort', 'datetime',
349 'datetimeshort', 'daydate', 'daydatetime', 'dayshort', 'daytime',
350 'monthyear', 'recent', 'recentfull', 'time');
351 $a = new stdclass();
352 foreach ($formats as $format) {
353 $a->{$format} = userdate($timestamp, get_string('strftime'.$format, 'langconfig'));
354 }
355 $day = userdate($timestamp, '%Y%m%d', 99, false);
356 $today = userdate(time(), '%Y%m%d', 99, false);
357 $tomorrow = userdate(time() + DAYSECS, '%Y%m%d', 99, false);
358 $yesterday = userdate(time() - DAYSECS, '%Y%m%d', 99, false);
359 $distance = (int)round(abs(time() - $timestamp) / DAYSECS);
360 if ($day == $today) {
361 $a->distanceday = get_string('daystoday', 'workshop');
362 } elseif ($day == $yesterday) {
363 $a->distanceday = get_string('daysyesterday', 'workshop');
364 } elseif ($day < $today) {
365 $a->distanceday = get_string('daysago', 'workshop', $distance);
366 } elseif ($day == $tomorrow) {
367 $a->distanceday = get_string('daystomorrow', 'workshop');
368 } elseif ($day > $today) {
369 $a->distanceday = get_string('daysleft', 'workshop', $distance);
370 }
371 return $a;
372 }
373
aa40adbf
DM
374 ////////////////////////////////////////////////////////////////////////////////
375 // Workshop API //
376 ////////////////////////////////////////////////////////////////////////////////
377
6e309973
DM
378 /**
379 * Fetches all users with the capability mod/workshop:submit in the current context
380 *
3d2924e9 381 * The returned objects contain id, lastname and firstname properties and are ordered by lastname,firstname
53fad4b9 382 *
aa40adbf 383 * @todo handle with limits and groups
53fad4b9 384 * @param bool $musthavesubmission If true, return only users who have already submitted. All possible authors otherwise.
7a789aa8 385 * @return array array[userid] => stdclass{->id ->lastname ->firstname}
6e309973 386 */
d895c6aa
DM
387 public function get_potential_authors($musthavesubmission=true) {
388 $users = get_users_by_capability($this->context, 'mod/workshop:submit',
1fed6ce3 389 'u.id,u.lastname,u.firstname', 'u.lastname,u.firstname,u.id', '', '', '', '', false, false, true);
3d2924e9 390 if ($musthavesubmission) {
da0b1f70 391 $users = array_intersect_key($users, $this->users_with_submission(array_keys($users)));
66c9894d 392 }
da0b1f70 393 return $users;
6e309973
DM
394 }
395
6e309973
DM
396 /**
397 * Fetches all users with the capability mod/workshop:peerassess in the current context
398 *
b13142da 399 * The returned objects contain id, lastname and firstname properties and are ordered by lastname,firstname
53fad4b9 400 *
aa40adbf 401 * @todo handle with limits and groups
53fad4b9 402 * @param bool $musthavesubmission If true, return only users who have already submitted. All possible users otherwise.
7a789aa8 403 * @return array array[userid] => stdclass{->id ->lastname ->firstname}
6e309973 404 */
d895c6aa
DM
405 public function get_potential_reviewers($musthavesubmission=false) {
406 $users = get_users_by_capability($this->context, 'mod/workshop:peerassess',
1fed6ce3 407 'u.id, u.lastname, u.firstname', 'u.lastname,u.firstname,u.id', '', '', '', '', false, false, true);
3d2924e9
DM
408 if ($musthavesubmission) {
409 // users without their own submission can not be reviewers
da0b1f70 410 $users = array_intersect_key($users, $this->users_with_submission(array_keys($users)));
0968b1a3 411 }
da0b1f70 412 return $users;
0968b1a3
DM
413 }
414
b8ead2e6
DM
415 /**
416 * Groups the given users by the group membership
417 *
418 * This takes the module grouping settings into account. If "Available for group members only"
419 * is set, returns only groups withing the course module grouping. Always returns group [0] with
420 * all the given users.
421 *
7a789aa8
DM
422 * @param array $users array[userid] => stdclass{->id ->lastname ->firstname}
423 * @return array array[groupid][userid] => stdclass{->id ->lastname ->firstname}
53fad4b9 424 */
3d2924e9 425 public function get_grouped($users) {
53fad4b9 426 global $DB;
3d2924e9 427 global $CFG;
53fad4b9 428
b8ead2e6
DM
429 $grouped = array(); // grouped users to be returned
430 if (empty($users)) {
431 return $grouped;
a7c5b918 432 }
98da6021 433 if (!empty($CFG->enablegroupmembersonly) and $this->cm->groupmembersonly) {
53fad4b9
DM
434 // Available for group members only - the workshop is available only
435 // to users assigned to groups within the selected grouping, or to
436 // any group if no grouping is selected.
437 $groupingid = $this->cm->groupingid;
b8ead2e6 438 // All users that are members of at least one group will be
53fad4b9 439 // added into a virtual group id 0
b8ead2e6 440 $grouped[0] = array();
53fad4b9
DM
441 } else {
442 $groupingid = 0;
b8ead2e6
DM
443 // there is no need to be member of a group so $grouped[0] will contain
444 // all users
445 $grouped[0] = $users;
53fad4b9 446 }
b8ead2e6 447 $gmemberships = groups_get_all_groups($this->cm->course, array_keys($users), $groupingid,
53fad4b9
DM
448 'gm.id,gm.groupid,gm.userid');
449 foreach ($gmemberships as $gmembership) {
b8ead2e6
DM
450 if (!isset($grouped[$gmembership->groupid])) {
451 $grouped[$gmembership->groupid] = array();
53fad4b9 452 }
b8ead2e6
DM
453 $grouped[$gmembership->groupid][$gmembership->userid] = $users[$gmembership->userid];
454 $grouped[0][$gmembership->userid] = $users[$gmembership->userid];
53fad4b9 455 }
b8ead2e6 456 return $grouped;
53fad4b9 457 }
6e309973 458
aa40adbf
DM
459 /**
460 * Returns the list of all allocations (it est assigned assessments) in the workshop
461 *
462 * Assessments of example submissions are ignored
463 *
464 * @return array
465 */
466 public function get_allocations() {
467 global $DB;
468
00aca3c1 469 $sql = 'SELECT a.id, a.submissionid, a.reviewerid, s.authorid
aa40adbf
DM
470 FROM {workshop_assessments} a
471 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
472 WHERE s.example = 0 AND s.workshopid = :workshopid';
473 $params = array('workshopid' => $this->id);
474
475 return $DB->get_records_sql($sql, $params);
476 }
477
6e309973
DM
478 /**
479 * Returns submissions from this workshop
480 *
3dc78e5b
DM
481 * Fetches data from {workshop_submissions} and adds some useful information from other
482 * tables. Does not return textual fields to prevent possible memory lack issues.
53fad4b9 483 *
00aca3c1 484 * @param mixed $authorid int|array|'all' If set to [array of] integer, return submission[s] of the given user[s] only
934329e5 485 * @return array of records or an empty array
6e309973 486 */
29dc43e7 487 public function get_submissions($authorid='all') {
6e309973
DM
488 global $DB;
489
00aca3c1 490 $sql = 'SELECT s.id, s.workshopid, s.example, s.authorid, s.timecreated, s.timemodified,
232175e4 491 s.title, s.grade, s.gradeover, s.gradeoverby, s.published,
00aca3c1 492 u.lastname AS authorlastname, u.firstname AS authorfirstname,
3a11c09f 493 u.picture AS authorpicture, u.imagealt AS authorimagealt, u.email AS authoremail,
00aca3c1 494 t.lastname AS overlastname, t.firstname AS overfirstname,
3a11c09f 495 t.picture AS overpicture, t.imagealt AS overimagealt, t.email AS overemail
3d2924e9 496 FROM {workshop_submissions} s
00aca3c1 497 INNER JOIN {user} u ON (s.authorid = u.id)
29dc43e7
DM
498 LEFT JOIN {user} t ON (s.gradeoverby = t.id)
499 WHERE s.example = 0 AND s.workshopid = :workshopid';
3d2924e9 500 $params = array('workshopid' => $this->id);
6e309973 501
00aca3c1 502 if ('all' === $authorid) {
3d2924e9 503 // no additional conditions
934329e5 504 } elseif (!empty($authorid)) {
00aca3c1
DM
505 list($usql, $uparams) = $DB->get_in_or_equal($authorid, SQL_PARAMS_NAMED);
506 $sql .= " AND authorid $usql";
6e309973 507 $params = array_merge($params, $uparams);
3d2924e9 508 } else {
934329e5
DM
509 // $authorid is empty
510 return array();
6e309973 511 }
3dc78e5b 512 $sql .= ' ORDER BY u.lastname, u.firstname';
6e309973 513
3dc78e5b 514 return $DB->get_records_sql($sql, $params);
6e309973
DM
515 }
516
51508f25
DM
517 /**
518 * Returns a submission record with the author's data
519 *
520 * @param int $id submission id
7a789aa8 521 * @return stdclass
51508f25
DM
522 */
523 public function get_submission_by_id($id) {
524 global $DB;
525
29dc43e7
DM
526 // we intentionally check the workshopid here, too, so the workshop can't touch submissions
527 // from other instances
51508f25
DM
528 $sql = 'SELECT s.*,
529 u.lastname AS authorlastname, u.firstname AS authorfirstname, u.id AS authorid,
3a11c09f 530 u.picture AS authorpicture, u.imagealt AS authorimagealt, u.email AS authoremail
51508f25 531 FROM {workshop_submissions} s
00aca3c1 532 INNER JOIN {user} u ON (s.authorid = u.id)
81eccf0a 533 WHERE s.example = 0 AND s.workshopid = :workshopid AND s.id = :id';
51508f25
DM
534 $params = array('workshopid' => $this->id, 'id' => $id);
535 return $DB->get_record_sql($sql, $params, MUST_EXIST);
536 }
537
53fad4b9 538 /**
3dc78e5b 539 * Returns a submission submitted by the given author
53fad4b9 540 *
3dc78e5b 541 * @param int $id author id
7a789aa8 542 * @return stdclass|false
53fad4b9 543 */
00aca3c1 544 public function get_submission_by_author($authorid) {
e9b0f0ab
DM
545 global $DB;
546
00aca3c1 547 if (empty($authorid)) {
53fad4b9
DM
548 return false;
549 }
3dc78e5b
DM
550 $sql = 'SELECT s.*,
551 u.lastname AS authorlastname, u.firstname AS authorfirstname, u.id AS authorid,
3a11c09f 552 u.picture AS authorpicture, u.imagealt AS authorimagealt, u.email AS authoremail
3dc78e5b 553 FROM {workshop_submissions} s
00aca3c1
DM
554 INNER JOIN {user} u ON (s.authorid = u.id)
555 WHERE s.example = 0 AND s.workshopid = :workshopid AND s.authorid = :authorid';
556 $params = array('workshopid' => $this->id, 'authorid' => $authorid);
3dc78e5b 557 return $DB->get_record_sql($sql, $params);
53fad4b9 558 }
6e309973 559
00bc77ee
DM
560 /**
561 * Returns published submissions with their authors data
562 *
563 * @return array of stdclass
564 */
565 public function get_published_submissions($orderby='finalgrade DESC') {
566 global $DB;
567
568 $sql = "SELECT s.id, s.authorid, s.timecreated, s.timemodified,
569 s.title, s.grade, s.gradeover, COALESCE(s.gradeover,s.grade) AS finalgrade,
570 u.lastname AS authorlastname, u.firstname AS authorfirstname, u.id AS authorid,
3a11c09f 571 u.picture AS authorpicture, u.imagealt AS authorimagealt, u.email AS authoremail
00bc77ee
DM
572 FROM {workshop_submissions} s
573 INNER JOIN {user} u ON (s.authorid = u.id)
574 WHERE s.example = 0 AND s.workshopid = :workshopid AND s.published = 1
575 ORDER BY $orderby";
576 $params = array('workshopid' => $this->id);
577 return $DB->get_records_sql($sql, $params);
578 }
579
81eccf0a
DM
580 /**
581 * Returns full record of the given example submission
582 *
583 * @param int $id example submission od
584 * @return object
585 */
586 public function get_example_by_id($id) {
587 global $DB;
588 return $DB->get_record('workshop_submissions',
589 array('id' => $id, 'workshopid' => $this->id, 'example' => 1), '*', MUST_EXIST);
590 }
591
cbf87967
DM
592 /**
593 * Returns the list of example submissions in this workshop with reference assessments attached
594 *
595 * @return array of objects or an empty array
596 * @see workshop::prepare_example_summary()
597 */
598 public function get_examples_for_manager() {
599 global $DB;
600
601 $sql = 'SELECT s.id, s.title,
602 a.id AS assessmentid, a.weight, a.grade, a.gradinggrade
603 FROM {workshop_submissions} s
604 LEFT JOIN {workshop_assessments} a ON (a.submissionid = s.id AND a.weight = 1)
605 WHERE s.example = 1 AND s.workshopid = :workshopid
606 ORDER BY s.title';
607 return $DB->get_records_sql($sql, array('workshopid' => $this->id));
608 }
609
610 /**
611 * Returns the list of all example submissions in this workshop with the information of assessments done by the given user
612 *
613 * @param int $reviewerid user id
614 * @return array of objects, indexed by example submission id
615 * @see workshop::prepare_example_summary()
616 */
617 public function get_examples_for_reviewer($reviewerid) {
618 global $DB;
619
620 if (empty($reviewerid)) {
621 return false;
622 }
623 $sql = 'SELECT s.id, s.title,
624 a.id AS assessmentid, a.weight, a.grade, a.gradinggrade
625 FROM {workshop_submissions} s
626 LEFT JOIN {workshop_assessments} a ON (a.submissionid = s.id AND a.reviewerid = :reviewerid AND a.weight = 0)
627 WHERE s.example = 1 AND s.workshopid = :workshopid
628 ORDER BY s.title';
629 return $DB->get_records_sql($sql, array('workshopid' => $this->id, 'reviewerid' => $reviewerid));
630 }
631
632 /**
633 * Prepares component containing summary of given example to be rendered
634 *
5924db72 635 * @param stdClass $example as returned by {@link workshop::get_examples_for_manager()} or {@link workshop::get_examples_for_reviewer()}
7a789aa8 636 * @return stdclass component to be rendered
cbf87967 637 */
7a789aa8 638 public function prepare_example_summary(stdclass $example) {
cbf87967 639
7a789aa8 640 $summary = new stdclass();
cbf87967
DM
641 $summary->example = $example;
642 if (is_null($example->grade)) {
643 $summary->status = 'notgraded';
644 $buttontext = get_string('assess', 'workshop');
645 } else {
646 $summary->status = 'graded';
647 $buttontext = get_string('reassess', 'workshop');
648 }
649
7a789aa8 650 $summary->gradeinfo = new stdclass();
cbf87967
DM
651 $summary->gradeinfo->received = $this->real_grade($example->grade);
652 $summary->gradeinfo->max = $this->real_grade(100);
653
3ba60ee1
PS
654 $aurl = new moodle_url($this->exsubmission_url($example->id), array('assess' => 'on', 'sesskey' => sesskey()));
655 $summary->btnform = new single_button($aurl, $buttontext, 'get');
cbf87967
DM
656
657 return $summary;
658 }
659
81eccf0a
DM
660 /**
661 * Removes the submission and all relevant data
662 *
5924db72 663 * @param stdClass $submission record to delete
81eccf0a
DM
664 * @return void
665 */
7a789aa8 666 public function delete_submission(stdclass $submission) {
81eccf0a
DM
667 global $DB;
668 $assessments = $DB->get_records('workshop_assessments', array('submissionid' => $submission->id), '', 'id');
669 $this->delete_assessment(array_keys($assessments));
670 $DB->delete_records('workshop_submissions', array('id' => $submission->id));
671 }
672
6e309973 673 /**
3dc78e5b 674 * Returns the list of all assessments in the workshop with some data added
6e309973
DM
675 *
676 * Fetches data from {workshop_assessments} and adds some useful information from other
3dc78e5b
DM
677 * tables. The returned object does not contain textual fields (ie comments) to prevent memory
678 * lack issues.
679 *
7a789aa8 680 * @return array [assessmentid] => assessment stdclass
6e309973 681 */
3dc78e5b 682 public function get_all_assessments() {
6e309973 683 global $DB;
53fad4b9 684
f6e8b318 685 $sql = 'SELECT a.id, a.submissionid, a.reviewerid, a.timecreated, a.timemodified,
3dc78e5b 686 a.grade, a.gradinggrade, a.gradinggradeover, a.gradinggradeoverby,
3d2924e9
DM
687 reviewer.id AS reviewerid,reviewer.firstname AS reviewerfirstname,reviewer.lastname as reviewerlastname,
688 s.title,
ddb59c77 689 author.id AS authorid, author.firstname AS authorfirstname,author.lastname AS authorlastname
3d2924e9 690 FROM {workshop_assessments} a
00aca3c1 691 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id)
3d2924e9 692 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
00aca3c1 693 INNER JOIN {user} author ON (s.authorid = author.id)
3dc78e5b
DM
694 WHERE s.workshopid = :workshopid AND s.example = 0
695 ORDER BY reviewer.lastname, reviewer.firstname';
3d2924e9
DM
696 $params = array('workshopid' => $this->id);
697
3dc78e5b 698 return $DB->get_records_sql($sql, $params);
53fad4b9
DM
699 }
700
701 /**
3dc78e5b 702 * Get the complete information about the given assessment
53fad4b9
DM
703 *
704 * @param int $id Assessment ID
5a372494 705 * @return stdclass
53fad4b9
DM
706 */
707 public function get_assessment_by_id($id) {
3dc78e5b
DM
708 global $DB;
709
710 $sql = 'SELECT a.*,
711 reviewer.id AS reviewerid,reviewer.firstname AS reviewerfirstname,reviewer.lastname as reviewerlastname,
712 s.title,
713 author.id AS authorid, author.firstname AS authorfirstname,author.lastname as authorlastname
714 FROM {workshop_assessments} a
00aca3c1 715 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id)
3dc78e5b 716 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
00aca3c1 717 INNER JOIN {user} author ON (s.authorid = author.id)
3dc78e5b
DM
718 WHERE a.id = :id AND s.workshopid = :workshopid';
719 $params = array('id' => $id, 'workshopid' => $this->id);
720
721 return $DB->get_record_sql($sql, $params, MUST_EXIST);
53fad4b9
DM
722 }
723
5a372494
DM
724 /**
725 * Get the complete information about the user's assessment of the given submission
726 *
727 * @param int $sid submission ID
728 * @param int $uid user ID of the reviewer
729 * @return false|stdclass false if not found, stdclass otherwise
730 */
731 public function get_assessment_of_submission_by_user($submissionid, $reviewerid) {
732 global $DB;
733
734 $sql = 'SELECT a.*,
735 reviewer.id AS reviewerid,reviewer.firstname AS reviewerfirstname,reviewer.lastname as reviewerlastname,
736 s.title,
737 author.id AS authorid, author.firstname AS authorfirstname,author.lastname as authorlastname
738 FROM {workshop_assessments} a
739 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id)
740 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id AND s.example = 0)
741 INNER JOIN {user} author ON (s.authorid = author.id)
742 WHERE s.id = :sid AND reviewer.id = :rid AND s.workshopid = :workshopid';
743 $params = array('sid' => $submissionid, 'rid' => $reviewerid, 'workshopid' => $this->id);
744
745 return $DB->get_record_sql($sql, $params, IGNORE_MISSING);
746 }
747
748 /**
749 * Get the complete information about all assessments of the given submission
750 *
751 * @param int $submissionid
752 * @return array
753 */
754 public function get_assessments_of_submission($submissionid) {
755 global $DB;
756
757 $sql = 'SELECT a.*,
758 reviewer.id AS reviewerid,reviewer.firstname AS reviewerfirstname,reviewer.lastname AS reviewerlastname
759 FROM {workshop_assessments} a
760 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id)
761 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
762 WHERE s.example = 0 AND s.id = :submissionid AND s.workshopid = :workshopid';
763 $params = array('submissionid' => $submissionid, 'workshopid' => $this->id);
764
765 return $DB->get_records_sql($sql, $params);
766 }
767
53fad4b9 768 /**
3dc78e5b 769 * Get the complete information about all assessments allocated to the given reviewer
53fad4b9 770 *
00aca3c1 771 * @param int $reviewerid
3dc78e5b 772 * @return array
53fad4b9 773 */
00aca3c1 774 public function get_assessments_by_reviewer($reviewerid) {
3dc78e5b
DM
775 global $DB;
776
777 $sql = 'SELECT a.*,
ddb59c77
DM
778 reviewer.id AS reviewerid,reviewer.firstname AS reviewerfirstname,reviewer.lastname AS reviewerlastname,
779 s.id AS submissionid, s.title AS submissiontitle, s.timecreated AS submissioncreated,
780 s.timemodified AS submissionmodified,
781 author.id AS authorid, author.firstname AS authorfirstname,author.lastname AS authorlastname,
3a11c09f 782 author.picture AS authorpicture, author.imagealt AS authorimagealt, author.email AS authoremail
3dc78e5b 783 FROM {workshop_assessments} a
00aca3c1 784 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id)
3dc78e5b 785 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
00aca3c1
DM
786 INNER JOIN {user} author ON (s.authorid = author.id)
787 WHERE s.example = 0 AND reviewer.id = :reviewerid AND s.workshopid = :workshopid';
788 $params = array('reviewerid' => $reviewerid, 'workshopid' => $this->id);
3dc78e5b
DM
789
790 return $DB->get_records_sql($sql, $params);
53fad4b9 791 }
6e309973 792
6e309973
DM
793 /**
794 * Allocate a submission to a user for review
53fad4b9 795 *
5924db72 796 * @param stdClass $submission Submission object with at least id property
6e309973 797 * @param int $reviewerid User ID
becec954 798 * @param int $weight of the new assessment, from 0 to 16
67ae13d9 799 * @param bool $bulk repeated inserts into DB expected
6e309973
DM
800 * @return int ID of the new assessment or an error code
801 */
67ae13d9 802 public function add_allocation(stdclass $submission, $reviewerid, $weight=1, $bulk=false) {
6e309973
DM
803 global $DB;
804
00aca3c1 805 if ($DB->record_exists('workshop_assessments', array('submissionid' => $submission->id, 'reviewerid' => $reviewerid))) {
b761e6d9 806 return self::ALLOCATION_EXISTS;
6e309973
DM
807 }
808
67ae13d9
DM
809 $weight = (int)$weight;
810 if ($weight < 0) {
811 $weight = 0;
812 }
813 if ($weight > 16) {
814 $weight = 16;
815 }
816
6e309973 817 $now = time();
7a789aa8 818 $assessment = new stdclass();
e554671d
DM
819 $assessment->submissionid = $submission->id;
820 $assessment->reviewerid = $reviewerid;
7a5f4be0 821 $assessment->timecreated = $now; // do not set timemodified here
becec954 822 $assessment->weight = $weight;
884482fb
DM
823 $assessment->generalcommentformat = editors_get_preferred_format();
824 $assessment->feedbackreviewerformat = editors_get_preferred_format();
6e309973 825
235b31c8 826 return $DB->insert_record('workshop_assessments', $assessment, true, $bulk);
6e309973
DM
827 }
828
6e309973 829 /**
53fad4b9 830 * Delete assessment record or records
6e309973 831 *
53fad4b9
DM
832 * @param mixed $id int|array assessment id or array of assessments ids
833 * @return bool false if $id not a valid parameter, true otherwise
6e309973
DM
834 */
835 public function delete_assessment($id) {
836 global $DB;
837
838 // todo remove all given grades from workshop_grades;
6e309973 839
53fad4b9 840 if (is_array($id)) {
235b31c8 841 return $DB->delete_records_list('workshop_assessments', 'id', $id);
3d2924e9 842 } else {
235b31c8 843 return $DB->delete_records('workshop_assessments', array('id' => $id));
53fad4b9 844 }
53fad4b9 845 }
6e309973
DM
846
847 /**
848 * Returns instance of grading strategy class
53fad4b9 849 *
7a789aa8 850 * @return stdclass Instance of a grading strategy
6e309973
DM
851 */
852 public function grading_strategy_instance() {
3d2924e9
DM
853 global $CFG; // because we require other libs here
854
3fd2b0e1 855 if (is_null($this->strategyinstance)) {
f05c168d 856 $strategylib = dirname(__FILE__) . '/form/' . $this->strategy . '/lib.php';
6e309973
DM
857 if (is_readable($strategylib)) {
858 require_once($strategylib);
859 } else {
f05c168d 860 throw new coding_exception('the grading forms subplugin must contain library ' . $strategylib);
6e309973 861 }
0dc47fb9 862 $classname = 'workshop_' . $this->strategy . '_strategy';
3fd2b0e1
DM
863 $this->strategyinstance = new $classname($this);
864 if (!in_array('workshop_strategy', class_implements($this->strategyinstance))) {
b761e6d9 865 throw new coding_exception($classname . ' does not implement workshop_strategy interface');
6e309973
DM
866 }
867 }
3fd2b0e1 868 return $this->strategyinstance;
6e309973
DM
869 }
870
45d24d39
DM
871 /**
872 * Returns instance of grading evaluation class
873 *
7a789aa8 874 * @return stdclass Instance of a grading evaluation
45d24d39
DM
875 */
876 public function grading_evaluation_instance() {
877 global $CFG; // because we require other libs here
878
879 if (is_null($this->evaluationinstance)) {
880 $evaluationlib = dirname(__FILE__) . '/eval/' . $this->evaluation . '/lib.php';
881 if (is_readable($evaluationlib)) {
882 require_once($evaluationlib);
883 } else {
884 throw new coding_exception('the grading evaluation subplugin must contain library ' . $evaluationlib);
885 }
886 $classname = 'workshop_' . $this->evaluation . '_evaluation';
887 $this->evaluationinstance = new $classname($this);
888 if (!in_array('workshop_evaluation', class_implements($this->evaluationinstance))) {
889 throw new coding_exception($classname . ' does not implement workshop_evaluation interface');
890 }
891 }
892 return $this->evaluationinstance;
893 }
894
66c9894d
DM
895 /**
896 * Returns instance of submissions allocator
53fad4b9 897 *
130ae619 898 * @param string $method The name of the allocation method, must be PARAM_ALPHA
7a789aa8 899 * @return stdclass Instance of submissions allocator
66c9894d
DM
900 */
901 public function allocator_instance($method) {
3d2924e9
DM
902 global $CFG; // because we require other libs here
903
f05c168d 904 $allocationlib = dirname(__FILE__) . '/allocation/' . $method . '/lib.php';
66c9894d
DM
905 if (is_readable($allocationlib)) {
906 require_once($allocationlib);
907 } else {
f05c168d 908 throw new coding_exception('Unable to find the allocation library ' . $allocationlib);
66c9894d
DM
909 }
910 $classname = 'workshop_' . $method . '_allocator';
911 return new $classname($this);
912 }
913
b8ead2e6 914 /**
454e8dd9 915 * @return moodle_url of this workshop's view page
b8ead2e6
DM
916 */
917 public function view_url() {
918 global $CFG;
a6855934 919 return new moodle_url('/mod/workshop/view.php', array('id' => $this->cm->id));
b8ead2e6
DM
920 }
921
922 /**
454e8dd9 923 * @return moodle_url of the page for editing this workshop's grading form
b8ead2e6
DM
924 */
925 public function editform_url() {
926 global $CFG;
a6855934 927 return new moodle_url('/mod/workshop/editform.php', array('cmid' => $this->cm->id));
b8ead2e6
DM
928 }
929
930 /**
454e8dd9 931 * @return moodle_url of the page for previewing this workshop's grading form
b8ead2e6
DM
932 */
933 public function previewform_url() {
934 global $CFG;
a6855934 935 return new moodle_url('/mod/workshop/editformpreview.php', array('cmid' => $this->cm->id));
b8ead2e6
DM
936 }
937
938 /**
939 * @param int $assessmentid The ID of assessment record
454e8dd9 940 * @return moodle_url of the assessment page
b8ead2e6 941 */
a39d7d87 942 public function assess_url($assessmentid) {
b8ead2e6 943 global $CFG;
454e8dd9 944 $assessmentid = clean_param($assessmentid, PARAM_INT);
a6855934 945 return new moodle_url('/mod/workshop/assessment.php', array('asid' => $assessmentid));
b8ead2e6
DM
946 }
947
becec954
DM
948 /**
949 * @param int $assessmentid The ID of assessment record
950 * @return moodle_url of the example assessment page
951 */
952 public function exassess_url($assessmentid) {
953 global $CFG;
954 $assessmentid = clean_param($assessmentid, PARAM_INT);
a6855934 955 return new moodle_url('/mod/workshop/exassessment.php', array('asid' => $assessmentid));
becec954
DM
956 }
957
39861053 958 /**
67cd00ba 959 * @return moodle_url of the page to view a submission, defaults to the own one
39861053 960 */
67cd00ba 961 public function submission_url($id=null) {
39861053 962 global $CFG;
a6855934 963 return new moodle_url('/mod/workshop/submission.php', array('cmid' => $this->cm->id, 'id' => $id));
39861053
DM
964 }
965
81eccf0a
DM
966 /**
967 * @param int $id example submission id
968 * @return moodle_url of the page to view an example submission
969 */
becec954 970 public function exsubmission_url($id) {
81eccf0a 971 global $CFG;
a6855934 972 return new moodle_url('/mod/workshop/exsubmission.php', array('cmid' => $this->cm->id, 'id' => $id));
81eccf0a
DM
973 }
974
cbf87967
DM
975 /**
976 * @param int $sid submission id
977 * @param array $aid of int assessment ids
978 * @return moodle_url of the page to compare assessments of the given submission
979 */
980 public function compare_url($sid, array $aids) {
981 global $CFG;
982
a6855934 983 $url = new moodle_url('/mod/workshop/compare.php', array('cmid' => $this->cm->id, 'sid' => $sid));
cbf87967
DM
984 $i = 0;
985 foreach ($aids as $aid) {
986 $url->param("aid{$i}", $aid);
987 $i++;
988 }
989 return $url;
990 }
991
992 /**
993 * @param int $sid submission id
994 * @param int $aid assessment id
995 * @return moodle_url of the page to compare the reference assessments of the given example submission
996 */
997 public function excompare_url($sid, $aid) {
998 global $CFG;
a6855934 999 return new moodle_url('/mod/workshop/excompare.php', array('cmid' => $this->cm->id, 'sid' => $sid, 'aid' => $aid));
cbf87967
DM
1000 }
1001
da0b1f70 1002 /**
454e8dd9 1003 * @return moodle_url of the mod_edit form
da0b1f70
DM
1004 */
1005 public function updatemod_url() {
1006 global $CFG;
a6855934 1007 return new moodle_url('/course/modedit.php', array('update' => $this->cm->id, 'return' => 1));
da0b1f70
DM
1008 }
1009
454e8dd9 1010 /**
08af32af 1011 * @param string $method allocation method
454e8dd9
DM
1012 * @return moodle_url to the allocation page
1013 */
08af32af 1014 public function allocation_url($method=null) {
da0b1f70 1015 global $CFG;
08af32af
DM
1016 $params = array('cmid' => $this->cm->id);
1017 if (!empty($method)) {
1018 $params['method'] = $method;
1019 }
1020 return new moodle_url('/mod/workshop/allocation.php', $params);
da0b1f70
DM
1021 }
1022
454e8dd9
DM
1023 /**
1024 * @param int $phasecode The internal phase code
1025 * @return moodle_url of the script to change the current phase to $phasecode
1026 */
1027 public function switchphase_url($phasecode) {
1028 global $CFG;
1029 $phasecode = clean_param($phasecode, PARAM_INT);
a6855934 1030 return new moodle_url('/mod/workshop/switchphase.php', array('cmid' => $this->cm->id, 'phase' => $phasecode));
454e8dd9
DM
1031 }
1032
89c1aa97
DM
1033 /**
1034 * @return moodle_url to the aggregation page
1035 */
1036 public function aggregate_url() {
1037 global $CFG;
a6855934 1038 return new moodle_url('/mod/workshop/aggregate.php', array('cmid' => $this->cm->id));
89c1aa97
DM
1039 }
1040
32c78bc3
DM
1041 /**
1042 * @return moodle_url of this workshop's toolbox page
1043 */
1044 public function toolbox_url($tool) {
1045 global $CFG;
1046 return new moodle_url('/mod/workshop/toolbox.php', array('id' => $this->cm->id, 'tool' => $tool));
1047 }
1048
b8ead2e6 1049 /**
2f289d36 1050 * Are users allowed to create their submissions?
407b1e91 1051 *
407b1e91 1052 * @return bool
b8ead2e6 1053 */
2f289d36
DM
1054 public function creating_submission_allowed() {
1055 $now = time();
1056
1057 if ($this->latesubmissions) {
1058 if ($this->phase != self::PHASE_SUBMISSION and $this->phase != self::PHASE_ASSESSMENT) {
1059 // late submissions are allowed in the submission and assessment phase only
1060 return false;
1061 }
1062 if (!empty($this->submissionstart) and $this->submissionstart > $now) {
1063 // late submissions are not allowed before the submission start
1064 return false;
1065 }
1066 return true;
1067
1068 } else {
1069 if ($this->phase != self::PHASE_SUBMISSION) {
1070 // submissions are allowed during the submission phase only
1071 return false;
1072 }
1073 if (!empty($this->submissionstart) and $this->submissionstart > $now) {
1074 // if enabled, submitting is not allowed before the date/time defined in the mod_form
1075 return false;
1076 }
1077 if (!empty($this->submissionend) and $now > $this->submissionend ) {
1078 // if enabled, submitting is not allowed after the date/time defined in the mod_form unless late submission is allowed
1079 return false;
1080 }
1081 return true;
1082 }
1083 }
1084
1085 /**
1086 * Are users allowed to modify their existing submission?
1087 *
1088 * @return bool
1089 */
1090 public function modifying_submission_allowed() {
1091 $now = time();
1092
74bf8a94 1093 if ($this->phase != self::PHASE_SUBMISSION) {
2f289d36 1094 // submissions can be edited during the submission phase only
74bf8a94
DM
1095 return false;
1096 }
74bf8a94
DM
1097 if (!empty($this->submissionstart) and $this->submissionstart > $now) {
1098 // if enabled, submitting is not allowed before the date/time defined in the mod_form
1099 return false;
1100 }
2f289d36 1101 if (!empty($this->submissionend) and $now > $this->submissionend) {
74bf8a94
DM
1102 // if enabled, submitting is not allowed after the date/time defined in the mod_form unless late submission is allowed
1103 return false;
1104 }
407b1e91 1105 return true;
b8ead2e6
DM
1106 }
1107
c1e883bb 1108 /**
407b1e91 1109 * Are reviewers allowed to create/edit their assessments?
c1e883bb 1110 *
c1e883bb
DM
1111 * @return bool
1112 */
407b1e91 1113 public function assessing_allowed() {
74bf8a94
DM
1114 if ($this->phase != self::PHASE_ASSESSMENT) {
1115 // assessing is not allowed but in the assessment phase
1116 return false;
1117 }
1118 $now = time();
1119 if (!empty($this->assessmentstart) and $this->assessmentstart > $now) {
1120 // if enabled, assessing is not allowed before the date/time defined in the mod_form
1121 return false;
1122 }
1123 if (!empty($this->assessmentend) and $now > $this->assessmentend ) {
1124 // if enabled, assessing is not allowed after the date/time defined in the mod_form
1125 return false;
1126 }
1127 // here we go, assessing is allowed
c1e883bb
DM
1128 return true;
1129 }
1130
becec954
DM
1131 /**
1132 * Are reviewers allowed to create/edit their assessments of the example submissions?
1133 *
514d8c22
DM
1134 * Returns null if example submissions are not enabled in this workshop. Otherwise returns
1135 * true or false. Note this does not check other conditions like the number of already
1136 * assessed examples, examples mode etc.
becec954 1137 *
74bf8a94 1138 * @return null|bool
becec954
DM
1139 */
1140 public function assessing_examples_allowed() {
74bf8a94
DM
1141 if (empty($this->useexamples)) {
1142 return null;
1143 }
1144 if (self::EXAMPLES_VOLUNTARY == $this->examplesmode) {
1145 return true;
1146 }
1147 if (self::EXAMPLES_BEFORE_SUBMISSION == $this->examplesmode and self::PHASE_SUBMISSION == $this->phase) {
1148 return true;
1149 }
1150 if (self::EXAMPLES_BEFORE_ASSESSMENT == $this->examplesmode and self::PHASE_ASSESSMENT == $this->phase) {
1151 return true;
1152 }
1153 return false;
becec954 1154 }
407b1e91 1155
3dc78e5b
DM
1156 /**
1157 * Are the peer-reviews available to the authors?
1158 *
3dc78e5b
DM
1159 * @return bool
1160 */
1161 public function assessments_available() {
5a372494 1162 return $this->phase == self::PHASE_CLOSED;
3dc78e5b
DM
1163 }
1164
454e8dd9
DM
1165 /**
1166 * Switch to a new workshop phase
1167 *
1168 * Modifies the underlying database record. You should terminate the script shortly after calling this.
1169 *
1170 * @param int $newphase new phase code
1171 * @return bool true if success, false otherwise
1172 */
1173 public function switch_phase($newphase) {
1174 global $DB;
1175
365c2cc2 1176 $known = $this->available_phases_list();
454e8dd9
DM
1177 if (!isset($known[$newphase])) {
1178 return false;
1179 }
f6e8b318
DM
1180
1181 if (self::PHASE_CLOSED == $newphase) {
f27b70fb 1182 // push the grades into the gradebook
7a789aa8 1183 $workshop = new stdclass();
10bc4bce
DM
1184 foreach ($this as $property => $value) {
1185 $workshop->{$property} = $value;
1186 }
1187 $workshop->course = $this->course->id;
1188 $workshop->cmidnumber = $this->cm->id;
1189 $workshop->modname = 'workshop';
1190 workshop_update_grades($workshop);
f6e8b318
DM
1191 }
1192
454e8dd9
DM
1193 $DB->set_field('workshop', 'phase', $newphase, array('id' => $this->id));
1194 return true;
1195 }
ddb59c77
DM
1196
1197 /**
1198 * Saves a raw grade for submission as calculated from the assessment form fields
1199 *
1200 * @param array $assessmentid assessment record id, must exists
00aca3c1 1201 * @param mixed $grade raw percentual grade from 0.00000 to 100.00000
ddb59c77
DM
1202 * @return false|float the saved grade
1203 */
1204 public function set_peer_grade($assessmentid, $grade) {
1205 global $DB;
1206
1207 if (is_null($grade)) {
1208 return false;
1209 }
7a789aa8 1210 $data = new stdclass();
ddb59c77
DM
1211 $data->id = $assessmentid;
1212 $data->grade = $grade;
7a5f4be0 1213 $data->timemodified = time();
ddb59c77
DM
1214 $DB->update_record('workshop_assessments', $data);
1215 return $grade;
1216 }
6516b9e9 1217
29dc43e7
DM
1218 /**
1219 * Prepares data object with all workshop grades to be rendered
1220 *
5e71cefb
DM
1221 * @param int $userid the user we are preparing the report for
1222 * @param mixed $groups single group or array of groups - only show users who are in one of these group(s). Defaults to all
29dc43e7 1223 * @param int $page the current page (for the pagination)
5e71cefb 1224 * @param int $perpage participants per page (for the pagination)
f27b70fb 1225 * @param string $sortby lastname|firstname|submissiontitle|submissiongrade|gradinggrade
5e71cefb 1226 * @param string $sorthow ASC|DESC
7a789aa8 1227 * @return stdclass data for the renderer
29dc43e7 1228 */
d895c6aa 1229 public function prepare_grading_report($userid, $groups, $page, $perpage, $sortby, $sorthow) {
29dc43e7
DM
1230 global $DB;
1231
d895c6aa
DM
1232 $canviewall = has_capability('mod/workshop:viewallassessments', $this->context, $userid);
1233 $isparticipant = has_any_capability(array('mod/workshop:submit', 'mod/workshop:peerassess'), $this->context, $userid);
29dc43e7
DM
1234
1235 if (!$canviewall and !$isparticipant) {
1236 // who the hell is this?
1237 return array();
1238 }
1239
f27b70fb 1240 if (!in_array($sortby, array('lastname','firstname','submissiontitle','submissiongrade','gradinggrade'))) {
5e71cefb
DM
1241 $sortby = 'lastname';
1242 }
1243
1244 if (!($sorthow === 'ASC' or $sorthow === 'DESC')) {
1245 $sorthow = 'ASC';
1246 }
1247
1248 // get the list of user ids to be displayed
29dc43e7
DM
1249 if ($canviewall) {
1250 // fetch the list of ids of all workshop participants - this may get really long so fetch just id
d895c6aa 1251 $participants = get_users_by_capability($this->context, array('mod/workshop:submit', 'mod/workshop:peerassess'),
5e71cefb 1252 'u.id', '', '', '', $groups, '', false, false, true);
29dc43e7
DM
1253 } else {
1254 // this is an ordinary workshop participant (aka student) - display the report just for him/her
1255 $participants = array($userid => (object)array('id' => $userid));
1256 }
1257
5e71cefb 1258 // we will need to know the number of all records later for the pagination purposes
29dc43e7
DM
1259 $numofparticipants = count($participants);
1260
deea6e7a
DM
1261 if ($numofparticipants > 0) {
1262 // load all fields which can be used for sorting and paginate the records
1263 list($participantids, $params) = $DB->get_in_or_equal(array_keys($participants), SQL_PARAMS_NAMED);
1264 $params['workshopid1'] = $this->id;
1265 $params['workshopid2'] = $this->id;
1266 $sqlsort = $sortby . ' ' . $sorthow . ',u.lastname,u.firstname,u.id';
3a11c09f 1267 $sql = "SELECT u.id AS userid,u.firstname,u.lastname,u.picture,u.imagealt,u.email,
deea6e7a
DM
1268 s.title AS submissiontitle, s.grade AS submissiongrade, ag.gradinggrade
1269 FROM {user} u
1270 LEFT JOIN {workshop_submissions} s ON (s.authorid = u.id AND s.workshopid = :workshopid1 AND s.example = 0)
1271 LEFT JOIN {workshop_aggregations} ag ON (ag.userid = u.id AND ag.workshopid = :workshopid2)
1272 WHERE u.id $participantids
1273 ORDER BY $sqlsort";
1274 $participants = $DB->get_records_sql($sql, $params, $page * $perpage, $perpage);
1275 } else {
1276 $participants = array();
1277 }
29dc43e7
DM
1278
1279 // this will hold the information needed to display user names and pictures
5e71cefb
DM
1280 $userinfo = array();
1281
1282 // get the user details for all participants to display
1283 foreach ($participants as $participant) {
1284 if (!isset($userinfo[$participant->userid])) {
7a789aa8 1285 $userinfo[$participant->userid] = new stdclass();
5e71cefb
DM
1286 $userinfo[$participant->userid]->id = $participant->userid;
1287 $userinfo[$participant->userid]->firstname = $participant->firstname;
1288 $userinfo[$participant->userid]->lastname = $participant->lastname;
1289 $userinfo[$participant->userid]->picture = $participant->picture;
1290 $userinfo[$participant->userid]->imagealt = $participant->imagealt;
3a11c09f 1291 $userinfo[$participant->userid]->email = $participant->email;
5e71cefb
DM
1292 }
1293 }
29dc43e7 1294
5e71cefb 1295 // load the submissions details
29dc43e7 1296 $submissions = $this->get_submissions(array_keys($participants));
5e71cefb
DM
1297
1298 // get the user details for all moderators (teachers) that have overridden a submission grade
29dc43e7 1299 foreach ($submissions as $submission) {
29dc43e7 1300 if (!isset($userinfo[$submission->gradeoverby])) {
7a789aa8 1301 $userinfo[$submission->gradeoverby] = new stdclass();
29dc43e7
DM
1302 $userinfo[$submission->gradeoverby]->id = $submission->gradeoverby;
1303 $userinfo[$submission->gradeoverby]->firstname = $submission->overfirstname;
1304 $userinfo[$submission->gradeoverby]->lastname = $submission->overlastname;
1305 $userinfo[$submission->gradeoverby]->picture = $submission->overpicture;
1306 $userinfo[$submission->gradeoverby]->imagealt = $submission->overimagealt;
3a11c09f 1307 $userinfo[$submission->gradeoverby]->email = $submission->overemail;
29dc43e7
DM
1308 }
1309 }
1310
5e71cefb 1311 // get the user details for all reviewers of the displayed participants
29dc43e7
DM
1312 $reviewers = array();
1313 if ($submissions) {
1314 list($submissionids, $params) = $DB->get_in_or_equal(array_keys($submissions), SQL_PARAMS_NAMED);
581878b8 1315 $sql = "SELECT a.id AS assessmentid, a.submissionid, a.grade, a.gradinggrade, a.gradinggradeover, a.weight,
3a11c09f 1316 r.id AS reviewerid, r.lastname, r.firstname, r.picture, r.imagealt, r.email,
29dc43e7
DM
1317 s.id AS submissionid, s.authorid
1318 FROM {workshop_assessments} a
1319 JOIN {user} r ON (a.reviewerid = r.id)
0324b6f1 1320 JOIN {workshop_submissions} s ON (a.submissionid = s.id AND s.example = 0)
c6b784f0
DM
1321 WHERE a.submissionid $submissionids
1322 ORDER BY a.weight DESC, r.lastname, r.firstname";
29dc43e7
DM
1323 $reviewers = $DB->get_records_sql($sql, $params);
1324 foreach ($reviewers as $reviewer) {
1325 if (!isset($userinfo[$reviewer->reviewerid])) {
7a789aa8 1326 $userinfo[$reviewer->reviewerid] = new stdclass();
29dc43e7
DM
1327 $userinfo[$reviewer->reviewerid]->id = $reviewer->reviewerid;
1328 $userinfo[$reviewer->reviewerid]->firstname = $reviewer->firstname;
1329 $userinfo[$reviewer->reviewerid]->lastname = $reviewer->lastname;
1330 $userinfo[$reviewer->reviewerid]->picture = $reviewer->picture;
1331 $userinfo[$reviewer->reviewerid]->imagealt = $reviewer->imagealt;
3a11c09f 1332 $userinfo[$reviewer->reviewerid]->email = $reviewer->email;
29dc43e7
DM
1333 }
1334 }
1335 }
1336
5e71cefb 1337 // get the user details for all reviewees of the displayed participants
934329e5
DM
1338 $reviewees = array();
1339 if ($participants) {
1340 list($participantids, $params) = $DB->get_in_or_equal(array_keys($participants), SQL_PARAMS_NAMED);
1341 $params['workshopid'] = $this->id;
581878b8 1342 $sql = "SELECT a.id AS assessmentid, a.submissionid, a.grade, a.gradinggrade, a.gradinggradeover, a.reviewerid, a.weight,
934329e5 1343 s.id AS submissionid,
3a11c09f 1344 e.id AS authorid, e.lastname, e.firstname, e.picture, e.imagealt, e.email
934329e5
DM
1345 FROM {user} u
1346 JOIN {workshop_assessments} a ON (a.reviewerid = u.id)
0324b6f1 1347 JOIN {workshop_submissions} s ON (a.submissionid = s.id AND s.example = 0)
934329e5 1348 JOIN {user} e ON (s.authorid = e.id)
c6b784f0
DM
1349 WHERE u.id $participantids AND s.workshopid = :workshopid
1350 ORDER BY a.weight DESC, e.lastname, e.firstname";
934329e5
DM
1351 $reviewees = $DB->get_records_sql($sql, $params);
1352 foreach ($reviewees as $reviewee) {
1353 if (!isset($userinfo[$reviewee->authorid])) {
7a789aa8 1354 $userinfo[$reviewee->authorid] = new stdclass();
934329e5
DM
1355 $userinfo[$reviewee->authorid]->id = $reviewee->authorid;
1356 $userinfo[$reviewee->authorid]->firstname = $reviewee->firstname;
1357 $userinfo[$reviewee->authorid]->lastname = $reviewee->lastname;
1358 $userinfo[$reviewee->authorid]->picture = $reviewee->picture;
1359 $userinfo[$reviewee->authorid]->imagealt = $reviewee->imagealt;
3a11c09f 1360 $userinfo[$reviewee->authorid]->email = $reviewee->email;
934329e5 1361 }
29dc43e7
DM
1362 }
1363 }
1364
5e71cefb
DM
1365 // finally populate the object to be rendered
1366 $grades = $participants;
29dc43e7
DM
1367
1368 foreach ($participants as $participant) {
1369 // set up default (null) values
d183140d
DM
1370 $grades[$participant->userid]->submissionid = null;
1371 $grades[$participant->userid]->submissiontitle = null;
1372 $grades[$participant->userid]->submissiongrade = null;
1373 $grades[$participant->userid]->submissiongradeover = null;
1374 $grades[$participant->userid]->submissiongradeoverby = null;
232175e4 1375 $grades[$participant->userid]->submissionpublished = null;
5e71cefb
DM
1376 $grades[$participant->userid]->reviewedby = array();
1377 $grades[$participant->userid]->reviewerof = array();
29dc43e7
DM
1378 }
1379 unset($participants);
1380 unset($participant);
1381
1382 foreach ($submissions as $submission) {
1383 $grades[$submission->authorid]->submissionid = $submission->id;
1384 $grades[$submission->authorid]->submissiontitle = $submission->title;
b4857acb
DM
1385 $grades[$submission->authorid]->submissiongrade = $this->real_grade($submission->grade);
1386 $grades[$submission->authorid]->submissiongradeover = $this->real_grade($submission->gradeover);
29dc43e7 1387 $grades[$submission->authorid]->submissiongradeoverby = $submission->gradeoverby;
232175e4 1388 $grades[$submission->authorid]->submissionpublished = $submission->published;
29dc43e7
DM
1389 }
1390 unset($submissions);
1391 unset($submission);
1392
1393 foreach($reviewers as $reviewer) {
7a789aa8 1394 $info = new stdclass();
29dc43e7
DM
1395 $info->userid = $reviewer->reviewerid;
1396 $info->assessmentid = $reviewer->assessmentid;
1397 $info->submissionid = $reviewer->submissionid;
b4857acb
DM
1398 $info->grade = $this->real_grade($reviewer->grade);
1399 $info->gradinggrade = $this->real_grading_grade($reviewer->gradinggrade);
1400 $info->gradinggradeover = $this->real_grading_grade($reviewer->gradinggradeover);
581878b8 1401 $info->weight = $reviewer->weight;
29dc43e7
DM
1402 $grades[$reviewer->authorid]->reviewedby[$reviewer->reviewerid] = $info;
1403 }
1404 unset($reviewers);
1405 unset($reviewer);
1406
1407 foreach($reviewees as $reviewee) {
7a789aa8 1408 $info = new stdclass();
29dc43e7
DM
1409 $info->userid = $reviewee->authorid;
1410 $info->assessmentid = $reviewee->assessmentid;
1411 $info->submissionid = $reviewee->submissionid;
b4857acb
DM
1412 $info->grade = $this->real_grade($reviewee->grade);
1413 $info->gradinggrade = $this->real_grading_grade($reviewee->gradinggrade);
1414 $info->gradinggradeover = $this->real_grading_grade($reviewee->gradinggradeover);
581878b8 1415 $info->weight = $reviewee->weight;
29dc43e7
DM
1416 $grades[$reviewee->reviewerid]->reviewerof[$reviewee->authorid] = $info;
1417 }
1418 unset($reviewees);
1419 unset($reviewee);
1420
b4857acb
DM
1421 foreach ($grades as $grade) {
1422 $grade->gradinggrade = $this->real_grading_grade($grade->gradinggrade);
b4857acb
DM
1423 }
1424
7a789aa8 1425 $data = new stdclass();
29dc43e7
DM
1426 $data->grades = $grades;
1427 $data->userinfo = $userinfo;
1428 $data->totalcount = $numofparticipants;
b4857acb
DM
1429 $data->maxgrade = $this->real_grade(100);
1430 $data->maxgradinggrade = $this->real_grading_grade(100);
29dc43e7
DM
1431 return $data;
1432 }
1433
29dc43e7 1434 /**
b4857acb 1435 * Calculates the real value of a grade
29dc43e7 1436 *
b4857acb
DM
1437 * @param float $value percentual value from 0 to 100
1438 * @param float $max the maximal grade
1439 * @return string
1440 */
1441 public function real_grade_value($value, $max) {
1442 $localized = true;
557a1100 1443 if (is_null($value) or $value === '') {
b4857acb
DM
1444 return null;
1445 } elseif ($max == 0) {
1446 return 0;
1447 } else {
1448 return format_float($max * $value / 100, $this->gradedecimals, $localized);
1449 }
1450 }
1451
e554671d
DM
1452 /**
1453 * Calculates the raw (percentual) value from a real grade
1454 *
1455 * This is used in cases when a user wants to give a grade such as 12 of 20 and we need to save
1456 * this value in a raw percentual form into DB
1457 * @param float $value given grade
1458 * @param float $max the maximal grade
1459 * @return float suitable to be stored as numeric(10,5)
1460 */
1461 public function raw_grade_value($value, $max) {
557a1100 1462 if (is_null($value) or $value === '') {
e554671d
DM
1463 return null;
1464 }
1465 if ($max == 0 or $value < 0) {
1466 return 0;
1467 }
1468 $p = $value / $max * 100;
1469 if ($p > 100) {
1470 return $max;
1471 }
1472 return grade_floatval($p);
1473 }
1474
b4857acb
DM
1475 /**
1476 * Calculates the real value of grade for submission
1477 *
1478 * @param float $value percentual value from 0 to 100
1479 * @return string
1480 */
1481 public function real_grade($value) {
1482 return $this->real_grade_value($value, $this->grade);
1483 }
1484
1485 /**
1486 * Calculates the real value of grade for assessment
1487 *
1488 * @param float $value percentual value from 0 to 100
1489 * @return string
1490 */
1491 public function real_grading_grade($value) {
1492 return $this->real_grade_value($value, $this->gradinggrade);
29dc43e7
DM
1493 }
1494
e706b9c3
DM
1495 /**
1496 * Sets the given grades and received grading grades to null
1497 *
1498 * This does not clear the information about how the peers filled the assessment forms, but
1499 * clears the calculated grades in workshop_assessments. Therefore reviewers have to re-assess
1500 * the allocated submissions.
1501 *
1502 * @return void
1503 */
1504 public function clear_assessments() {
1505 global $DB;
1506
1507 $submissions = $this->get_submissions();
1508 if (empty($submissions)) {
1509 // no money, no love
1510 return;
1511 }
1512 $submissions = array_keys($submissions);
1513 list($sql, $params) = $DB->get_in_or_equal($submissions, SQL_PARAMS_NAMED);
1514 $sql = "submissionid $sql";
1515 $DB->set_field_select('workshop_assessments', 'grade', null, $sql, $params);
1516 $DB->set_field_select('workshop_assessments', 'gradinggrade', null, $sql, $params);
1517 }
1518
32c78bc3
DM
1519 /**
1520 * Sets the grades for submission to null
1521 *
1522 * @param null|int|array $restrict If null, update all authors, otherwise update just grades for the given author(s)
1523 * @return void
1524 */
1525 public function clear_submission_grades($restrict=null) {
1526 global $DB;
1527
1528 $sql = "workshopid = :workshopid AND example = 0";
1529 $params = array('workshopid' => $this->id);
1530
1531 if (is_null($restrict)) {
1532 // update all users - no more conditions
1533 } elseif (!empty($restrict)) {
1534 list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED);
1535 $sql .= " AND authorid $usql";
1536 $params = array_merge($params, $uparams);
1537 } else {
1538 throw new coding_exception('Empty value is not a valid parameter here');
1539 }
1540
1541 $DB->set_field_select('workshop_submissions', 'grade', null, $sql, $params);
1542 }
1543
89c1aa97 1544 /**
e9a90e69 1545 * Calculates grades for submission for the given participant(s) and updates it in the database
89c1aa97
DM
1546 *
1547 * @param null|int|array $restrict If null, update all authors, otherwise update just grades for the given author(s)
1548 * @return void
1549 */
8a1ba8ac 1550 public function aggregate_submission_grades($restrict=null) {
89c1aa97
DM
1551 global $DB;
1552
1553 // fetch a recordset with all assessments to process
1696f36c 1554 $sql = 'SELECT s.id AS submissionid, s.grade AS submissiongrade,
89c1aa97
DM
1555 a.weight, a.grade
1556 FROM {workshop_submissions} s
1557 LEFT JOIN {workshop_assessments} a ON (a.submissionid = s.id)
1558 WHERE s.example=0 AND s.workshopid=:workshopid'; // to be cont.
1559 $params = array('workshopid' => $this->id);
1560
1561 if (is_null($restrict)) {
1562 // update all users - no more conditions
1563 } elseif (!empty($restrict)) {
1564 list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED);
1565 $sql .= " AND s.authorid $usql";
1566 $params = array_merge($params, $uparams);
1567 } else {
1568 throw new coding_exception('Empty value is not a valid parameter here');
1569 }
1570
1571 $sql .= ' ORDER BY s.id'; // this is important for bulk processing
89c1aa97 1572
e9a90e69
DM
1573 $rs = $DB->get_recordset_sql($sql, $params);
1574 $batch = array(); // will contain a set of all assessments of a single submission
1575 $previous = null; // a previous record in the recordset
1576
89c1aa97
DM
1577 foreach ($rs as $current) {
1578 if (is_null($previous)) {
1579 // we are processing the very first record in the recordset
1580 $previous = $current;
89c1aa97 1581 }
e9a90e69 1582 if ($current->submissionid == $previous->submissionid) {
89c1aa97 1583 // we are still processing the current submission
e9a90e69
DM
1584 $batch[] = $current;
1585 } else {
1586 // process all the assessments of a sigle submission
1587 $this->aggregate_submission_grades_process($batch);
1588 // and then start to process another submission
1589 $batch = array($current);
1590 $previous = $current;
89c1aa97
DM
1591 }
1592 }
e9a90e69
DM
1593 // do not forget to process the last batch!
1594 $this->aggregate_submission_grades_process($batch);
89c1aa97
DM
1595 $rs->close();
1596 }
1597
32c78bc3
DM
1598 /**
1599 * Sets the aggregated grades for assessment to null
1600 *
1601 * @param null|int|array $restrict If null, update all reviewers, otherwise update just grades for the given reviewer(s)
1602 * @return void
1603 */
1604 public function clear_grading_grades($restrict=null) {
1605 global $DB;
1606
1607 $sql = "workshopid = :workshopid";
1608 $params = array('workshopid' => $this->id);
1609
1610 if (is_null($restrict)) {
1611 // update all users - no more conditions
1612 } elseif (!empty($restrict)) {
1613 list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED);
1614 $sql .= " AND userid $usql";
1615 $params = array_merge($params, $uparams);
1616 } else {
1617 throw new coding_exception('Empty value is not a valid parameter here');
1618 }
1619
1620 $DB->set_field_select('workshop_aggregations', 'gradinggrade', null, $sql, $params);
1621 }
1622
89c1aa97
DM
1623 /**
1624 * Calculates grades for assessment for the given participant(s)
1625 *
39411930
DM
1626 * Grade for assessment is calculated as a simple mean of all grading grades calculated by the grading evaluator.
1627 * The assessment weight is not taken into account here.
89c1aa97
DM
1628 *
1629 * @param null|int|array $restrict If null, update all reviewers, otherwise update just grades for the given reviewer(s)
1630 * @return void
1631 */
8a1ba8ac 1632 public function aggregate_grading_grades($restrict=null) {
89c1aa97
DM
1633 global $DB;
1634
39411930
DM
1635 // fetch a recordset with all assessments to process
1636 $sql = 'SELECT a.reviewerid, a.gradinggrade, a.gradinggradeover,
1637 ag.id AS aggregationid, ag.gradinggrade AS aggregatedgrade
1638 FROM {workshop_assessments} a
1639 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
1640 LEFT JOIN {workshop_aggregations} ag ON (ag.userid = a.reviewerid AND ag.workshopid = s.workshopid)
1641 WHERE s.example=0 AND s.workshopid=:workshopid'; // to be cont.
1642 $params = array('workshopid' => $this->id);
1643
1644 if (is_null($restrict)) {
1645 // update all users - no more conditions
1646 } elseif (!empty($restrict)) {
1647 list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED);
1648 $sql .= " AND a.reviewerid $usql";
1649 $params = array_merge($params, $uparams);
1650 } else {
1651 throw new coding_exception('Empty value is not a valid parameter here');
1652 }
1653
1654 $sql .= ' ORDER BY a.reviewerid'; // this is important for bulk processing
1655
1656 $rs = $DB->get_recordset_sql($sql, $params);
1657 $batch = array(); // will contain a set of all assessments of a single submission
1658 $previous = null; // a previous record in the recordset
1659
1660 foreach ($rs as $current) {
1661 if (is_null($previous)) {
1662 // we are processing the very first record in the recordset
1663 $previous = $current;
1664 }
1665 if ($current->reviewerid == $previous->reviewerid) {
1666 // we are still processing the current reviewer
1667 $batch[] = $current;
1668 } else {
1669 // process all the assessments of a sigle submission
1670 $this->aggregate_grading_grades_process($batch);
1671 // and then start to process another reviewer
1672 $batch = array($current);
1673 $previous = $current;
1674 }
1675 }
1676 // do not forget to process the last batch!
1677 $this->aggregate_grading_grades_process($batch);
1678 $rs->close();
89c1aa97
DM
1679 }
1680
77f43e7d 1681 /**
f6e8b318 1682 * Returns the mform the teachers use to put a feedback for the reviewer
77f43e7d 1683 *
c6b784f0 1684 * @param moodle_url $actionurl
5924db72 1685 * @param stdClass $assessment
c6b784f0 1686 * @param array $options editable, editableweight, overridablegradinggrade
f6e8b318 1687 * @return workshop_feedbackreviewer_form
77f43e7d 1688 */
c6b784f0 1689 public function get_feedbackreviewer_form(moodle_url $actionurl, stdclass $assessment, $options=array()) {
77f43e7d
DM
1690 global $CFG;
1691 require_once(dirname(__FILE__) . '/feedbackreviewer_form.php');
1692
7a789aa8 1693 $current = new stdclass();
e554671d 1694 $current->asid = $assessment->id;
c6b784f0 1695 $current->weight = $assessment->weight;
e554671d
DM
1696 $current->gradinggrade = $this->real_grading_grade($assessment->gradinggrade);
1697 $current->gradinggradeover = $this->real_grading_grade($assessment->gradinggradeover);
1698 $current->feedbackreviewer = $assessment->feedbackreviewer;
1699 $current->feedbackreviewerformat = $assessment->feedbackreviewerformat;
1700 if (is_null($current->gradinggrade)) {
1701 $current->gradinggrade = get_string('nullgrade', 'workshop');
1702 }
c6b784f0
DM
1703 if (!isset($options['editable'])) {
1704 $editable = true; // by default
1705 } else {
1706 $editable = (bool)$options['editable'];
1707 }
e554671d
DM
1708
1709 // prepare wysiwyg editor
1710 $current = file_prepare_standard_editor($current, 'feedbackreviewer', array());
1711
77f43e7d 1712 return new workshop_feedbackreviewer_form($actionurl,
c6b784f0 1713 array('workshop' => $this, 'current' => $current, 'editoropts' => array(), 'options' => $options),
77f43e7d
DM
1714 'post', '', null, $editable);
1715 }
1716
67cd00ba
DM
1717 /**
1718 * Returns the mform the teachers use to put a feedback for the author on their submission
1719 *
c6b784f0 1720 * @param moodle_url $actionurl
5924db72 1721 * @param stdClass $submission
c6b784f0 1722 * @param array $options editable
67cd00ba
DM
1723 * @return workshop_feedbackauthor_form
1724 */
c6b784f0 1725 public function get_feedbackauthor_form(moodle_url $actionurl, stdclass $submission, $options=array()) {
67cd00ba
DM
1726 global $CFG;
1727 require_once(dirname(__FILE__) . '/feedbackauthor_form.php');
1728
7a789aa8 1729 $current = new stdclass();
67cd00ba 1730 $current->submissionid = $submission->id;
232175e4 1731 $current->published = $submission->published;
557a1100
DM
1732 $current->grade = $this->real_grade($submission->grade);
1733 $current->gradeover = $this->real_grade($submission->gradeover);
1734 $current->feedbackauthor = $submission->feedbackauthor;
1735 $current->feedbackauthorformat = $submission->feedbackauthorformat;
67cd00ba
DM
1736 if (is_null($current->grade)) {
1737 $current->grade = get_string('nullgrade', 'workshop');
1738 }
c6b784f0
DM
1739 if (!isset($options['editable'])) {
1740 $editable = true; // by default
1741 } else {
1742 $editable = (bool)$options['editable'];
1743 }
67cd00ba
DM
1744
1745 // prepare wysiwyg editor
1746 $current = file_prepare_standard_editor($current, 'feedbackauthor', array());
1747
1748 return new workshop_feedbackauthor_form($actionurl,
232175e4 1749 array('workshop' => $this, 'current' => $current, 'editoropts' => array(), 'options' => $options),
67cd00ba
DM
1750 'post', '', null, $editable);
1751 }
1752
aa40adbf
DM
1753 ////////////////////////////////////////////////////////////////////////////////
1754 // Internal methods (implementation details) //
1755 ////////////////////////////////////////////////////////////////////////////////
6516b9e9 1756
e9a90e69
DM
1757 /**
1758 * Given an array of all assessments of a single submission, calculates the final grade for this submission
1759 *
1760 * This calculates the weighted mean of the passed assessment grades. If, however, the submission grade
1761 * was overridden by a teacher, the gradeover value is returned and the rest of grades are ignored.
1762 *
7a789aa8 1763 * @param array $assessments of stdclass(->submissionid ->submissiongrade ->gradeover ->weight ->grade)
1fed6ce3 1764 * @return void
e9a90e69
DM
1765 */
1766 protected function aggregate_submission_grades_process(array $assessments) {
1767 global $DB;
1768
1769 $submissionid = null; // the id of the submission being processed
1770 $current = null; // the grade currently saved in database
1771 $finalgrade = null; // the new grade to be calculated
1772 $sumgrades = 0;
1773 $sumweights = 0;
1774
1775 foreach ($assessments as $assessment) {
1776 if (is_null($submissionid)) {
1777 // the id is the same in all records, fetch it during the first loop cycle
1778 $submissionid = $assessment->submissionid;
1779 }
1780 if (is_null($current)) {
1781 // the currently saved grade is the same in all records, fetch it during the first loop cycle
1782 $current = $assessment->submissiongrade;
1783 }
e9a90e69
DM
1784 if (is_null($assessment->grade)) {
1785 // this was not assessed yet
1786 continue;
1787 }
1788 if ($assessment->weight == 0) {
1789 // this does not influence the calculation
1790 continue;
1791 }
1792 $sumgrades += $assessment->grade * $assessment->weight;
1793 $sumweights += $assessment->weight;
1794 }
1795 if ($sumweights > 0 and is_null($finalgrade)) {
1796 $finalgrade = grade_floatval($sumgrades / $sumweights);
1797 }
1798 // check if the new final grade differs from the one stored in the database
1799 if (grade_floats_different($finalgrade, $current)) {
1800 // we need to save new calculation into the database
7a789aa8 1801 $record = new stdclass();
10bc4bce
DM
1802 $record->id = $submissionid;
1803 $record->grade = $finalgrade;
1804 $record->timegraded = time();
1805 $DB->update_record('workshop_submissions', $record);
e9a90e69
DM
1806 }
1807 }
1808
39411930
DM
1809 /**
1810 * Given an array of all assessments done by a single reviewer, calculates the final grading grade
1811 *
1812 * This calculates the simple mean of the passed grading grades. If, however, the grading grade
1813 * was overridden by a teacher, the gradinggradeover value is returned and the rest of grades are ignored.
1814 *
7a789aa8 1815 * @param array $assessments of stdclass(->reviewerid ->gradinggrade ->gradinggradeover ->aggregationid ->aggregatedgrade)
1fed6ce3 1816 * @return void
39411930
DM
1817 */
1818 protected function aggregate_grading_grades_process(array $assessments) {
1819 global $DB;
1820
1821 $reviewerid = null; // the id of the reviewer being processed
1822 $current = null; // the gradinggrade currently saved in database
1823 $finalgrade = null; // the new grade to be calculated
1824 $agid = null; // aggregation id
1825 $sumgrades = 0;
1826 $count = 0;
1827
1828 foreach ($assessments as $assessment) {
1829 if (is_null($reviewerid)) {
1830 // the id is the same in all records, fetch it during the first loop cycle
1831 $reviewerid = $assessment->reviewerid;
1832 }
1833 if (is_null($agid)) {
1834 // the id is the same in all records, fetch it during the first loop cycle
1835 $agid = $assessment->aggregationid;
1836 }
1837 if (is_null($current)) {
1838 // the currently saved grade is the same in all records, fetch it during the first loop cycle
1839 $current = $assessment->aggregatedgrade;
1840 }
1841 if (!is_null($assessment->gradinggradeover)) {
5924db72 1842 // the grading grade for this assessment is overridden by a teacher
39411930
DM
1843 $sumgrades += $assessment->gradinggradeover;
1844 $count++;
1845 } else {
1846 if (!is_null($assessment->gradinggrade)) {
1847 $sumgrades += $assessment->gradinggrade;
1848 $count++;
1849 }
1850 }
1851 }
1852 if ($count > 0) {
1853 $finalgrade = grade_floatval($sumgrades / $count);
1854 }
1855 // check if the new final grade differs from the one stored in the database
1856 if (grade_floats_different($finalgrade, $current)) {
1857 // we need to save new calculation into the database
1858 if (is_null($agid)) {
1859 // no aggregation record yet
7a789aa8 1860 $record = new stdclass();
39411930
DM
1861 $record->workshopid = $this->id;
1862 $record->userid = $reviewerid;
1863 $record->gradinggrade = $finalgrade;
10bc4bce 1864 $record->timegraded = time();
39411930
DM
1865 $DB->insert_record('workshop_aggregations', $record);
1866 } else {
7a789aa8 1867 $record = new stdclass();
10bc4bce
DM
1868 $record->id = $agid;
1869 $record->gradinggrade = $finalgrade;
1870 $record->timegraded = time();
1871 $DB->update_record('workshop_aggregations', $record);
39411930
DM
1872 }
1873 }
1874 }
1875
6516b9e9 1876 /**
aa40adbf 1877 * Given a list of user ids, returns the filtered one containing just ids of users with own submission
6516b9e9 1878 *
aa40adbf
DM
1879 * Example submissions are ignored.
1880 *
1881 * @param array $userids
6516b9e9
DM
1882 * @return array
1883 */
aa40adbf
DM
1884 protected function users_with_submission(array $userids) {
1885 global $DB;
1886
1887 if (empty($userids)) {
1888 return array();
1889 }
1890 $userswithsubmission = array();
1891 list($usql, $uparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
00aca3c1 1892 $sql = "SELECT id,authorid
aa40adbf 1893 FROM {workshop_submissions}
00aca3c1 1894 WHERE example = 0 AND workshopid = :workshopid AND authorid $usql";
aa40adbf
DM
1895 $params = array('workshopid' => $this->id);
1896 $params = array_merge($params, $uparams);
1897 $submissions = $DB->get_records_sql($sql, $params);
1898 foreach ($submissions as $submission) {
00aca3c1 1899 $userswithsubmission[$submission->authorid] = true;
aa40adbf
DM
1900 }
1901
1902 return $userswithsubmission;
6516b9e9
DM
1903 }
1904
aa40adbf
DM
1905 /**
1906 * @return array of available workshop phases
1907 */
365c2cc2 1908 protected function available_phases_list() {
aa40adbf
DM
1909 return array(
1910 self::PHASE_SETUP => true,
1911 self::PHASE_SUBMISSION => true,
1912 self::PHASE_ASSESSMENT => true,
1913 self::PHASE_EVALUATION => true,
1914 self::PHASE_CLOSED => true,
1915 );
1916 }
1917
66c9894d 1918}
55fc1e59
DM
1919
1920/**
1921 * Represents the user planner tool
1922 *
1923 * Planner contains list of phases. Each phase contains list of tasks. Task is a simple object with
1924 * title, link and completed (true/false/null logic).
1925 */
1926class workshop_user_plan implements renderable {
1927
cff28ef0
DM
1928 /** @var int id of the user this plan is for */
1929 public $userid;
55fc1e59
DM
1930 /** @var workshop */
1931 public $workshop;
1932 /** @var array of (stdclass)tasks */
1933 public $phases = array();
cff28ef0
DM
1934 /** @var null|array of example submissions to be assessed by the planner owner */
1935 protected $examples = null;
55fc1e59
DM
1936
1937 /**
1938 * Prepare an individual workshop plan for the given user.
1939 *
1940 * @param workshop $workshop instance
1941 * @param int $userid whom the plan is prepared for
1942 */
1943 public function __construct(workshop $workshop, $userid) {
1944 global $DB;
1945
1946 $this->workshop = $workshop;
cff28ef0 1947 $this->userid = $userid;
55fc1e59 1948
5bab64a3
DM
1949 //---------------------------------------------------------
1950 // * SETUP | submission | assessment | evaluation | closed
1951 //---------------------------------------------------------
55fc1e59
DM
1952 $phase = new stdclass();
1953 $phase->title = get_string('phasesetup', 'workshop');
1954 $phase->tasks = array();
cff28ef0 1955 if (has_capability('moodle/course:manageactivities', $workshop->context, $userid)) {
55fc1e59
DM
1956 $task = new stdclass();
1957 $task->title = get_string('taskintro', 'workshop');
cff28ef0 1958 $task->link = $workshop->updatemod_url();
bfbca63d 1959 $task->completed = !(trim($workshop->intro) == '');
55fc1e59
DM
1960 $phase->tasks['intro'] = $task;
1961 }
cff28ef0 1962 if (has_capability('moodle/course:manageactivities', $workshop->context, $userid)) {
55fc1e59
DM
1963 $task = new stdclass();
1964 $task->title = get_string('taskinstructauthors', 'workshop');
cff28ef0 1965 $task->link = $workshop->updatemod_url();
bfbca63d 1966 $task->completed = !(trim($workshop->instructauthors) == '');
55fc1e59
DM
1967 $phase->tasks['instructauthors'] = $task;
1968 }
cff28ef0 1969 if (has_capability('mod/workshop:editdimensions', $workshop->context, $userid)) {
55fc1e59
DM
1970 $task = new stdclass();
1971 $task->title = get_string('editassessmentform', 'workshop');
cff28ef0
DM
1972 $task->link = $workshop->editform_url();
1973 if ($workshop->grading_strategy_instance()->form_ready()) {
55fc1e59 1974 $task->completed = true;
cff28ef0 1975 } elseif ($workshop->phase > workshop::PHASE_SETUP) {
55fc1e59
DM
1976 $task->completed = false;
1977 }
1978 $phase->tasks['editform'] = $task;
1979 }
cff28ef0 1980 if ($workshop->useexamples and has_capability('mod/workshop:manageexamples', $workshop->context, $userid)) {
55fc1e59
DM
1981 $task = new stdclass();
1982 $task->title = get_string('prepareexamples', 'workshop');
cff28ef0 1983 if ($DB->count_records('workshop_submissions', array('example' => 1, 'workshopid' => $workshop->id)) > 0) {
55fc1e59 1984 $task->completed = true;
cff28ef0 1985 } elseif ($workshop->phase > workshop::PHASE_SETUP) {
55fc1e59
DM
1986 $task->completed = false;
1987 }
1988 $phase->tasks['prepareexamples'] = $task;
1989 }
cff28ef0 1990 if (empty($phase->tasks) and $workshop->phase == workshop::PHASE_SETUP) {
55fc1e59
DM
1991 // if we are in the setup phase and there is no task (typical for students), let us
1992 // display some explanation what is going on
1993 $task = new stdclass();
1994 $task->title = get_string('undersetup', 'workshop');
1995 $task->completed = 'info';
1996 $phase->tasks['setupinfo'] = $task;
1997 }
1998 $this->phases[workshop::PHASE_SETUP] = $phase;
1999
5bab64a3
DM
2000 //---------------------------------------------------------
2001 // setup | * SUBMISSION | assessment | evaluation | closed
2002 //---------------------------------------------------------
55fc1e59
DM
2003 $phase = new stdclass();
2004 $phase->title = get_string('phasesubmission', 'workshop');
2005 $phase->tasks = array();
cff28ef0
DM
2006 if (($workshop->usepeerassessment or $workshop->useselfassessment)
2007 and has_capability('moodle/course:manageactivities', $workshop->context, $userid)) {
55fc1e59
DM
2008 $task = new stdclass();
2009 $task->title = get_string('taskinstructreviewers', 'workshop');
cff28ef0 2010 $task->link = $workshop->updatemod_url();
bfbca63d 2011 if (trim($workshop->instructreviewers)) {
55fc1e59 2012 $task->completed = true;
cff28ef0 2013 } elseif ($workshop->phase >= workshop::PHASE_ASSESSMENT) {
55fc1e59
DM
2014 $task->completed = false;
2015 }
2016 $phase->tasks['instructreviewers'] = $task;
2017 }
cff28ef0 2018 if ($workshop->useexamples and $workshop->examplesmode == workshop::EXAMPLES_BEFORE_SUBMISSION
514d8c22 2019 and has_capability('mod/workshop:submit', $workshop->context, $userid, false)
cff28ef0 2020 and !has_capability('mod/workshop:manageexamples', $workshop->context, $userid)) {
514d8c22
DM
2021 $task = new stdclass();
2022 $task->title = get_string('exampleassesstask', 'workshop');
cff28ef0 2023 $examples = $this->get_examples();
514d8c22
DM
2024 $a = new stdclass();
2025 $a->expected = count($examples);
2026 $a->assessed = 0;
2027 foreach ($examples as $exampleid => $example) {
2028 if (!is_null($example->grade)) {
2029 $a->assessed++;
2030 }
2031 }
2032 $task->details = get_string('exampleassesstaskdetails', 'workshop', $a);
2033 if ($a->assessed == $a->expected) {
2034 $task->completed = true;
cff28ef0 2035 } elseif ($workshop->phase >= workshop::PHASE_ASSESSMENT) {
514d8c22
DM
2036 $task->completed = false;
2037 }
2038 $phase->tasks['examples'] = $task;
2039 }
cff28ef0 2040 if (has_capability('mod/workshop:submit', $workshop->context, $userid, false)) {
55fc1e59
DM
2041 $task = new stdclass();
2042 $task->title = get_string('tasksubmit', 'workshop');
cff28ef0
DM
2043 $task->link = $workshop->submission_url();
2044 if ($DB->record_exists('workshop_submissions', array('workshopid'=>$workshop->id, 'example'=>0, 'authorid'=>$userid))) {
55fc1e59 2045 $task->completed = true;
cff28ef0 2046 } elseif ($workshop->phase >= workshop::PHASE_ASSESSMENT) {
55fc1e59
DM
2047 $task->completed = false;
2048 } else {
2049 $task->completed = null; // still has a chance to submit
2050 }
2051 $phase->tasks['submit'] = $task;
2052 }
cff28ef0 2053 if (has_capability('mod/workshop:allocate', $workshop->context, $userid)) {
55fc1e59
DM
2054 $task = new stdclass();
2055 $task->title = get_string('allocate', 'workshop');
cff28ef0
DM
2056 $task->link = $workshop->allocation_url();
2057 $numofauthors = count(get_users_by_capability($workshop->context, 'mod/workshop:submit', 'u.id', '', '', '',
55fc1e59 2058 '', '', false, true));
cff28ef0 2059 $numofsubmissions = $DB->count_records('workshop_submissions', array('workshopid'=>$workshop->id, 'example'=>0));
55fc1e59
DM
2060 $sql = 'SELECT COUNT(s.id) AS nonallocated
2061 FROM {workshop_submissions} s
2062 LEFT JOIN {workshop_assessments} a ON (a.submissionid=s.id)
2063 WHERE s.workshopid = :workshopid AND s.example=0 AND a.submissionid IS NULL';
cff28ef0 2064 $params['workshopid'] = $workshop->id;
55fc1e59
DM
2065 $numnonallocated = $DB->count_records_sql($sql, $params);
2066 if ($numofsubmissions == 0) {
2067 $task->completed = null;
2068 } elseif ($numnonallocated == 0) {
2069 $task->completed = true;
cff28ef0 2070 } elseif ($workshop->phase > workshop::PHASE_SUBMISSION) {
55fc1e59
DM
2071 $task->completed = false;
2072 } else {
2073 $task->completed = null; // still has a chance to allocate
2074 }
2075 $a = new stdclass();
2076 $a->expected = $numofauthors;
2077 $a->submitted = $numofsubmissions;
2078 $a->allocate = $numnonallocated;
2079 $task->details = get_string('allocatedetails', 'workshop', $a);
2080 unset($a);
2081 $phase->tasks['allocate'] = $task;
2082
cff28ef0 2083 if ($numofsubmissions < $numofauthors and $workshop->phase >= workshop::PHASE_SUBMISSION) {
55fc1e59
DM
2084 $task = new stdclass();
2085 $task->title = get_string('someuserswosubmission', 'workshop');
2086 $task->completed = 'info';
2087 $phase->tasks['allocateinfo'] = $task;
2088 }
2089 }
cff28ef0 2090 if ($workshop->submissionstart) {
5bab64a3 2091 $task = new stdclass();
cff28ef0 2092 $task->title = get_string('submissionstartdatetime', 'workshop', workshop::timestamp_formats($workshop->submissionstart));
5bab64a3
DM
2093 $task->completed = 'info';
2094 $phase->tasks['submissionstartdatetime'] = $task;
2095 }
cff28ef0 2096 if ($workshop->submissionend) {
5bab64a3 2097 $task = new stdclass();
cff28ef0 2098 $task->title = get_string('submissionenddatetime', 'workshop', workshop::timestamp_formats($workshop->submissionend));
5bab64a3
DM
2099 $task->completed = 'info';
2100 $phase->tasks['submissionenddatetime'] = $task;
2101 }
2f289d36
DM
2102 if (($workshop->submissionstart < time()) and $workshop->latesubmissions) {
2103 $task = new stdclass();
2104 $task->title = get_string('latesubmissionsallowed', 'workshop');
2105 $task->completed = 'info';
2106 $phase->tasks['latesubmissionsallowed'] = $task;
2107 }
55fc1e59
DM
2108 $this->phases[workshop::PHASE_SUBMISSION] = $phase;
2109
5bab64a3
DM
2110 //---------------------------------------------------------
2111 // setup | submission | * ASSESSMENT | evaluation | closed
2112 //---------------------------------------------------------
55fc1e59
DM
2113 $phase = new stdclass();
2114 $phase->title = get_string('phaseassessment', 'workshop');
2115 $phase->tasks = array();
cff28ef0
DM
2116 $phase->isreviewer = has_capability('mod/workshop:peerassess', $workshop->context, $userid);
2117 if ($workshop->useexamples and $workshop->examplesmode == workshop::EXAMPLES_BEFORE_ASSESSMENT
2118 and $phase->isreviewer and !has_capability('mod/workshop:manageexamples', $workshop->context, $userid)) {
2119 $task = new stdclass();
2120 $task->title = get_string('exampleassesstask', 'workshop');
2121 $examples = $workshop->get_examples_for_reviewer($userid);
2122 $a = new stdclass();
2123 $a->expected = count($examples);
2124 $a->assessed = 0;
2125 foreach ($examples as $exampleid => $example) {
2126 if (!is_null($example->grade)) {
2127 $a->assessed++;
55fc1e59
DM
2128 }
2129 }
cff28ef0
DM
2130 $task->details = get_string('exampleassesstaskdetails', 'workshop', $a);
2131 if ($a->assessed == $a->expected) {
55fc1e59 2132 $task->completed = true;
cff28ef0 2133 } elseif ($workshop->phase > workshop::PHASE_ASSESSMENT) {
55fc1e59
DM
2134 $task->completed = false;
2135 }
cff28ef0 2136 $phase->tasks['examples'] = $task;
55fc1e59 2137 }
cff28ef0
DM
2138 if (empty($phase->tasks['examples']) or !empty($phase->tasks['examples']->completed)) {
2139 $phase->assessments = $workshop->get_assessments_by_reviewer($userid);
2140 $numofpeers = 0; // number of allocated peer-assessments
2141 $numofpeerstodo = 0; // number of peer-assessments to do
2142 $numofself = 0; // number of allocated self-assessments - should be 0 or 1
2143 $numofselftodo = 0; // number of self-assessments to do - should be 0 or 1
2144 foreach ($phase->assessments as $a) {
2145 if ($a->authorid == $userid) {
2146 $numofself++;
2147 if (is_null($a->grade)) {
2148 $numofselftodo++;
2149 }
2150 } else {
2151 $numofpeers++;
2152 if (is_null($a->grade)) {
2153 $numofpeerstodo++;
2154 }
2155 }
2156 }
2157 unset($a);
2158 if ($workshop->usepeerassessment and $numofpeers) {
2159 $task = new stdclass();
2160 if ($numofpeerstodo == 0) {
2161 $task->completed = true;
2162 } elseif ($workshop->phase > workshop::PHASE_ASSESSMENT) {
2163 $task->completed = false;
2164 }
2165 $a = new stdclass();
2166 $a->total = $numofpeers;
2167 $a->todo = $numofpeerstodo;
2168 $task->title = get_string('taskassesspeers', 'workshop');
2169 $task->details = get_string('taskassesspeersdetails', 'workshop', $a);
2170 unset($a);
2171 $phase->tasks['assesspeers'] = $task;
2172 }
2173 if ($workshop->useselfassessment and $numofself) {
2174 $task = new stdclass();
2175 if ($numofselftodo == 0) {
2176 $task->completed = true;
2177 } elseif ($workshop->phase > workshop::PHASE_ASSESSMENT) {
2178 $task->completed = false;
2179 }
2180 $task->title = get_string('taskassessself', 'workshop');
2181 $phase->tasks['assessself'] = $task;
55fc1e59 2182 }
55fc1e59 2183 }
cff28ef0 2184 if ($workshop->assessmentstart) {
5bab64a3 2185 $task = new stdclass();
cff28ef0 2186 $task->title = get_string('assessmentstartdatetime', 'workshop', workshop::timestamp_formats($workshop->assessmentstart));
5bab64a3
DM
2187 $task->completed = 'info';
2188 $phase->tasks['assessmentstartdatetime'] = $task;
2189 }
cff28ef0 2190 if ($workshop->assessmentend) {
5bab64a3 2191 $task = new stdclass();
cff28ef0 2192 $task->title = get_string('assessmentenddatetime', 'workshop', workshop::timestamp_formats($workshop->assessmentend));
5bab64a3
DM
2193 $task->completed = 'info';
2194 $phase->tasks['assessmentenddatetime'] = $task;
2195 }
55fc1e59
DM
2196 $this->phases[workshop::PHASE_ASSESSMENT] = $phase;
2197
5bab64a3
DM
2198 //---------------------------------------------------------
2199 // setup | submission | assessment | * EVALUATION | closed
2200 //---------------------------------------------------------
55fc1e59
DM
2201 $phase = new stdclass();
2202 $phase->title = get_string('phaseevaluation', 'workshop');
2203 $phase->tasks = array();
cff28ef0
DM
2204 if (has_capability('mod/workshop:overridegrades', $workshop->context)) {
2205 $expected = count($workshop->get_potential_authors(false));
55fc1e59 2206 $calculated = $DB->count_records_select('workshop_submissions',
cff28ef0 2207 'workshopid = ? AND (grade IS NOT NULL OR gradeover IS NOT NULL)', array($workshop->id));
55fc1e59
DM
2208 $task = new stdclass();
2209 $task->title = get_string('calculatesubmissiongrades', 'workshop');
2210 $a = new stdclass();
2211 $a->expected = $expected;
2212 $a->calculated = $calculated;
2213 $task->details = get_string('calculatesubmissiongradesdetails', 'workshop', $a);
2214 if ($calculated >= $expected) {
2215 $task->completed = true;
cff28ef0 2216 } elseif ($workshop->phase > workshop::PHASE_EVALUATION) {
55fc1e59
DM
2217 $task->completed = false;
2218 }
2219 $phase->tasks['calculatesubmissiongrade'] = $task;
2220
cff28ef0 2221 $expected = count($workshop->get_potential_reviewers(false));
55fc1e59 2222 $calculated = $DB->count_records_select('workshop_aggregations',
cff28ef0 2223 'workshopid = ? AND gradinggrade IS NOT NULL', array($workshop->id));
55fc1e59
DM
2224 $task = new stdclass();
2225 $task->title = get_string('calculategradinggrades', 'workshop');
2226 $a = new stdclass();
2227 $a->expected = $expected;
2228 $a->calculated = $calculated;
2229 $task->details = get_string('calculategradinggradesdetails', 'workshop', $a);
2230 if ($calculated >= $expected) {
2231 $task->completed = true;
cff28ef0 2232 } elseif ($workshop->phase > workshop::PHASE_EVALUATION) {
55fc1e59
DM
2233 $task->completed = false;
2234 }
2235 $phase->tasks['calculategradinggrade'] = $task;
2236
cff28ef0 2237 } elseif ($workshop->phase == workshop::PHASE_EVALUATION) {
55fc1e59
DM
2238 $task = new stdclass();
2239 $task->title = get_string('evaluategradeswait', 'workshop');
2240 $task->completed = 'info';
2241 $phase->tasks['evaluateinfo'] = $task;
2242 }
2243 $this->phases[workshop::PHASE_EVALUATION] = $phase;
2244
5bab64a3
DM
2245 //---------------------------------------------------------
2246 // setup | submission | assessment | evaluation | * CLOSED
2247 //---------------------------------------------------------
55fc1e59
DM
2248 $phase = new stdclass();
2249 $phase->title = get_string('phaseclosed', 'workshop');
2250 $phase->tasks = array();
2251 $this->phases[workshop::PHASE_CLOSED] = $phase;
2252
2253 // Polish data, set default values if not done explicitly
2254 foreach ($this->phases as $phasecode => $phase) {
2255 $phase->title = isset($phase->title) ? $phase->title : '';
2256 $phase->tasks = isset($phase->tasks) ? $phase->tasks : array();
cff28ef0 2257 if ($phasecode == $workshop->phase) {
55fc1e59
DM
2258 $phase->active = true;
2259 } else {
2260 $phase->active = false;
2261 }
2262 if (!isset($phase->actions)) {
2263 $phase->actions = array();
2264 }
2265
2266 foreach ($phase->tasks as $taskcode => $task) {
2267 $task->title = isset($task->title) ? $task->title : '';
2268 $task->link = isset($task->link) ? $task->link : null;
2269 $task->details = isset($task->details) ? $task->details : '';
2270 $task->completed = isset($task->completed) ? $task->completed : null;
2271 }
2272 }
2273
5924db72 2274 // Add phase switching actions
cff28ef0 2275 if (has_capability('mod/workshop:switchphase', $workshop->context, $userid)) {
55fc1e59
DM
2276 foreach ($this->phases as $phasecode => $phase) {
2277 if (! $phase->active) {
2278 $action = new stdclass();
2279 $action->type = 'switchphase';
cff28ef0 2280 $action->url = $workshop->switchphase_url($phasecode);
55fc1e59
DM
2281 $phase->actions[] = $action;
2282 }
2283 }
2284 }
2285 }
cff28ef0
DM
2286
2287 /**
2288 * Returns example submissions to be assessed by the owner of the planner
2289 *
2290 * This is here to cache the DB query because the same list is needed later in view.php
2291 *
2292 * @see workshop::get_examples_for_reviewer() for the format of returned value
2293 * @return array
2294 */
2295 public function get_examples() {
2296 if (is_null($this->examples)) {
2297 $this->examples = $this->workshop->get_examples_for_reviewer($this->userid);
2298 }
2299 return $this->examples;
2300 }
55fc1e59 2301}