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