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