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