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