Merge branch 'MDL-42411-master' of git://github.com/damyon/moodle
[moodle.git] / mod / workshop / locallib.php
blob04a389c1a8aaa4f359308364419a542bd06ee3e2
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
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.
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/>.
18 /**
19 * Library of internal classes and functions for module workshop
21 * All the workshop specific functions, needed to implement the module
22 * logic, should go to here. Instead of having bunch of function named
23 * workshop_something() taking the workshop instance as the first
24 * parameter, we use a class workshop that provides all methods.
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
32 defined('MOODLE_INTERNAL') || die();
34 require_once(dirname(__FILE__).'/lib.php'); // we extend this library here
35 require_once($CFG->libdir . '/gradelib.php'); // we use some rounding and comparing routines here
36 require_once($CFG->libdir . '/filelib.php');
38 /**
39 * Full-featured workshop API
41 * This wraps the workshop database record with a set of methods that are called
42 * from the module itself. The class should be initialized right after you get
43 * $workshop, $cm and $course records at the begining of the script.
45 class workshop {
47 /** error status of the {@link self::add_allocation()} */
48 const ALLOCATION_EXISTS = -9999;
50 /** the internal code of the workshop phases as are stored in the database */
51 const PHASE_SETUP = 10;
52 const PHASE_SUBMISSION = 20;
53 const PHASE_ASSESSMENT = 30;
54 const PHASE_EVALUATION = 40;
55 const PHASE_CLOSED = 50;
57 /** the internal code of the examples modes as are stored in the database */
58 const EXAMPLES_VOLUNTARY = 0;
59 const EXAMPLES_BEFORE_SUBMISSION = 1;
60 const EXAMPLES_BEFORE_ASSESSMENT = 2;
62 /** @var stdclass course module record */
63 public $cm;
65 /** @var stdclass course record */
66 public $course;
68 /** @var stdclass context object */
69 public $context;
71 /** @var int workshop instance identifier */
72 public $id;
74 /** @var string workshop activity name */
75 public $name;
77 /** @var string introduction or description of the activity */
78 public $intro;
80 /** @var int format of the {@link $intro} */
81 public $introformat;
83 /** @var string instructions for the submission phase */
84 public $instructauthors;
86 /** @var int format of the {@link $instructauthors} */
87 public $instructauthorsformat;
89 /** @var string instructions for the assessment phase */
90 public $instructreviewers;
92 /** @var int format of the {@link $instructreviewers} */
93 public $instructreviewersformat;
95 /** @var int timestamp of when the module was modified */
96 public $timemodified;
98 /** @var int current phase of workshop, for example {@link workshop::PHASE_SETUP} */
99 public $phase;
101 /** @var bool optional feature: students practise evaluating on example submissions from teacher */
102 public $useexamples;
104 /** @var bool optional feature: students perform peer assessment of others' work (deprecated, consider always enabled) */
105 public $usepeerassessment;
107 /** @var bool optional feature: students perform self assessment of their own work */
108 public $useselfassessment;
110 /** @var float number (10, 5) unsigned, the maximum grade for submission */
111 public $grade;
113 /** @var float number (10, 5) unsigned, the maximum grade for assessment */
114 public $gradinggrade;
116 /** @var string type of the current grading strategy used in this workshop, for example 'accumulative' */
117 public $strategy;
119 /** @var string the name of the evaluation plugin to use for grading grades calculation */
120 public $evaluation;
122 /** @var int number of digits that should be shown after the decimal point when displaying grades */
123 public $gradedecimals;
125 /** @var int number of allowed submission attachments and the files embedded into submission */
126 public $nattachments;
128 /** @var bool allow submitting the work after the deadline */
129 public $latesubmissions;
131 /** @var int maximum size of the one attached file in bytes */
132 public $maxbytes;
134 /** @var int mode of example submissions support, for example {@link workshop::EXAMPLES_VOLUNTARY} */
135 public $examplesmode;
137 /** @var int if greater than 0 then the submission is not allowed before this timestamp */
138 public $submissionstart;
140 /** @var int if greater than 0 then the submission is not allowed after this timestamp */
141 public $submissionend;
143 /** @var int if greater than 0 then the peer assessment is not allowed before this timestamp */
144 public $assessmentstart;
146 /** @var int if greater than 0 then the peer assessment is not allowed after this timestamp */
147 public $assessmentend;
149 /** @var bool automatically switch to the assessment phase after the submissions deadline */
150 public $phaseswitchassessment;
152 /** @var string conclusion text to be displayed at the end of the activity */
153 public $conclusion;
155 /** @var int format of the conclusion text */
156 public $conclusionformat;
158 /** @var int the mode of the overall feedback */
159 public $overallfeedbackmode;
161 /** @var int maximum number of overall feedback attachments */
162 public $overallfeedbackfiles;
164 /** @var int maximum size of one file attached to the overall feedback */
165 public $overallfeedbackmaxbytes;
168 * @var workshop_strategy grading strategy instance
169 * Do not use directly, get the instance using {@link workshop::grading_strategy_instance()}
171 protected $strategyinstance = null;
174 * @var workshop_evaluation grading evaluation instance
175 * Do not use directly, get the instance using {@link workshop::grading_evaluation_instance()}
177 protected $evaluationinstance = null;
180 * Initializes the workshop API instance using the data from DB
182 * Makes deep copy of all passed records properties. Replaces integer $course attribute
183 * with a full database record (course should not be stored in instances table anyway).
185 * @param stdClass $dbrecord Workshop instance data from {workshop} table
186 * @param stdClass $cm Course module record as returned by {@link get_coursemodule_from_id()}
187 * @param stdClass $course Course record from {course} table
188 * @param stdClass $context The context of the workshop instance
190 public function __construct(stdclass $dbrecord, stdclass $cm, stdclass $course, stdclass $context=null) {
191 foreach ($dbrecord as $field => $value) {
192 if (property_exists('workshop', $field)) {
193 $this->{$field} = $value;
196 $this->cm = $cm;
197 $this->course = $course;
198 if (is_null($context)) {
199 $this->context = context_module::instance($this->cm->id);
200 } else {
201 $this->context = $context;
205 ////////////////////////////////////////////////////////////////////////////////
206 // Static methods //
207 ////////////////////////////////////////////////////////////////////////////////
210 * Return list of available allocation methods
212 * @return array Array ['string' => 'string'] of localized allocation method names
214 public static function installed_allocators() {
215 $installed = core_component::get_plugin_list('workshopallocation');
216 $forms = array();
217 foreach ($installed as $allocation => $allocationpath) {
218 if (file_exists($allocationpath . '/lib.php')) {
219 $forms[$allocation] = get_string('pluginname', 'workshopallocation_' . $allocation);
222 // usability - make sure that manual allocation appears the first
223 if (isset($forms['manual'])) {
224 $m = array('manual' => $forms['manual']);
225 unset($forms['manual']);
226 $forms = array_merge($m, $forms);
228 return $forms;
232 * Returns an array of options for the editors that are used for submitting and assessing instructions
234 * @param stdClass $context
235 * @uses EDITOR_UNLIMITED_FILES hard-coded value for the 'maxfiles' option
236 * @return array
238 public static function instruction_editors_options(stdclass $context) {
239 return array('subdirs' => 1, 'maxbytes' => 0, 'maxfiles' => -1,
240 'changeformat' => 1, 'context' => $context, 'noclean' => 1, 'trusttext' => 0);
244 * Given the percent and the total, returns the number
246 * @param float $percent from 0 to 100
247 * @param float $total the 100% value
248 * @return float
250 public static function percent_to_value($percent, $total) {
251 if ($percent < 0 or $percent > 100) {
252 throw new coding_exception('The percent can not be less than 0 or higher than 100');
255 return $total * $percent / 100;
259 * Returns an array of numeric values that can be used as maximum grades
261 * @return array Array of integers
263 public static function available_maxgrades_list() {
264 $grades = array();
265 for ($i=100; $i>=0; $i--) {
266 $grades[$i] = $i;
268 return $grades;
272 * Returns the localized list of supported examples modes
274 * @return array
276 public static function available_example_modes_list() {
277 $options = array();
278 $options[self::EXAMPLES_VOLUNTARY] = get_string('examplesvoluntary', 'workshop');
279 $options[self::EXAMPLES_BEFORE_SUBMISSION] = get_string('examplesbeforesubmission', 'workshop');
280 $options[self::EXAMPLES_BEFORE_ASSESSMENT] = get_string('examplesbeforeassessment', 'workshop');
281 return $options;
285 * Returns the list of available grading strategy methods
287 * @return array ['string' => 'string']
289 public static function available_strategies_list() {
290 $installed = core_component::get_plugin_list('workshopform');
291 $forms = array();
292 foreach ($installed as $strategy => $strategypath) {
293 if (file_exists($strategypath . '/lib.php')) {
294 $forms[$strategy] = get_string('pluginname', 'workshopform_' . $strategy);
297 return $forms;
301 * Returns the list of available grading evaluation methods
303 * @return array of (string)name => (string)localized title
305 public static function available_evaluators_list() {
306 $evals = array();
307 foreach (core_component::get_plugin_list_with_file('workshopeval', 'lib.php', false) as $eval => $evalpath) {
308 $evals[$eval] = get_string('pluginname', 'workshopeval_' . $eval);
310 return $evals;
314 * Return an array of possible values of assessment dimension weight
316 * @return array of integers 0, 1, 2, ..., 16
318 public static function available_dimension_weights_list() {
319 $weights = array();
320 for ($i=16; $i>=0; $i--) {
321 $weights[$i] = $i;
323 return $weights;
327 * Return an array of possible values of assessment weight
329 * Note there is no real reason why the maximum value here is 16. It used to be 10 in
330 * workshop 1.x and I just decided to use the same number as in the maximum weight of
331 * a single assessment dimension.
332 * The value looks reasonable, though. Teachers who would want to assign themselves
333 * higher weight probably do not want peer assessment really...
335 * @return array of integers 0, 1, 2, ..., 16
337 public static function available_assessment_weights_list() {
338 $weights = array();
339 for ($i=16; $i>=0; $i--) {
340 $weights[$i] = $i;
342 return $weights;
346 * Helper function returning the greatest common divisor
348 * @param int $a
349 * @param int $b
350 * @return int
352 public static function gcd($a, $b) {
353 return ($b == 0) ? ($a):(self::gcd($b, $a % $b));
357 * Helper function returning the least common multiple
359 * @param int $a
360 * @param int $b
361 * @return int
363 public static function lcm($a, $b) {
364 return ($a / self::gcd($a,$b)) * $b;
368 * Returns an object suitable for strings containing dates/times
370 * The returned object contains properties date, datefullshort, datetime, ... containing the given
371 * timestamp formatted using strftimedate, strftimedatefullshort, strftimedatetime, ... from the
372 * current lang's langconfig.php
373 * This allows translators and administrators customize the date/time format.
375 * @param int $timestamp the timestamp in UTC
376 * @return stdclass
378 public static function timestamp_formats($timestamp) {
379 $formats = array('date', 'datefullshort', 'dateshort', 'datetime',
380 'datetimeshort', 'daydate', 'daydatetime', 'dayshort', 'daytime',
381 'monthyear', 'recent', 'recentfull', 'time');
382 $a = new stdclass();
383 foreach ($formats as $format) {
384 $a->{$format} = userdate($timestamp, get_string('strftime'.$format, 'langconfig'));
386 $day = userdate($timestamp, '%Y%m%d', 99, false);
387 $today = userdate(time(), '%Y%m%d', 99, false);
388 $tomorrow = userdate(time() + DAYSECS, '%Y%m%d', 99, false);
389 $yesterday = userdate(time() - DAYSECS, '%Y%m%d', 99, false);
390 $distance = (int)round(abs(time() - $timestamp) / DAYSECS);
391 if ($day == $today) {
392 $a->distanceday = get_string('daystoday', 'workshop');
393 } elseif ($day == $yesterday) {
394 $a->distanceday = get_string('daysyesterday', 'workshop');
395 } elseif ($day < $today) {
396 $a->distanceday = get_string('daysago', 'workshop', $distance);
397 } elseif ($day == $tomorrow) {
398 $a->distanceday = get_string('daystomorrow', 'workshop');
399 } elseif ($day > $today) {
400 $a->distanceday = get_string('daysleft', 'workshop', $distance);
402 return $a;
405 ////////////////////////////////////////////////////////////////////////////////
406 // Workshop API //
407 ////////////////////////////////////////////////////////////////////////////////
410 * Fetches all enrolled users with the capability mod/workshop:submit in the current workshop
412 * The returned objects contain properties required by user_picture and are ordered by lastname, firstname.
413 * Only users with the active enrolment are returned.
415 * @param bool $musthavesubmission if true, return only users who have already submitted
416 * @param int $groupid 0 means ignore groups, any other value limits the result by group id
417 * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set)
418 * @param int $limitnum return a subset containing this number of records (optional, required if $limitfrom is set)
419 * @return array array[userid] => stdClass
421 public function get_potential_authors($musthavesubmission=true, $groupid=0, $limitfrom=0, $limitnum=0) {
422 global $DB;
424 list($sql, $params) = $this->get_users_with_capability_sql('mod/workshop:submit', $musthavesubmission, $groupid);
426 if (empty($sql)) {
427 return array();
430 list($sort, $sortparams) = users_order_by_sql('tmp');
431 $sql = "SELECT *
432 FROM ($sql) tmp
433 ORDER BY $sort";
435 return $DB->get_records_sql($sql, array_merge($params, $sortparams), $limitfrom, $limitnum);
439 * Returns the total number of users that would be fetched by {@link self::get_potential_authors()}
441 * @param bool $musthavesubmission if true, count only users who have already submitted
442 * @param int $groupid 0 means ignore groups, any other value limits the result by group id
443 * @return int
445 public function count_potential_authors($musthavesubmission=true, $groupid=0) {
446 global $DB;
448 list($sql, $params) = $this->get_users_with_capability_sql('mod/workshop:submit', $musthavesubmission, $groupid);
450 if (empty($sql)) {
451 return 0;
454 $sql = "SELECT COUNT(*)
455 FROM ($sql) tmp";
457 return $DB->count_records_sql($sql, $params);
461 * Fetches all enrolled users with the capability mod/workshop:peerassess in the current workshop
463 * The returned objects contain properties required by user_picture and are ordered by lastname, firstname.
464 * Only users with the active enrolment are returned.
466 * @param bool $musthavesubmission if true, return only users who have already submitted
467 * @param int $groupid 0 means ignore groups, any other value limits the result by group id
468 * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set)
469 * @param int $limitnum return a subset containing this number of records (optional, required if $limitfrom is set)
470 * @return array array[userid] => stdClass
472 public function get_potential_reviewers($musthavesubmission=false, $groupid=0, $limitfrom=0, $limitnum=0) {
473 global $DB;
475 list($sql, $params) = $this->get_users_with_capability_sql('mod/workshop:peerassess', $musthavesubmission, $groupid);
477 if (empty($sql)) {
478 return array();
481 list($sort, $sortparams) = users_order_by_sql('tmp');
482 $sql = "SELECT *
483 FROM ($sql) tmp
484 ORDER BY $sort";
486 return $DB->get_records_sql($sql, array_merge($params, $sortparams), $limitfrom, $limitnum);
490 * Returns the total number of users that would be fetched by {@link self::get_potential_reviewers()}
492 * @param bool $musthavesubmission if true, count only users who have already submitted
493 * @param int $groupid 0 means ignore groups, any other value limits the result by group id
494 * @return int
496 public function count_potential_reviewers($musthavesubmission=false, $groupid=0) {
497 global $DB;
499 list($sql, $params) = $this->get_users_with_capability_sql('mod/workshop:peerassess', $musthavesubmission, $groupid);
501 if (empty($sql)) {
502 return 0;
505 $sql = "SELECT COUNT(*)
506 FROM ($sql) tmp";
508 return $DB->count_records_sql($sql, $params);
512 * Fetches all enrolled users that are authors or reviewers (or both) in the current workshop
514 * The returned objects contain properties required by user_picture and are ordered by lastname, firstname.
515 * Only users with the active enrolment are returned.
517 * @see self::get_potential_authors()
518 * @see self::get_potential_reviewers()
519 * @param bool $musthavesubmission if true, return only users who have already submitted
520 * @param int $groupid 0 means ignore groups, any other value limits the result by group id
521 * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set)
522 * @param int $limitnum return a subset containing this number of records (optional, required if $limitfrom is set)
523 * @return array array[userid] => stdClass
525 public function get_participants($musthavesubmission=false, $groupid=0, $limitfrom=0, $limitnum=0) {
526 global $DB;
528 list($sql, $params) = $this->get_participants_sql($musthavesubmission, $groupid);
530 if (empty($sql)) {
531 return array();
534 list($sort, $sortparams) = users_order_by_sql('tmp');
535 $sql = "SELECT *
536 FROM ($sql) tmp
537 ORDER BY $sort";
539 return $DB->get_records_sql($sql, array_merge($params, $sortparams), $limitfrom, $limitnum);
543 * Returns the total number of records that would be returned by {@link self::get_participants()}
545 * @param bool $musthavesubmission if true, return only users who have already submitted
546 * @param int $groupid 0 means ignore groups, any other value limits the result by group id
547 * @return int
549 public function count_participants($musthavesubmission=false, $groupid=0) {
550 global $DB;
552 list($sql, $params) = $this->get_participants_sql($musthavesubmission, $groupid);
554 if (empty($sql)) {
555 return 0;
558 $sql = "SELECT COUNT(*)
559 FROM ($sql) tmp";
561 return $DB->count_records_sql($sql, $params);
565 * Checks if the given user is an actively enrolled participant in the workshop
567 * @param int $userid, defaults to the current $USER
568 * @return boolean
570 public function is_participant($userid=null) {
571 global $USER, $DB;
573 if (is_null($userid)) {
574 $userid = $USER->id;
577 list($sql, $params) = $this->get_participants_sql();
579 if (empty($sql)) {
580 return false;
583 $sql = "SELECT COUNT(*)
584 FROM {user} uxx
585 JOIN ({$sql}) pxx ON uxx.id = pxx.id
586 WHERE uxx.id = :uxxid";
587 $params['uxxid'] = $userid;
589 if ($DB->count_records_sql($sql, $params)) {
590 return true;
593 return false;
597 * Groups the given users by the group membership
599 * This takes the module grouping settings into account. If "Available for group members only"
600 * is set, returns only groups withing the course module grouping. Always returns group [0] with
601 * all the given users.
603 * @param array $users array[userid] => stdclass{->id ->lastname ->firstname}
604 * @return array array[groupid][userid] => stdclass{->id ->lastname ->firstname}
606 public function get_grouped($users) {
607 global $DB;
608 global $CFG;
610 $grouped = array(); // grouped users to be returned
611 if (empty($users)) {
612 return $grouped;
614 if (!empty($CFG->enablegroupmembersonly) and $this->cm->groupmembersonly) {
615 // Available for group members only - the workshop is available only
616 // to users assigned to groups within the selected grouping, or to
617 // any group if no grouping is selected.
618 $groupingid = $this->cm->groupingid;
619 // All users that are members of at least one group will be
620 // added into a virtual group id 0
621 $grouped[0] = array();
622 } else {
623 $groupingid = 0;
624 // there is no need to be member of a group so $grouped[0] will contain
625 // all users
626 $grouped[0] = $users;
628 $gmemberships = groups_get_all_groups($this->cm->course, array_keys($users), $groupingid,
629 'gm.id,gm.groupid,gm.userid');
630 foreach ($gmemberships as $gmembership) {
631 if (!isset($grouped[$gmembership->groupid])) {
632 $grouped[$gmembership->groupid] = array();
634 $grouped[$gmembership->groupid][$gmembership->userid] = $users[$gmembership->userid];
635 $grouped[0][$gmembership->userid] = $users[$gmembership->userid];
637 return $grouped;
641 * Returns the list of all allocations (i.e. assigned assessments) in the workshop
643 * Assessments of example submissions are ignored
645 * @return array
647 public function get_allocations() {
648 global $DB;
650 $sql = 'SELECT a.id, a.submissionid, a.reviewerid, s.authorid
651 FROM {workshop_assessments} a
652 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
653 WHERE s.example = 0 AND s.workshopid = :workshopid';
654 $params = array('workshopid' => $this->id);
656 return $DB->get_records_sql($sql, $params);
660 * Returns the total number of records that would be returned by {@link self::get_submissions()}
662 * @param mixed $authorid int|array|'all' If set to [array of] integer, return submission[s] of the given user[s] only
663 * @param int $groupid If non-zero, return only submissions by authors in the specified group
664 * @return int number of records
666 public function count_submissions($authorid='all', $groupid=0) {
667 global $DB;
669 $params = array('workshopid' => $this->id);
670 $sql = "SELECT COUNT(s.id)
671 FROM {workshop_submissions} s
672 JOIN {user} u ON (s.authorid = u.id)";
673 if ($groupid) {
674 $sql .= " JOIN {groups_members} gm ON (gm.userid = u.id AND gm.groupid = :groupid)";
675 $params['groupid'] = $groupid;
677 $sql .= " WHERE s.example = 0 AND s.workshopid = :workshopid";
679 if ('all' === $authorid) {
680 // no additional conditions
681 } elseif (!empty($authorid)) {
682 list($usql, $uparams) = $DB->get_in_or_equal($authorid, SQL_PARAMS_NAMED);
683 $sql .= " AND authorid $usql";
684 $params = array_merge($params, $uparams);
685 } else {
686 // $authorid is empty
687 return 0;
690 return $DB->count_records_sql($sql, $params);
695 * Returns submissions from this workshop
697 * Fetches data from {workshop_submissions} and adds some useful information from other
698 * tables. Does not return textual fields to prevent possible memory lack issues.
700 * @see self::count_submissions()
701 * @param mixed $authorid int|array|'all' If set to [array of] integer, return submission[s] of the given user[s] only
702 * @param int $groupid If non-zero, return only submissions by authors in the specified group
703 * @param int $limitfrom Return a subset of records, starting at this point (optional)
704 * @param int $limitnum Return a subset containing this many records in total (optional, required if $limitfrom is set)
705 * @return array of records or an empty array
707 public function get_submissions($authorid='all', $groupid=0, $limitfrom=0, $limitnum=0) {
708 global $DB;
710 $authorfields = user_picture::fields('u', null, 'authoridx', 'author');
711 $gradeoverbyfields = user_picture::fields('t', null, 'gradeoverbyx', 'over');
712 $params = array('workshopid' => $this->id);
713 $sql = "SELECT s.id, s.workshopid, s.example, s.authorid, s.timecreated, s.timemodified,
714 s.title, s.grade, s.gradeover, s.gradeoverby, s.published,
715 $authorfields, $gradeoverbyfields
716 FROM {workshop_submissions} s
717 JOIN {user} u ON (s.authorid = u.id)";
718 if ($groupid) {
719 $sql .= " JOIN {groups_members} gm ON (gm.userid = u.id AND gm.groupid = :groupid)";
720 $params['groupid'] = $groupid;
722 $sql .= " LEFT JOIN {user} t ON (s.gradeoverby = t.id)
723 WHERE s.example = 0 AND s.workshopid = :workshopid";
725 if ('all' === $authorid) {
726 // no additional conditions
727 } elseif (!empty($authorid)) {
728 list($usql, $uparams) = $DB->get_in_or_equal($authorid, SQL_PARAMS_NAMED);
729 $sql .= " AND authorid $usql";
730 $params = array_merge($params, $uparams);
731 } else {
732 // $authorid is empty
733 return array();
735 list($sort, $sortparams) = users_order_by_sql('u');
736 $sql .= " ORDER BY $sort";
738 return $DB->get_records_sql($sql, array_merge($params, $sortparams), $limitfrom, $limitnum);
742 * Returns a submission record with the author's data
744 * @param int $id submission id
745 * @return stdclass
747 public function get_submission_by_id($id) {
748 global $DB;
750 // we intentionally check the workshopid here, too, so the workshop can't touch submissions
751 // from other instances
752 $authorfields = user_picture::fields('u', null, 'authoridx', 'author');
753 $gradeoverbyfields = user_picture::fields('g', null, 'gradeoverbyx', 'gradeoverby');
754 $sql = "SELECT s.*, $authorfields, $gradeoverbyfields
755 FROM {workshop_submissions} s
756 INNER JOIN {user} u ON (s.authorid = u.id)
757 LEFT JOIN {user} g ON (s.gradeoverby = g.id)
758 WHERE s.example = 0 AND s.workshopid = :workshopid AND s.id = :id";
759 $params = array('workshopid' => $this->id, 'id' => $id);
760 return $DB->get_record_sql($sql, $params, MUST_EXIST);
764 * Returns a submission submitted by the given author
766 * @param int $id author id
767 * @return stdclass|false
769 public function get_submission_by_author($authorid) {
770 global $DB;
772 if (empty($authorid)) {
773 return false;
775 $authorfields = user_picture::fields('u', null, 'authoridx', 'author');
776 $gradeoverbyfields = user_picture::fields('g', null, 'gradeoverbyx', 'gradeoverby');
777 $sql = "SELECT s.*, $authorfields, $gradeoverbyfields
778 FROM {workshop_submissions} s
779 INNER JOIN {user} u ON (s.authorid = u.id)
780 LEFT JOIN {user} g ON (s.gradeoverby = g.id)
781 WHERE s.example = 0 AND s.workshopid = :workshopid AND s.authorid = :authorid";
782 $params = array('workshopid' => $this->id, 'authorid' => $authorid);
783 return $DB->get_record_sql($sql, $params);
787 * Returns published submissions with their authors data
789 * @return array of stdclass
791 public function get_published_submissions($orderby='finalgrade DESC') {
792 global $DB;
794 $authorfields = user_picture::fields('u', null, 'authoridx', 'author');
795 $sql = "SELECT s.id, s.authorid, s.timecreated, s.timemodified,
796 s.title, s.grade, s.gradeover, COALESCE(s.gradeover,s.grade) AS finalgrade,
797 $authorfields
798 FROM {workshop_submissions} s
799 INNER JOIN {user} u ON (s.authorid = u.id)
800 WHERE s.example = 0 AND s.workshopid = :workshopid AND s.published = 1
801 ORDER BY $orderby";
802 $params = array('workshopid' => $this->id);
803 return $DB->get_records_sql($sql, $params);
807 * Returns full record of the given example submission
809 * @param int $id example submission od
810 * @return object
812 public function get_example_by_id($id) {
813 global $DB;
814 return $DB->get_record('workshop_submissions',
815 array('id' => $id, 'workshopid' => $this->id, 'example' => 1), '*', MUST_EXIST);
819 * Returns the list of example submissions in this workshop with reference assessments attached
821 * @return array of objects or an empty array
822 * @see workshop::prepare_example_summary()
824 public function get_examples_for_manager() {
825 global $DB;
827 $sql = 'SELECT s.id, s.title,
828 a.id AS assessmentid, a.grade, a.gradinggrade
829 FROM {workshop_submissions} s
830 LEFT JOIN {workshop_assessments} a ON (a.submissionid = s.id AND a.weight = 1)
831 WHERE s.example = 1 AND s.workshopid = :workshopid
832 ORDER BY s.title';
833 return $DB->get_records_sql($sql, array('workshopid' => $this->id));
837 * Returns the list of all example submissions in this workshop with the information of assessments done by the given user
839 * @param int $reviewerid user id
840 * @return array of objects, indexed by example submission id
841 * @see workshop::prepare_example_summary()
843 public function get_examples_for_reviewer($reviewerid) {
844 global $DB;
846 if (empty($reviewerid)) {
847 return false;
849 $sql = 'SELECT s.id, s.title,
850 a.id AS assessmentid, a.grade, a.gradinggrade
851 FROM {workshop_submissions} s
852 LEFT JOIN {workshop_assessments} a ON (a.submissionid = s.id AND a.reviewerid = :reviewerid AND a.weight = 0)
853 WHERE s.example = 1 AND s.workshopid = :workshopid
854 ORDER BY s.title';
855 return $DB->get_records_sql($sql, array('workshopid' => $this->id, 'reviewerid' => $reviewerid));
859 * Prepares renderable submission component
861 * @param stdClass $record required by {@see workshop_submission}
862 * @param bool $showauthor show the author-related information
863 * @return workshop_submission
865 public function prepare_submission(stdClass $record, $showauthor = false) {
867 $submission = new workshop_submission($this, $record, $showauthor);
868 $submission->url = $this->submission_url($record->id);
870 return $submission;
874 * Prepares renderable submission summary component
876 * @param stdClass $record required by {@see workshop_submission_summary}
877 * @param bool $showauthor show the author-related information
878 * @return workshop_submission_summary
880 public function prepare_submission_summary(stdClass $record, $showauthor = false) {
882 $summary = new workshop_submission_summary($this, $record, $showauthor);
883 $summary->url = $this->submission_url($record->id);
885 return $summary;
889 * Prepares renderable example submission component
891 * @param stdClass $record required by {@see workshop_example_submission}
892 * @return workshop_example_submission
894 public function prepare_example_submission(stdClass $record) {
896 $example = new workshop_example_submission($this, $record);
898 return $example;
902 * Prepares renderable example submission summary component
904 * If the example is editable, the caller must set the 'editable' flag explicitly.
906 * @param stdClass $example as returned by {@link workshop::get_examples_for_manager()} or {@link workshop::get_examples_for_reviewer()}
907 * @return workshop_example_submission_summary to be rendered
909 public function prepare_example_summary(stdClass $example) {
911 $summary = new workshop_example_submission_summary($this, $example);
913 if (is_null($example->grade)) {
914 $summary->status = 'notgraded';
915 $summary->assesslabel = get_string('assess', 'workshop');
916 } else {
917 $summary->status = 'graded';
918 $summary->assesslabel = get_string('reassess', 'workshop');
921 $summary->gradeinfo = new stdclass();
922 $summary->gradeinfo->received = $this->real_grade($example->grade);
923 $summary->gradeinfo->max = $this->real_grade(100);
925 $summary->url = new moodle_url($this->exsubmission_url($example->id));
926 $summary->editurl = new moodle_url($this->exsubmission_url($example->id), array('edit' => 'on'));
927 $summary->assessurl = new moodle_url($this->exsubmission_url($example->id), array('assess' => 'on', 'sesskey' => sesskey()));
929 return $summary;
933 * Prepares renderable assessment component
935 * The $options array supports the following keys:
936 * showauthor - should the author user info be available for the renderer
937 * showreviewer - should the reviewer user info be available for the renderer
938 * showform - show the assessment form if it is available
939 * showweight - should the assessment weight be available for the renderer
941 * @param stdClass $record as returned by eg {@link self::get_assessment_by_id()}
942 * @param workshop_assessment_form|null $form as returned by {@link workshop_strategy::get_assessment_form()}
943 * @param array $options
944 * @return workshop_assessment
946 public function prepare_assessment(stdClass $record, $form, array $options = array()) {
948 $assessment = new workshop_assessment($this, $record, $options);
949 $assessment->url = $this->assess_url($record->id);
950 $assessment->maxgrade = $this->real_grade(100);
952 if (!empty($options['showform']) and !($form instanceof workshop_assessment_form)) {
953 debugging('Not a valid instance of workshop_assessment_form supplied', DEBUG_DEVELOPER);
956 if (!empty($options['showform']) and ($form instanceof workshop_assessment_form)) {
957 $assessment->form = $form;
960 if (empty($options['showweight'])) {
961 $assessment->weight = null;
964 if (!is_null($record->grade)) {
965 $assessment->realgrade = $this->real_grade($record->grade);
968 return $assessment;
972 * Prepares renderable example submission's assessment component
974 * The $options array supports the following keys:
975 * showauthor - should the author user info be available for the renderer
976 * showreviewer - should the reviewer user info be available for the renderer
977 * showform - show the assessment form if it is available
979 * @param stdClass $record as returned by eg {@link self::get_assessment_by_id()}
980 * @param workshop_assessment_form|null $form as returned by {@link workshop_strategy::get_assessment_form()}
981 * @param array $options
982 * @return workshop_example_assessment
984 public function prepare_example_assessment(stdClass $record, $form = null, array $options = array()) {
986 $assessment = new workshop_example_assessment($this, $record, $options);
987 $assessment->url = $this->exassess_url($record->id);
988 $assessment->maxgrade = $this->real_grade(100);
990 if (!empty($options['showform']) and !($form instanceof workshop_assessment_form)) {
991 debugging('Not a valid instance of workshop_assessment_form supplied', DEBUG_DEVELOPER);
994 if (!empty($options['showform']) and ($form instanceof workshop_assessment_form)) {
995 $assessment->form = $form;
998 if (!is_null($record->grade)) {
999 $assessment->realgrade = $this->real_grade($record->grade);
1002 $assessment->weight = null;
1004 return $assessment;
1008 * Prepares renderable example submission's reference assessment component
1010 * The $options array supports the following keys:
1011 * showauthor - should the author user info be available for the renderer
1012 * showreviewer - should the reviewer user info be available for the renderer
1013 * showform - show the assessment form if it is available
1015 * @param stdClass $record as returned by eg {@link self::get_assessment_by_id()}
1016 * @param workshop_assessment_form|null $form as returned by {@link workshop_strategy::get_assessment_form()}
1017 * @param array $options
1018 * @return workshop_example_reference_assessment
1020 public function prepare_example_reference_assessment(stdClass $record, $form = null, array $options = array()) {
1022 $assessment = new workshop_example_reference_assessment($this, $record, $options);
1023 $assessment->maxgrade = $this->real_grade(100);
1025 if (!empty($options['showform']) and !($form instanceof workshop_assessment_form)) {
1026 debugging('Not a valid instance of workshop_assessment_form supplied', DEBUG_DEVELOPER);
1029 if (!empty($options['showform']) and ($form instanceof workshop_assessment_form)) {
1030 $assessment->form = $form;
1033 if (!is_null($record->grade)) {
1034 $assessment->realgrade = $this->real_grade($record->grade);
1037 $assessment->weight = null;
1039 return $assessment;
1043 * Removes the submission and all relevant data
1045 * @param stdClass $submission record to delete
1046 * @return void
1048 public function delete_submission(stdclass $submission) {
1049 global $DB;
1050 $assessments = $DB->get_records('workshop_assessments', array('submissionid' => $submission->id), '', 'id');
1051 $this->delete_assessment(array_keys($assessments));
1052 $DB->delete_records('workshop_submissions', array('id' => $submission->id));
1056 * Returns the list of all assessments in the workshop with some data added
1058 * Fetches data from {workshop_assessments} and adds some useful information from other
1059 * tables. The returned object does not contain textual fields (i.e. comments) to prevent memory
1060 * lack issues.
1062 * @return array [assessmentid] => assessment stdclass
1064 public function get_all_assessments() {
1065 global $DB;
1067 $reviewerfields = user_picture::fields('reviewer', null, 'revieweridx', 'reviewer');
1068 $authorfields = user_picture::fields('author', null, 'authorid', 'author');
1069 $overbyfields = user_picture::fields('overby', null, 'gradinggradeoverbyx', 'overby');
1070 list($sort, $params) = users_order_by_sql('reviewer');
1071 $sql = "SELECT a.id, a.submissionid, a.reviewerid, a.timecreated, a.timemodified,
1072 a.grade, a.gradinggrade, a.gradinggradeover, a.gradinggradeoverby,
1073 $reviewerfields, $authorfields, $overbyfields,
1074 s.title
1075 FROM {workshop_assessments} a
1076 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id)
1077 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
1078 INNER JOIN {user} author ON (s.authorid = author.id)
1079 LEFT JOIN {user} overby ON (a.gradinggradeoverby = overby.id)
1080 WHERE s.workshopid = :workshopid AND s.example = 0
1081 ORDER BY $sort";
1082 $params['workshopid'] = $this->id;
1084 return $DB->get_records_sql($sql, $params);
1088 * Get the complete information about the given assessment
1090 * @param int $id Assessment ID
1091 * @return stdclass
1093 public function get_assessment_by_id($id) {
1094 global $DB;
1096 $reviewerfields = user_picture::fields('reviewer', null, 'revieweridx', 'reviewer');
1097 $authorfields = user_picture::fields('author', null, 'authorid', 'author');
1098 $overbyfields = user_picture::fields('overby', null, 'gradinggradeoverbyx', 'overby');
1099 $sql = "SELECT a.*, s.title, $reviewerfields, $authorfields, $overbyfields
1100 FROM {workshop_assessments} a
1101 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id)
1102 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
1103 INNER JOIN {user} author ON (s.authorid = author.id)
1104 LEFT JOIN {user} overby ON (a.gradinggradeoverby = overby.id)
1105 WHERE a.id = :id AND s.workshopid = :workshopid";
1106 $params = array('id' => $id, 'workshopid' => $this->id);
1108 return $DB->get_record_sql($sql, $params, MUST_EXIST);
1112 * Get the complete information about the user's assessment of the given submission
1114 * @param int $sid submission ID
1115 * @param int $uid user ID of the reviewer
1116 * @return false|stdclass false if not found, stdclass otherwise
1118 public function get_assessment_of_submission_by_user($submissionid, $reviewerid) {
1119 global $DB;
1121 $reviewerfields = user_picture::fields('reviewer', null, 'revieweridx', 'reviewer');
1122 $authorfields = user_picture::fields('author', null, 'authorid', 'author');
1123 $overbyfields = user_picture::fields('overby', null, 'gradinggradeoverbyx', 'overby');
1124 $sql = "SELECT a.*, s.title, $reviewerfields, $authorfields, $overbyfields
1125 FROM {workshop_assessments} a
1126 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id)
1127 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id AND s.example = 0)
1128 INNER JOIN {user} author ON (s.authorid = author.id)
1129 LEFT JOIN {user} overby ON (a.gradinggradeoverby = overby.id)
1130 WHERE s.id = :sid AND reviewer.id = :rid AND s.workshopid = :workshopid";
1131 $params = array('sid' => $submissionid, 'rid' => $reviewerid, 'workshopid' => $this->id);
1133 return $DB->get_record_sql($sql, $params, IGNORE_MISSING);
1137 * Get the complete information about all assessments of the given submission
1139 * @param int $submissionid
1140 * @return array
1142 public function get_assessments_of_submission($submissionid) {
1143 global $DB;
1145 $reviewerfields = user_picture::fields('reviewer', null, 'revieweridx', 'reviewer');
1146 $overbyfields = user_picture::fields('overby', null, 'gradinggradeoverbyx', 'overby');
1147 list($sort, $params) = users_order_by_sql('reviewer');
1148 $sql = "SELECT a.*, s.title, $reviewerfields, $overbyfields
1149 FROM {workshop_assessments} a
1150 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id)
1151 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
1152 LEFT JOIN {user} overby ON (a.gradinggradeoverby = overby.id)
1153 WHERE s.example = 0 AND s.id = :submissionid AND s.workshopid = :workshopid
1154 ORDER BY $sort";
1155 $params['submissionid'] = $submissionid;
1156 $params['workshopid'] = $this->id;
1158 return $DB->get_records_sql($sql, $params);
1162 * Get the complete information about all assessments allocated to the given reviewer
1164 * @param int $reviewerid
1165 * @return array
1167 public function get_assessments_by_reviewer($reviewerid) {
1168 global $DB;
1170 $reviewerfields = user_picture::fields('reviewer', null, 'revieweridx', 'reviewer');
1171 $authorfields = user_picture::fields('author', null, 'authorid', 'author');
1172 $overbyfields = user_picture::fields('overby', null, 'gradinggradeoverbyx', 'overby');
1173 $sql = "SELECT a.*, $reviewerfields, $authorfields, $overbyfields,
1174 s.id AS submissionid, s.title AS submissiontitle, s.timecreated AS submissioncreated,
1175 s.timemodified AS submissionmodified
1176 FROM {workshop_assessments} a
1177 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id)
1178 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
1179 INNER JOIN {user} author ON (s.authorid = author.id)
1180 LEFT JOIN {user} overby ON (a.gradinggradeoverby = overby.id)
1181 WHERE s.example = 0 AND reviewer.id = :reviewerid AND s.workshopid = :workshopid";
1182 $params = array('reviewerid' => $reviewerid, 'workshopid' => $this->id);
1184 return $DB->get_records_sql($sql, $params);
1188 * Get allocated assessments not graded yet by the given reviewer
1190 * @see self::get_assessments_by_reviewer()
1191 * @param int $reviewerid the reviewer id
1192 * @param null|int|array $exclude optional assessment id (or list of them) to be excluded
1193 * @return array
1195 public function get_pending_assessments_by_reviewer($reviewerid, $exclude = null) {
1197 $assessments = $this->get_assessments_by_reviewer($reviewerid);
1199 foreach ($assessments as $id => $assessment) {
1200 if (!is_null($assessment->grade)) {
1201 unset($assessments[$id]);
1202 continue;
1204 if (!empty($exclude)) {
1205 if (is_array($exclude) and in_array($id, $exclude)) {
1206 unset($assessments[$id]);
1207 continue;
1208 } else if ($id == $exclude) {
1209 unset($assessments[$id]);
1210 continue;
1215 return $assessments;
1219 * Allocate a submission to a user for review
1221 * @param stdClass $submission Submission object with at least id property
1222 * @param int $reviewerid User ID
1223 * @param int $weight of the new assessment, from 0 to 16
1224 * @param bool $bulk repeated inserts into DB expected
1225 * @return int ID of the new assessment or an error code {@link self::ALLOCATION_EXISTS} if the allocation already exists
1227 public function add_allocation(stdclass $submission, $reviewerid, $weight=1, $bulk=false) {
1228 global $DB;
1230 if ($DB->record_exists('workshop_assessments', array('submissionid' => $submission->id, 'reviewerid' => $reviewerid))) {
1231 return self::ALLOCATION_EXISTS;
1234 $weight = (int)$weight;
1235 if ($weight < 0) {
1236 $weight = 0;
1238 if ($weight > 16) {
1239 $weight = 16;
1242 $now = time();
1243 $assessment = new stdclass();
1244 $assessment->submissionid = $submission->id;
1245 $assessment->reviewerid = $reviewerid;
1246 $assessment->timecreated = $now; // do not set timemodified here
1247 $assessment->weight = $weight;
1248 $assessment->feedbackauthorformat = editors_get_preferred_format();
1249 $assessment->feedbackreviewerformat = editors_get_preferred_format();
1251 return $DB->insert_record('workshop_assessments', $assessment, true, $bulk);
1255 * Delete assessment record or records
1257 * @param mixed $id int|array assessment id or array of assessments ids
1258 * @return bool false if $id not a valid parameter, true otherwise
1260 public function delete_assessment($id) {
1261 global $DB;
1263 // todo remove all given grades from workshop_grades;
1265 if (is_array($id)) {
1266 return $DB->delete_records_list('workshop_assessments', 'id', $id);
1267 } else {
1268 return $DB->delete_records('workshop_assessments', array('id' => $id));
1273 * Returns instance of grading strategy class
1275 * @return stdclass Instance of a grading strategy
1277 public function grading_strategy_instance() {
1278 global $CFG; // because we require other libs here
1280 if (is_null($this->strategyinstance)) {
1281 $strategylib = dirname(__FILE__) . '/form/' . $this->strategy . '/lib.php';
1282 if (is_readable($strategylib)) {
1283 require_once($strategylib);
1284 } else {
1285 throw new coding_exception('the grading forms subplugin must contain library ' . $strategylib);
1287 $classname = 'workshop_' . $this->strategy . '_strategy';
1288 $this->strategyinstance = new $classname($this);
1289 if (!in_array('workshop_strategy', class_implements($this->strategyinstance))) {
1290 throw new coding_exception($classname . ' does not implement workshop_strategy interface');
1293 return $this->strategyinstance;
1297 * Sets the current evaluation method to the given plugin.
1299 * @param string $method the name of the workshopeval subplugin
1300 * @return bool true if successfully set
1301 * @throws coding_exception if attempting to set a non-installed evaluation method
1303 public function set_grading_evaluation_method($method) {
1304 global $DB;
1306 $evaluationlib = dirname(__FILE__) . '/eval/' . $method . '/lib.php';
1308 if (is_readable($evaluationlib)) {
1309 $this->evaluationinstance = null;
1310 $this->evaluation = $method;
1311 $DB->set_field('workshop', 'evaluation', $method, array('id' => $this->id));
1312 return true;
1315 throw new coding_exception('Attempt to set a non-existing evaluation method.');
1319 * Returns instance of grading evaluation class
1321 * @return stdclass Instance of a grading evaluation
1323 public function grading_evaluation_instance() {
1324 global $CFG; // because we require other libs here
1326 if (is_null($this->evaluationinstance)) {
1327 if (empty($this->evaluation)) {
1328 $this->evaluation = 'best';
1330 $evaluationlib = dirname(__FILE__) . '/eval/' . $this->evaluation . '/lib.php';
1331 if (is_readable($evaluationlib)) {
1332 require_once($evaluationlib);
1333 } else {
1334 // Fall back in case the subplugin is not available.
1335 $this->evaluation = 'best';
1336 $evaluationlib = dirname(__FILE__) . '/eval/' . $this->evaluation . '/lib.php';
1337 if (is_readable($evaluationlib)) {
1338 require_once($evaluationlib);
1339 } else {
1340 // Fall back in case the subplugin is not available any more.
1341 throw new coding_exception('Missing default grading evaluation library ' . $evaluationlib);
1344 $classname = 'workshop_' . $this->evaluation . '_evaluation';
1345 $this->evaluationinstance = new $classname($this);
1346 if (!in_array('workshop_evaluation', class_parents($this->evaluationinstance))) {
1347 throw new coding_exception($classname . ' does not extend workshop_evaluation class');
1350 return $this->evaluationinstance;
1354 * Returns instance of submissions allocator
1356 * @param string $method The name of the allocation method, must be PARAM_ALPHA
1357 * @return stdclass Instance of submissions allocator
1359 public function allocator_instance($method) {
1360 global $CFG; // because we require other libs here
1362 $allocationlib = dirname(__FILE__) . '/allocation/' . $method . '/lib.php';
1363 if (is_readable($allocationlib)) {
1364 require_once($allocationlib);
1365 } else {
1366 throw new coding_exception('Unable to find the allocation library ' . $allocationlib);
1368 $classname = 'workshop_' . $method . '_allocator';
1369 return new $classname($this);
1373 * @return moodle_url of this workshop's view page
1375 public function view_url() {
1376 global $CFG;
1377 return new moodle_url('/mod/workshop/view.php', array('id' => $this->cm->id));
1381 * @return moodle_url of the page for editing this workshop's grading form
1383 public function editform_url() {
1384 global $CFG;
1385 return new moodle_url('/mod/workshop/editform.php', array('cmid' => $this->cm->id));
1389 * @return moodle_url of the page for previewing this workshop's grading form
1391 public function previewform_url() {
1392 global $CFG;
1393 return new moodle_url('/mod/workshop/editformpreview.php', array('cmid' => $this->cm->id));
1397 * @param int $assessmentid The ID of assessment record
1398 * @return moodle_url of the assessment page
1400 public function assess_url($assessmentid) {
1401 global $CFG;
1402 $assessmentid = clean_param($assessmentid, PARAM_INT);
1403 return new moodle_url('/mod/workshop/assessment.php', array('asid' => $assessmentid));
1407 * @param int $assessmentid The ID of assessment record
1408 * @return moodle_url of the example assessment page
1410 public function exassess_url($assessmentid) {
1411 global $CFG;
1412 $assessmentid = clean_param($assessmentid, PARAM_INT);
1413 return new moodle_url('/mod/workshop/exassessment.php', array('asid' => $assessmentid));
1417 * @return moodle_url of the page to view a submission, defaults to the own one
1419 public function submission_url($id=null) {
1420 global $CFG;
1421 return new moodle_url('/mod/workshop/submission.php', array('cmid' => $this->cm->id, 'id' => $id));
1425 * @param int $id example submission id
1426 * @return moodle_url of the page to view an example submission
1428 public function exsubmission_url($id) {
1429 global $CFG;
1430 return new moodle_url('/mod/workshop/exsubmission.php', array('cmid' => $this->cm->id, 'id' => $id));
1434 * @param int $sid submission id
1435 * @param array $aid of int assessment ids
1436 * @return moodle_url of the page to compare assessments of the given submission
1438 public function compare_url($sid, array $aids) {
1439 global $CFG;
1441 $url = new moodle_url('/mod/workshop/compare.php', array('cmid' => $this->cm->id, 'sid' => $sid));
1442 $i = 0;
1443 foreach ($aids as $aid) {
1444 $url->param("aid{$i}", $aid);
1445 $i++;
1447 return $url;
1451 * @param int $sid submission id
1452 * @param int $aid assessment id
1453 * @return moodle_url of the page to compare the reference assessments of the given example submission
1455 public function excompare_url($sid, $aid) {
1456 global $CFG;
1457 return new moodle_url('/mod/workshop/excompare.php', array('cmid' => $this->cm->id, 'sid' => $sid, 'aid' => $aid));
1461 * @return moodle_url of the mod_edit form
1463 public function updatemod_url() {
1464 global $CFG;
1465 return new moodle_url('/course/modedit.php', array('update' => $this->cm->id, 'return' => 1));
1469 * @param string $method allocation method
1470 * @return moodle_url to the allocation page
1472 public function allocation_url($method=null) {
1473 global $CFG;
1474 $params = array('cmid' => $this->cm->id);
1475 if (!empty($method)) {
1476 $params['method'] = $method;
1478 return new moodle_url('/mod/workshop/allocation.php', $params);
1482 * @param int $phasecode The internal phase code
1483 * @return moodle_url of the script to change the current phase to $phasecode
1485 public function switchphase_url($phasecode) {
1486 global $CFG;
1487 $phasecode = clean_param($phasecode, PARAM_INT);
1488 return new moodle_url('/mod/workshop/switchphase.php', array('cmid' => $this->cm->id, 'phase' => $phasecode));
1492 * @return moodle_url to the aggregation page
1494 public function aggregate_url() {
1495 global $CFG;
1496 return new moodle_url('/mod/workshop/aggregate.php', array('cmid' => $this->cm->id));
1500 * @return moodle_url of this workshop's toolbox page
1502 public function toolbox_url($tool) {
1503 global $CFG;
1504 return new moodle_url('/mod/workshop/toolbox.php', array('id' => $this->cm->id, 'tool' => $tool));
1508 * Workshop wrapper around {@see add_to_log()}
1510 * @param string $action to be logged
1511 * @param moodle_url $url absolute url as returned by {@see workshop::submission_url()} and friends
1512 * @param mixed $info additional info, usually id in a table
1513 * @param bool $return true to return the arguments for add_to_log.
1514 * @return void|array array of arguments for add_to_log if $return is true
1516 public function log($action, moodle_url $url = null, $info = null, $return = false) {
1518 if (is_null($url)) {
1519 $url = $this->view_url();
1522 if (is_null($info)) {
1523 $info = $this->id;
1526 $logurl = $this->log_convert_url($url);
1527 $args = array($this->course->id, 'workshop', $action, $logurl, $info, $this->cm->id);
1528 if ($return) {
1529 return $args;
1531 call_user_func_array('add_to_log', $args);
1535 * Is the given user allowed to create their submission?
1537 * @param int $userid
1538 * @return bool
1540 public function creating_submission_allowed($userid) {
1542 $now = time();
1543 $ignoredeadlines = has_capability('mod/workshop:ignoredeadlines', $this->context, $userid);
1545 if ($this->latesubmissions) {
1546 if ($this->phase != self::PHASE_SUBMISSION and $this->phase != self::PHASE_ASSESSMENT) {
1547 // late submissions are allowed in the submission and assessment phase only
1548 return false;
1550 if (!$ignoredeadlines and !empty($this->submissionstart) and $this->submissionstart > $now) {
1551 // late submissions are not allowed before the submission start
1552 return false;
1554 return true;
1556 } else {
1557 if ($this->phase != self::PHASE_SUBMISSION) {
1558 // submissions are allowed during the submission phase only
1559 return false;
1561 if (!$ignoredeadlines and !empty($this->submissionstart) and $this->submissionstart > $now) {
1562 // if enabled, submitting is not allowed before the date/time defined in the mod_form
1563 return false;
1565 if (!$ignoredeadlines and !empty($this->submissionend) and $now > $this->submissionend ) {
1566 // if enabled, submitting is not allowed after the date/time defined in the mod_form unless late submission is allowed
1567 return false;
1569 return true;
1574 * Is the given user allowed to modify their existing submission?
1576 * @param int $userid
1577 * @return bool
1579 public function modifying_submission_allowed($userid) {
1581 $now = time();
1582 $ignoredeadlines = has_capability('mod/workshop:ignoredeadlines', $this->context, $userid);
1584 if ($this->phase != self::PHASE_SUBMISSION) {
1585 // submissions can be edited during the submission phase only
1586 return false;
1588 if (!$ignoredeadlines and !empty($this->submissionstart) and $this->submissionstart > $now) {
1589 // if enabled, re-submitting is not allowed before the date/time defined in the mod_form
1590 return false;
1592 if (!$ignoredeadlines and !empty($this->submissionend) and $now > $this->submissionend) {
1593 // if enabled, re-submitting is not allowed after the date/time defined in the mod_form even if late submission is allowed
1594 return false;
1596 return true;
1600 * Is the given reviewer allowed to create/edit their assessments?
1602 * @param int $userid
1603 * @return bool
1605 public function assessing_allowed($userid) {
1607 if ($this->phase != self::PHASE_ASSESSMENT) {
1608 // assessing is allowed in the assessment phase only, unless the user is a teacher
1609 // providing additional assessment during the evaluation phase
1610 if ($this->phase != self::PHASE_EVALUATION or !has_capability('mod/workshop:overridegrades', $this->context, $userid)) {
1611 return false;
1615 $now = time();
1616 $ignoredeadlines = has_capability('mod/workshop:ignoredeadlines', $this->context, $userid);
1618 if (!$ignoredeadlines and !empty($this->assessmentstart) and $this->assessmentstart > $now) {
1619 // if enabled, assessing is not allowed before the date/time defined in the mod_form
1620 return false;
1622 if (!$ignoredeadlines and !empty($this->assessmentend) and $now > $this->assessmentend) {
1623 // if enabled, assessing is not allowed after the date/time defined in the mod_form
1624 return false;
1626 // here we go, assessing is allowed
1627 return true;
1631 * Are reviewers allowed to create/edit their assessments of the example submissions?
1633 * Returns null if example submissions are not enabled in this workshop. Otherwise returns
1634 * true or false. Note this does not check other conditions like the number of already
1635 * assessed examples, examples mode etc.
1637 * @return null|bool
1639 public function assessing_examples_allowed() {
1640 if (empty($this->useexamples)) {
1641 return null;
1643 if (self::EXAMPLES_VOLUNTARY == $this->examplesmode) {
1644 return true;
1646 if (self::EXAMPLES_BEFORE_SUBMISSION == $this->examplesmode and self::PHASE_SUBMISSION == $this->phase) {
1647 return true;
1649 if (self::EXAMPLES_BEFORE_ASSESSMENT == $this->examplesmode and self::PHASE_ASSESSMENT == $this->phase) {
1650 return true;
1652 return false;
1656 * Are the peer-reviews available to the authors?
1658 * @return bool
1660 public function assessments_available() {
1661 return $this->phase == self::PHASE_CLOSED;
1665 * Switch to a new workshop phase
1667 * Modifies the underlying database record. You should terminate the script shortly after calling this.
1669 * @param int $newphase new phase code
1670 * @return bool true if success, false otherwise
1672 public function switch_phase($newphase) {
1673 global $DB;
1675 $known = $this->available_phases_list();
1676 if (!isset($known[$newphase])) {
1677 return false;
1680 if (self::PHASE_CLOSED == $newphase) {
1681 // push the grades into the gradebook
1682 $workshop = new stdclass();
1683 foreach ($this as $property => $value) {
1684 $workshop->{$property} = $value;
1686 $workshop->course = $this->course->id;
1687 $workshop->cmidnumber = $this->cm->id;
1688 $workshop->modname = 'workshop';
1689 workshop_update_grades($workshop);
1692 $DB->set_field('workshop', 'phase', $newphase, array('id' => $this->id));
1693 $this->phase = $newphase;
1694 return true;
1698 * Saves a raw grade for submission as calculated from the assessment form fields
1700 * @param array $assessmentid assessment record id, must exists
1701 * @param mixed $grade raw percentual grade from 0.00000 to 100.00000
1702 * @return false|float the saved grade
1704 public function set_peer_grade($assessmentid, $grade) {
1705 global $DB;
1707 if (is_null($grade)) {
1708 return false;
1710 $data = new stdclass();
1711 $data->id = $assessmentid;
1712 $data->grade = $grade;
1713 $data->timemodified = time();
1714 $DB->update_record('workshop_assessments', $data);
1715 return $grade;
1719 * Prepares data object with all workshop grades to be rendered
1721 * @param int $userid the user we are preparing the report for
1722 * @param int $groupid if non-zero, prepare the report for the given group only
1723 * @param int $page the current page (for the pagination)
1724 * @param int $perpage participants per page (for the pagination)
1725 * @param string $sortby lastname|firstname|submissiontitle|submissiongrade|gradinggrade
1726 * @param string $sorthow ASC|DESC
1727 * @return stdclass data for the renderer
1729 public function prepare_grading_report_data($userid, $groupid, $page, $perpage, $sortby, $sorthow) {
1730 global $DB;
1732 $canviewall = has_capability('mod/workshop:viewallassessments', $this->context, $userid);
1733 $isparticipant = $this->is_participant($userid);
1735 if (!$canviewall and !$isparticipant) {
1736 // who the hell is this?
1737 return array();
1740 if (!in_array($sortby, array('lastname','firstname','submissiontitle','submissiongrade','gradinggrade'))) {
1741 $sortby = 'lastname';
1744 if (!($sorthow === 'ASC' or $sorthow === 'DESC')) {
1745 $sorthow = 'ASC';
1748 // get the list of user ids to be displayed
1749 if ($canviewall) {
1750 $participants = $this->get_participants(false, $groupid);
1751 } else {
1752 // this is an ordinary workshop participant (aka student) - display the report just for him/her
1753 $participants = array($userid => (object)array('id' => $userid));
1756 // we will need to know the number of all records later for the pagination purposes
1757 $numofparticipants = count($participants);
1759 if ($numofparticipants > 0) {
1760 // load all fields which can be used for sorting and paginate the records
1761 list($participantids, $params) = $DB->get_in_or_equal(array_keys($participants), SQL_PARAMS_NAMED);
1762 $params['workshopid1'] = $this->id;
1763 $params['workshopid2'] = $this->id;
1764 $sqlsort = array();
1765 $sqlsortfields = array($sortby => $sorthow) + array('lastname' => 'ASC', 'firstname' => 'ASC', 'u.id' => 'ASC');
1766 foreach ($sqlsortfields as $sqlsortfieldname => $sqlsortfieldhow) {
1767 $sqlsort[] = $sqlsortfieldname . ' ' . $sqlsortfieldhow;
1769 $sqlsort = implode(',', $sqlsort);
1770 $picturefields = user_picture::fields('u', array(), 'userid');
1771 $sql = "SELECT $picturefields, s.title AS submissiontitle, s.grade AS submissiongrade, ag.gradinggrade
1772 FROM {user} u
1773 LEFT JOIN {workshop_submissions} s ON (s.authorid = u.id AND s.workshopid = :workshopid1 AND s.example = 0)
1774 LEFT JOIN {workshop_aggregations} ag ON (ag.userid = u.id AND ag.workshopid = :workshopid2)
1775 WHERE u.id $participantids
1776 ORDER BY $sqlsort";
1777 $participants = $DB->get_records_sql($sql, $params, $page * $perpage, $perpage);
1778 } else {
1779 $participants = array();
1782 // this will hold the information needed to display user names and pictures
1783 $userinfo = array();
1785 // get the user details for all participants to display
1786 $additionalnames = get_all_user_name_fields();
1787 foreach ($participants as $participant) {
1788 if (!isset($userinfo[$participant->userid])) {
1789 $userinfo[$participant->userid] = new stdclass();
1790 $userinfo[$participant->userid]->id = $participant->userid;
1791 $userinfo[$participant->userid]->picture = $participant->picture;
1792 $userinfo[$participant->userid]->imagealt = $participant->imagealt;
1793 $userinfo[$participant->userid]->email = $participant->email;
1794 foreach ($additionalnames as $addname) {
1795 $userinfo[$participant->userid]->$addname = $participant->$addname;
1800 // load the submissions details
1801 $submissions = $this->get_submissions(array_keys($participants));
1803 // get the user details for all moderators (teachers) that have overridden a submission grade
1804 foreach ($submissions as $submission) {
1805 if (!isset($userinfo[$submission->gradeoverby])) {
1806 $userinfo[$submission->gradeoverby] = new stdclass();
1807 $userinfo[$submission->gradeoverby]->id = $submission->gradeoverby;
1808 $userinfo[$submission->gradeoverby]->picture = $submission->overpicture;
1809 $userinfo[$submission->gradeoverby]->imagealt = $submission->overimagealt;
1810 $userinfo[$submission->gradeoverby]->email = $submission->overemail;
1811 foreach ($additionalnames as $addname) {
1812 $temp = 'over' . $addname;
1813 $userinfo[$submission->gradeoverby]->$addname = $submission->$temp;
1818 // get the user details for all reviewers of the displayed participants
1819 $reviewers = array();
1821 if ($submissions) {
1822 list($submissionids, $params) = $DB->get_in_or_equal(array_keys($submissions), SQL_PARAMS_NAMED);
1823 list($sort, $sortparams) = users_order_by_sql('r');
1824 $picturefields = user_picture::fields('r', array(), 'reviewerid');
1825 $sql = "SELECT a.id AS assessmentid, a.submissionid, a.grade, a.gradinggrade, a.gradinggradeover, a.weight,
1826 $picturefields, s.id AS submissionid, s.authorid
1827 FROM {workshop_assessments} a
1828 JOIN {user} r ON (a.reviewerid = r.id)
1829 JOIN {workshop_submissions} s ON (a.submissionid = s.id AND s.example = 0)
1830 WHERE a.submissionid $submissionids
1831 ORDER BY a.weight DESC, $sort";
1832 $reviewers = $DB->get_records_sql($sql, array_merge($params, $sortparams));
1833 foreach ($reviewers as $reviewer) {
1834 if (!isset($userinfo[$reviewer->reviewerid])) {
1835 $userinfo[$reviewer->reviewerid] = new stdclass();
1836 $userinfo[$reviewer->reviewerid]->id = $reviewer->reviewerid;
1837 $userinfo[$reviewer->reviewerid]->picture = $reviewer->picture;
1838 $userinfo[$reviewer->reviewerid]->imagealt = $reviewer->imagealt;
1839 $userinfo[$reviewer->reviewerid]->email = $reviewer->email;
1840 foreach ($additionalnames as $addname) {
1841 $userinfo[$reviewer->reviewerid]->$addname = $reviewer->$addname;
1847 // get the user details for all reviewees of the displayed participants
1848 $reviewees = array();
1849 if ($participants) {
1850 list($participantids, $params) = $DB->get_in_or_equal(array_keys($participants), SQL_PARAMS_NAMED);
1851 list($sort, $sortparams) = users_order_by_sql('e');
1852 $params['workshopid'] = $this->id;
1853 $picturefields = user_picture::fields('e', array(), 'authorid');
1854 $sql = "SELECT a.id AS assessmentid, a.submissionid, a.grade, a.gradinggrade, a.gradinggradeover, a.reviewerid, a.weight,
1855 s.id AS submissionid, $picturefields
1856 FROM {user} u
1857 JOIN {workshop_assessments} a ON (a.reviewerid = u.id)
1858 JOIN {workshop_submissions} s ON (a.submissionid = s.id AND s.example = 0)
1859 JOIN {user} e ON (s.authorid = e.id)
1860 WHERE u.id $participantids AND s.workshopid = :workshopid
1861 ORDER BY a.weight DESC, $sort";
1862 $reviewees = $DB->get_records_sql($sql, array_merge($params, $sortparams));
1863 foreach ($reviewees as $reviewee) {
1864 if (!isset($userinfo[$reviewee->authorid])) {
1865 $userinfo[$reviewee->authorid] = new stdclass();
1866 $userinfo[$reviewee->authorid]->id = $reviewee->authorid;
1867 $userinfo[$reviewee->authorid]->picture = $reviewee->picture;
1868 $userinfo[$reviewee->authorid]->imagealt = $reviewee->imagealt;
1869 $userinfo[$reviewee->authorid]->email = $reviewee->email;
1870 foreach ($additionalnames as $addname) {
1871 $userinfo[$reviewee->authorid]->$addname = $reviewee->$addname;
1877 // finally populate the object to be rendered
1878 $grades = $participants;
1880 foreach ($participants as $participant) {
1881 // set up default (null) values
1882 $grades[$participant->userid]->submissionid = null;
1883 $grades[$participant->userid]->submissiontitle = null;
1884 $grades[$participant->userid]->submissiongrade = null;
1885 $grades[$participant->userid]->submissiongradeover = null;
1886 $grades[$participant->userid]->submissiongradeoverby = null;
1887 $grades[$participant->userid]->submissionpublished = null;
1888 $grades[$participant->userid]->reviewedby = array();
1889 $grades[$participant->userid]->reviewerof = array();
1891 unset($participants);
1892 unset($participant);
1894 foreach ($submissions as $submission) {
1895 $grades[$submission->authorid]->submissionid = $submission->id;
1896 $grades[$submission->authorid]->submissiontitle = $submission->title;
1897 $grades[$submission->authorid]->submissiongrade = $this->real_grade($submission->grade);
1898 $grades[$submission->authorid]->submissiongradeover = $this->real_grade($submission->gradeover);
1899 $grades[$submission->authorid]->submissiongradeoverby = $submission->gradeoverby;
1900 $grades[$submission->authorid]->submissionpublished = $submission->published;
1902 unset($submissions);
1903 unset($submission);
1905 foreach($reviewers as $reviewer) {
1906 $info = new stdclass();
1907 $info->userid = $reviewer->reviewerid;
1908 $info->assessmentid = $reviewer->assessmentid;
1909 $info->submissionid = $reviewer->submissionid;
1910 $info->grade = $this->real_grade($reviewer->grade);
1911 $info->gradinggrade = $this->real_grading_grade($reviewer->gradinggrade);
1912 $info->gradinggradeover = $this->real_grading_grade($reviewer->gradinggradeover);
1913 $info->weight = $reviewer->weight;
1914 $grades[$reviewer->authorid]->reviewedby[$reviewer->reviewerid] = $info;
1916 unset($reviewers);
1917 unset($reviewer);
1919 foreach($reviewees as $reviewee) {
1920 $info = new stdclass();
1921 $info->userid = $reviewee->authorid;
1922 $info->assessmentid = $reviewee->assessmentid;
1923 $info->submissionid = $reviewee->submissionid;
1924 $info->grade = $this->real_grade($reviewee->grade);
1925 $info->gradinggrade = $this->real_grading_grade($reviewee->gradinggrade);
1926 $info->gradinggradeover = $this->real_grading_grade($reviewee->gradinggradeover);
1927 $info->weight = $reviewee->weight;
1928 $grades[$reviewee->reviewerid]->reviewerof[$reviewee->authorid] = $info;
1930 unset($reviewees);
1931 unset($reviewee);
1933 foreach ($grades as $grade) {
1934 $grade->gradinggrade = $this->real_grading_grade($grade->gradinggrade);
1937 $data = new stdclass();
1938 $data->grades = $grades;
1939 $data->userinfo = $userinfo;
1940 $data->totalcount = $numofparticipants;
1941 $data->maxgrade = $this->real_grade(100);
1942 $data->maxgradinggrade = $this->real_grading_grade(100);
1943 return $data;
1947 * Calculates the real value of a grade
1949 * @param float $value percentual value from 0 to 100
1950 * @param float $max the maximal grade
1951 * @return string
1953 public function real_grade_value($value, $max) {
1954 $localized = true;
1955 if (is_null($value) or $value === '') {
1956 return null;
1957 } elseif ($max == 0) {
1958 return 0;
1959 } else {
1960 return format_float($max * $value / 100, $this->gradedecimals, $localized);
1965 * Calculates the raw (percentual) value from a real grade
1967 * This is used in cases when a user wants to give a grade such as 12 of 20 and we need to save
1968 * this value in a raw percentual form into DB
1969 * @param float $value given grade
1970 * @param float $max the maximal grade
1971 * @return float suitable to be stored as numeric(10,5)
1973 public function raw_grade_value($value, $max) {
1974 if (is_null($value) or $value === '') {
1975 return null;
1977 if ($max == 0 or $value < 0) {
1978 return 0;
1980 $p = $value / $max * 100;
1981 if ($p > 100) {
1982 return $max;
1984 return grade_floatval($p);
1988 * Calculates the real value of grade for submission
1990 * @param float $value percentual value from 0 to 100
1991 * @return string
1993 public function real_grade($value) {
1994 return $this->real_grade_value($value, $this->grade);
1998 * Calculates the real value of grade for assessment
2000 * @param float $value percentual value from 0 to 100
2001 * @return string
2003 public function real_grading_grade($value) {
2004 return $this->real_grade_value($value, $this->gradinggrade);
2008 * Sets the given grades and received grading grades to null
2010 * This does not clear the information about how the peers filled the assessment forms, but
2011 * clears the calculated grades in workshop_assessments. Therefore reviewers have to re-assess
2012 * the allocated submissions.
2014 * @return void
2016 public function clear_assessments() {
2017 global $DB;
2019 $submissions = $this->get_submissions();
2020 if (empty($submissions)) {
2021 // no money, no love
2022 return;
2024 $submissions = array_keys($submissions);
2025 list($sql, $params) = $DB->get_in_or_equal($submissions, SQL_PARAMS_NAMED);
2026 $sql = "submissionid $sql";
2027 $DB->set_field_select('workshop_assessments', 'grade', null, $sql, $params);
2028 $DB->set_field_select('workshop_assessments', 'gradinggrade', null, $sql, $params);
2032 * Sets the grades for submission to null
2034 * @param null|int|array $restrict If null, update all authors, otherwise update just grades for the given author(s)
2035 * @return void
2037 public function clear_submission_grades($restrict=null) {
2038 global $DB;
2040 $sql = "workshopid = :workshopid AND example = 0";
2041 $params = array('workshopid' => $this->id);
2043 if (is_null($restrict)) {
2044 // update all users - no more conditions
2045 } elseif (!empty($restrict)) {
2046 list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED);
2047 $sql .= " AND authorid $usql";
2048 $params = array_merge($params, $uparams);
2049 } else {
2050 throw new coding_exception('Empty value is not a valid parameter here');
2053 $DB->set_field_select('workshop_submissions', 'grade', null, $sql, $params);
2057 * Calculates grades for submission for the given participant(s) and updates it in the database
2059 * @param null|int|array $restrict If null, update all authors, otherwise update just grades for the given author(s)
2060 * @return void
2062 public function aggregate_submission_grades($restrict=null) {
2063 global $DB;
2065 // fetch a recordset with all assessments to process
2066 $sql = 'SELECT s.id AS submissionid, s.grade AS submissiongrade,
2067 a.weight, a.grade
2068 FROM {workshop_submissions} s
2069 LEFT JOIN {workshop_assessments} a ON (a.submissionid = s.id)
2070 WHERE s.example=0 AND s.workshopid=:workshopid'; // to be cont.
2071 $params = array('workshopid' => $this->id);
2073 if (is_null($restrict)) {
2074 // update all users - no more conditions
2075 } elseif (!empty($restrict)) {
2076 list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED);
2077 $sql .= " AND s.authorid $usql";
2078 $params = array_merge($params, $uparams);
2079 } else {
2080 throw new coding_exception('Empty value is not a valid parameter here');
2083 $sql .= ' ORDER BY s.id'; // this is important for bulk processing
2085 $rs = $DB->get_recordset_sql($sql, $params);
2086 $batch = array(); // will contain a set of all assessments of a single submission
2087 $previous = null; // a previous record in the recordset
2089 foreach ($rs as $current) {
2090 if (is_null($previous)) {
2091 // we are processing the very first record in the recordset
2092 $previous = $current;
2094 if ($current->submissionid == $previous->submissionid) {
2095 // we are still processing the current submission
2096 $batch[] = $current;
2097 } else {
2098 // process all the assessments of a sigle submission
2099 $this->aggregate_submission_grades_process($batch);
2100 // and then start to process another submission
2101 $batch = array($current);
2102 $previous = $current;
2105 // do not forget to process the last batch!
2106 $this->aggregate_submission_grades_process($batch);
2107 $rs->close();
2111 * Sets the aggregated grades for assessment to null
2113 * @param null|int|array $restrict If null, update all reviewers, otherwise update just grades for the given reviewer(s)
2114 * @return void
2116 public function clear_grading_grades($restrict=null) {
2117 global $DB;
2119 $sql = "workshopid = :workshopid";
2120 $params = array('workshopid' => $this->id);
2122 if (is_null($restrict)) {
2123 // update all users - no more conditions
2124 } elseif (!empty($restrict)) {
2125 list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED);
2126 $sql .= " AND userid $usql";
2127 $params = array_merge($params, $uparams);
2128 } else {
2129 throw new coding_exception('Empty value is not a valid parameter here');
2132 $DB->set_field_select('workshop_aggregations', 'gradinggrade', null, $sql, $params);
2136 * Calculates grades for assessment for the given participant(s)
2138 * Grade for assessment is calculated as a simple mean of all grading grades calculated by the grading evaluator.
2139 * The assessment weight is not taken into account here.
2141 * @param null|int|array $restrict If null, update all reviewers, otherwise update just grades for the given reviewer(s)
2142 * @return void
2144 public function aggregate_grading_grades($restrict=null) {
2145 global $DB;
2147 // fetch a recordset with all assessments to process
2148 $sql = 'SELECT a.reviewerid, a.gradinggrade, a.gradinggradeover,
2149 ag.id AS aggregationid, ag.gradinggrade AS aggregatedgrade
2150 FROM {workshop_assessments} a
2151 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
2152 LEFT JOIN {workshop_aggregations} ag ON (ag.userid = a.reviewerid AND ag.workshopid = s.workshopid)
2153 WHERE s.example=0 AND s.workshopid=:workshopid'; // to be cont.
2154 $params = array('workshopid' => $this->id);
2156 if (is_null($restrict)) {
2157 // update all users - no more conditions
2158 } elseif (!empty($restrict)) {
2159 list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED);
2160 $sql .= " AND a.reviewerid $usql";
2161 $params = array_merge($params, $uparams);
2162 } else {
2163 throw new coding_exception('Empty value is not a valid parameter here');
2166 $sql .= ' ORDER BY a.reviewerid'; // this is important for bulk processing
2168 $rs = $DB->get_recordset_sql($sql, $params);
2169 $batch = array(); // will contain a set of all assessments of a single submission
2170 $previous = null; // a previous record in the recordset
2172 foreach ($rs as $current) {
2173 if (is_null($previous)) {
2174 // we are processing the very first record in the recordset
2175 $previous = $current;
2177 if ($current->reviewerid == $previous->reviewerid) {
2178 // we are still processing the current reviewer
2179 $batch[] = $current;
2180 } else {
2181 // process all the assessments of a sigle submission
2182 $this->aggregate_grading_grades_process($batch);
2183 // and then start to process another reviewer
2184 $batch = array($current);
2185 $previous = $current;
2188 // do not forget to process the last batch!
2189 $this->aggregate_grading_grades_process($batch);
2190 $rs->close();
2194 * Returns the mform the teachers use to put a feedback for the reviewer
2196 * @param moodle_url $actionurl
2197 * @param stdClass $assessment
2198 * @param array $options editable, editableweight, overridablegradinggrade
2199 * @return workshop_feedbackreviewer_form
2201 public function get_feedbackreviewer_form(moodle_url $actionurl, stdclass $assessment, $options=array()) {
2202 global $CFG;
2203 require_once(dirname(__FILE__) . '/feedbackreviewer_form.php');
2205 $current = new stdclass();
2206 $current->asid = $assessment->id;
2207 $current->weight = $assessment->weight;
2208 $current->gradinggrade = $this->real_grading_grade($assessment->gradinggrade);
2209 $current->gradinggradeover = $this->real_grading_grade($assessment->gradinggradeover);
2210 $current->feedbackreviewer = $assessment->feedbackreviewer;
2211 $current->feedbackreviewerformat = $assessment->feedbackreviewerformat;
2212 if (is_null($current->gradinggrade)) {
2213 $current->gradinggrade = get_string('nullgrade', 'workshop');
2215 if (!isset($options['editable'])) {
2216 $editable = true; // by default
2217 } else {
2218 $editable = (bool)$options['editable'];
2221 // prepare wysiwyg editor
2222 $current = file_prepare_standard_editor($current, 'feedbackreviewer', array());
2224 return new workshop_feedbackreviewer_form($actionurl,
2225 array('workshop' => $this, 'current' => $current, 'editoropts' => array(), 'options' => $options),
2226 'post', '', null, $editable);
2230 * Returns the mform the teachers use to put a feedback for the author on their submission
2232 * @param moodle_url $actionurl
2233 * @param stdClass $submission
2234 * @param array $options editable
2235 * @return workshop_feedbackauthor_form
2237 public function get_feedbackauthor_form(moodle_url $actionurl, stdclass $submission, $options=array()) {
2238 global $CFG;
2239 require_once(dirname(__FILE__) . '/feedbackauthor_form.php');
2241 $current = new stdclass();
2242 $current->submissionid = $submission->id;
2243 $current->published = $submission->published;
2244 $current->grade = $this->real_grade($submission->grade);
2245 $current->gradeover = $this->real_grade($submission->gradeover);
2246 $current->feedbackauthor = $submission->feedbackauthor;
2247 $current->feedbackauthorformat = $submission->feedbackauthorformat;
2248 if (is_null($current->grade)) {
2249 $current->grade = get_string('nullgrade', 'workshop');
2251 if (!isset($options['editable'])) {
2252 $editable = true; // by default
2253 } else {
2254 $editable = (bool)$options['editable'];
2257 // prepare wysiwyg editor
2258 $current = file_prepare_standard_editor($current, 'feedbackauthor', array());
2260 return new workshop_feedbackauthor_form($actionurl,
2261 array('workshop' => $this, 'current' => $current, 'editoropts' => array(), 'options' => $options),
2262 'post', '', null, $editable);
2266 * Returns the information about the user's grades as they are stored in the gradebook
2268 * The submission grade is returned for users with the capability mod/workshop:submit and the
2269 * assessment grade is returned for users with the capability mod/workshop:peerassess. Unless the
2270 * user has the capability to view hidden grades, grades must be visible to be returned. Null
2271 * grades are not returned. If none grade is to be returned, this method returns false.
2273 * @param int $userid the user's id
2274 * @return workshop_final_grades|false
2276 public function get_gradebook_grades($userid) {
2277 global $CFG;
2278 require_once($CFG->libdir.'/gradelib.php');
2280 if (empty($userid)) {
2281 throw new coding_exception('User id expected, empty value given.');
2284 // Read data via the Gradebook API
2285 $gradebook = grade_get_grades($this->course->id, 'mod', 'workshop', $this->id, $userid);
2287 $grades = new workshop_final_grades();
2289 if (has_capability('mod/workshop:submit', $this->context, $userid)) {
2290 if (!empty($gradebook->items[0]->grades)) {
2291 $submissiongrade = reset($gradebook->items[0]->grades);
2292 if (!is_null($submissiongrade->grade)) {
2293 if (!$submissiongrade->hidden or has_capability('moodle/grade:viewhidden', $this->context, $userid)) {
2294 $grades->submissiongrade = $submissiongrade;
2300 if (has_capability('mod/workshop:peerassess', $this->context, $userid)) {
2301 if (!empty($gradebook->items[1]->grades)) {
2302 $assessmentgrade = reset($gradebook->items[1]->grades);
2303 if (!is_null($assessmentgrade->grade)) {
2304 if (!$assessmentgrade->hidden or has_capability('moodle/grade:viewhidden', $this->context, $userid)) {
2305 $grades->assessmentgrade = $assessmentgrade;
2311 if (!is_null($grades->submissiongrade) or !is_null($grades->assessmentgrade)) {
2312 return $grades;
2315 return false;
2319 * Return the editor options for the overall feedback for the author.
2321 * @return array
2323 public function overall_feedback_content_options() {
2324 return array(
2325 'subdirs' => 0,
2326 'maxbytes' => $this->overallfeedbackmaxbytes,
2327 'maxfiles' => $this->overallfeedbackfiles,
2328 'changeformat' => 1,
2329 'context' => $this->context,
2334 * Return the filemanager options for the overall feedback for the author.
2336 * @return array
2338 public function overall_feedback_attachment_options() {
2339 return array(
2340 'subdirs' => 1,
2341 'maxbytes' => $this->overallfeedbackmaxbytes,
2342 'maxfiles' => $this->overallfeedbackfiles,
2343 'return_types' => FILE_INTERNAL,
2347 ////////////////////////////////////////////////////////////////////////////////
2348 // Internal methods (implementation details) //
2349 ////////////////////////////////////////////////////////////////////////////////
2352 * Given an array of all assessments of a single submission, calculates the final grade for this submission
2354 * This calculates the weighted mean of the passed assessment grades. If, however, the submission grade
2355 * was overridden by a teacher, the gradeover value is returned and the rest of grades are ignored.
2357 * @param array $assessments of stdclass(->submissionid ->submissiongrade ->gradeover ->weight ->grade)
2358 * @return void
2360 protected function aggregate_submission_grades_process(array $assessments) {
2361 global $DB;
2363 $submissionid = null; // the id of the submission being processed
2364 $current = null; // the grade currently saved in database
2365 $finalgrade = null; // the new grade to be calculated
2366 $sumgrades = 0;
2367 $sumweights = 0;
2369 foreach ($assessments as $assessment) {
2370 if (is_null($submissionid)) {
2371 // the id is the same in all records, fetch it during the first loop cycle
2372 $submissionid = $assessment->submissionid;
2374 if (is_null($current)) {
2375 // the currently saved grade is the same in all records, fetch it during the first loop cycle
2376 $current = $assessment->submissiongrade;
2378 if (is_null($assessment->grade)) {
2379 // this was not assessed yet
2380 continue;
2382 if ($assessment->weight == 0) {
2383 // this does not influence the calculation
2384 continue;
2386 $sumgrades += $assessment->grade * $assessment->weight;
2387 $sumweights += $assessment->weight;
2389 if ($sumweights > 0 and is_null($finalgrade)) {
2390 $finalgrade = grade_floatval($sumgrades / $sumweights);
2392 // check if the new final grade differs from the one stored in the database
2393 if (grade_floats_different($finalgrade, $current)) {
2394 // we need to save new calculation into the database
2395 $record = new stdclass();
2396 $record->id = $submissionid;
2397 $record->grade = $finalgrade;
2398 $record->timegraded = time();
2399 $DB->update_record('workshop_submissions', $record);
2404 * Given an array of all assessments done by a single reviewer, calculates the final grading grade
2406 * This calculates the simple mean of the passed grading grades. If, however, the grading grade
2407 * was overridden by a teacher, the gradinggradeover value is returned and the rest of grades are ignored.
2409 * @param array $assessments of stdclass(->reviewerid ->gradinggrade ->gradinggradeover ->aggregationid ->aggregatedgrade)
2410 * @param null|int $timegraded explicit timestamp of the aggregation, defaults to the current time
2411 * @return void
2413 protected function aggregate_grading_grades_process(array $assessments, $timegraded = null) {
2414 global $DB;
2416 $reviewerid = null; // the id of the reviewer being processed
2417 $current = null; // the gradinggrade currently saved in database
2418 $finalgrade = null; // the new grade to be calculated
2419 $agid = null; // aggregation id
2420 $sumgrades = 0;
2421 $count = 0;
2423 if (is_null($timegraded)) {
2424 $timegraded = time();
2427 foreach ($assessments as $assessment) {
2428 if (is_null($reviewerid)) {
2429 // the id is the same in all records, fetch it during the first loop cycle
2430 $reviewerid = $assessment->reviewerid;
2432 if (is_null($agid)) {
2433 // the id is the same in all records, fetch it during the first loop cycle
2434 $agid = $assessment->aggregationid;
2436 if (is_null($current)) {
2437 // the currently saved grade is the same in all records, fetch it during the first loop cycle
2438 $current = $assessment->aggregatedgrade;
2440 if (!is_null($assessment->gradinggradeover)) {
2441 // the grading grade for this assessment is overridden by a teacher
2442 $sumgrades += $assessment->gradinggradeover;
2443 $count++;
2444 } else {
2445 if (!is_null($assessment->gradinggrade)) {
2446 $sumgrades += $assessment->gradinggrade;
2447 $count++;
2451 if ($count > 0) {
2452 $finalgrade = grade_floatval($sumgrades / $count);
2454 // check if the new final grade differs from the one stored in the database
2455 if (grade_floats_different($finalgrade, $current)) {
2456 // we need to save new calculation into the database
2457 if (is_null($agid)) {
2458 // no aggregation record yet
2459 $record = new stdclass();
2460 $record->workshopid = $this->id;
2461 $record->userid = $reviewerid;
2462 $record->gradinggrade = $finalgrade;
2463 $record->timegraded = $timegraded;
2464 $DB->insert_record('workshop_aggregations', $record);
2465 } else {
2466 $record = new stdclass();
2467 $record->id = $agid;
2468 $record->gradinggrade = $finalgrade;
2469 $record->timegraded = $timegraded;
2470 $DB->update_record('workshop_aggregations', $record);
2476 * Returns SQL to fetch all enrolled users with the given capability in the current workshop
2478 * The returned array consists of string $sql and the $params array. Note that the $sql can be
2479 * empty if groupmembersonly is enabled and the associated grouping is empty.
2481 * @param string $capability the name of the capability
2482 * @param bool $musthavesubmission ff true, return only users who have already submitted
2483 * @param int $groupid 0 means ignore groups, any other value limits the result by group id
2484 * @return array of (string)sql, (array)params
2486 protected function get_users_with_capability_sql($capability, $musthavesubmission, $groupid) {
2487 global $CFG;
2488 /** @var int static counter used to generate unique parameter holders */
2489 static $inc = 0;
2490 $inc++;
2492 // if the caller requests all groups and we are in groupmembersonly mode, use the
2493 // recursive call of itself to get users from all groups in the grouping
2494 if (empty($groupid) and !empty($CFG->enablegroupmembersonly) and $this->cm->groupmembersonly) {
2495 $groupingid = $this->cm->groupingid;
2496 $groupinggroupids = array_keys(groups_get_all_groups($this->cm->course, 0, $this->cm->groupingid, 'g.id'));
2497 $sql = array();
2498 $params = array();
2499 foreach ($groupinggroupids as $groupinggroupid) {
2500 if ($groupinggroupid > 0) { // just in case in order not to fall into the endless loop
2501 list($gsql, $gparams) = $this->get_users_with_capability_sql($capability, $musthavesubmission, $groupinggroupid);
2502 $sql[] = $gsql;
2503 $params = array_merge($params, $gparams);
2506 $sql = implode(PHP_EOL." UNION ".PHP_EOL, $sql);
2507 return array($sql, $params);
2510 list($esql, $params) = get_enrolled_sql($this->context, $capability, $groupid, true);
2512 $userfields = user_picture::fields('u');
2514 $sql = "SELECT $userfields
2515 FROM {user} u
2516 JOIN ($esql) je ON (je.id = u.id AND u.deleted = 0) ";
2518 if ($musthavesubmission) {
2519 $sql .= " JOIN {workshop_submissions} ws ON (ws.authorid = u.id AND ws.example = 0 AND ws.workshopid = :workshopid{$inc}) ";
2520 $params['workshopid'.$inc] = $this->id;
2523 return array($sql, $params);
2527 * Returns SQL statement that can be used to fetch all actively enrolled participants in the workshop
2529 * @param bool $musthavesubmission if true, return only users who have already submitted
2530 * @param int $groupid 0 means ignore groups, any other value limits the result by group id
2531 * @return array of (string)sql, (array)params
2533 protected function get_participants_sql($musthavesubmission=false, $groupid=0) {
2535 list($sql1, $params1) = $this->get_users_with_capability_sql('mod/workshop:submit', $musthavesubmission, $groupid);
2536 list($sql2, $params2) = $this->get_users_with_capability_sql('mod/workshop:peerassess', $musthavesubmission, $groupid);
2538 if (empty($sql1) or empty($sql2)) {
2539 if (empty($sql1) and empty($sql2)) {
2540 return array('', array());
2541 } else if (empty($sql1)) {
2542 $sql = $sql2;
2543 $params = $params2;
2544 } else {
2545 $sql = $sql1;
2546 $params = $params1;
2548 } else {
2549 $sql = $sql1.PHP_EOL." UNION ".PHP_EOL.$sql2;
2550 $params = array_merge($params1, $params2);
2553 return array($sql, $params);
2557 * @return array of available workshop phases
2559 protected function available_phases_list() {
2560 return array(
2561 self::PHASE_SETUP => true,
2562 self::PHASE_SUBMISSION => true,
2563 self::PHASE_ASSESSMENT => true,
2564 self::PHASE_EVALUATION => true,
2565 self::PHASE_CLOSED => true,
2570 * Converts absolute URL to relative URL needed by {@see add_to_log()}
2572 * @param moodle_url $url absolute URL
2573 * @return string
2575 protected function log_convert_url(moodle_url $fullurl) {
2576 static $baseurl;
2578 if (!isset($baseurl)) {
2579 $baseurl = new moodle_url('/mod/workshop/');
2580 $baseurl = $baseurl->out();
2583 return substr($fullurl->out(), strlen($baseurl));
2587 ////////////////////////////////////////////////////////////////////////////////
2588 // Renderable components
2589 ////////////////////////////////////////////////////////////////////////////////
2592 * Represents the user planner tool
2594 * Planner contains list of phases. Each phase contains list of tasks. Task is a simple object with
2595 * title, link and completed (true/false/null logic).
2597 class workshop_user_plan implements renderable {
2599 /** @var int id of the user this plan is for */
2600 public $userid;
2601 /** @var workshop */
2602 public $workshop;
2603 /** @var array of (stdclass)tasks */
2604 public $phases = array();
2605 /** @var null|array of example submissions to be assessed by the planner owner */
2606 protected $examples = null;
2609 * Prepare an individual workshop plan for the given user.
2611 * @param workshop $workshop instance
2612 * @param int $userid whom the plan is prepared for
2614 public function __construct(workshop $workshop, $userid) {
2615 global $DB;
2617 $this->workshop = $workshop;
2618 $this->userid = $userid;
2620 //---------------------------------------------------------
2621 // * SETUP | submission | assessment | evaluation | closed
2622 //---------------------------------------------------------
2623 $phase = new stdclass();
2624 $phase->title = get_string('phasesetup', 'workshop');
2625 $phase->tasks = array();
2626 if (has_capability('moodle/course:manageactivities', $workshop->context, $userid)) {
2627 $task = new stdclass();
2628 $task->title = get_string('taskintro', 'workshop');
2629 $task->link = $workshop->updatemod_url();
2630 $task->completed = !(trim($workshop->intro) == '');
2631 $phase->tasks['intro'] = $task;
2633 if (has_capability('moodle/course:manageactivities', $workshop->context, $userid)) {
2634 $task = new stdclass();
2635 $task->title = get_string('taskinstructauthors', 'workshop');
2636 $task->link = $workshop->updatemod_url();
2637 $task->completed = !(trim($workshop->instructauthors) == '');
2638 $phase->tasks['instructauthors'] = $task;
2640 if (has_capability('mod/workshop:editdimensions', $workshop->context, $userid)) {
2641 $task = new stdclass();
2642 $task->title = get_string('editassessmentform', 'workshop');
2643 $task->link = $workshop->editform_url();
2644 if ($workshop->grading_strategy_instance()->form_ready()) {
2645 $task->completed = true;
2646 } elseif ($workshop->phase > workshop::PHASE_SETUP) {
2647 $task->completed = false;
2649 $phase->tasks['editform'] = $task;
2651 if ($workshop->useexamples and has_capability('mod/workshop:manageexamples', $workshop->context, $userid)) {
2652 $task = new stdclass();
2653 $task->title = get_string('prepareexamples', 'workshop');
2654 if ($DB->count_records('workshop_submissions', array('example' => 1, 'workshopid' => $workshop->id)) > 0) {
2655 $task->completed = true;
2656 } elseif ($workshop->phase > workshop::PHASE_SETUP) {
2657 $task->completed = false;
2659 $phase->tasks['prepareexamples'] = $task;
2661 if (empty($phase->tasks) and $workshop->phase == workshop::PHASE_SETUP) {
2662 // if we are in the setup phase and there is no task (typical for students), let us
2663 // display some explanation what is going on
2664 $task = new stdclass();
2665 $task->title = get_string('undersetup', 'workshop');
2666 $task->completed = 'info';
2667 $phase->tasks['setupinfo'] = $task;
2669 $this->phases[workshop::PHASE_SETUP] = $phase;
2671 //---------------------------------------------------------
2672 // setup | * SUBMISSION | assessment | evaluation | closed
2673 //---------------------------------------------------------
2674 $phase = new stdclass();
2675 $phase->title = get_string('phasesubmission', 'workshop');
2676 $phase->tasks = array();
2677 if (has_capability('moodle/course:manageactivities', $workshop->context, $userid)) {
2678 $task = new stdclass();
2679 $task->title = get_string('taskinstructreviewers', 'workshop');
2680 $task->link = $workshop->updatemod_url();
2681 if (trim($workshop->instructreviewers)) {
2682 $task->completed = true;
2683 } elseif ($workshop->phase >= workshop::PHASE_ASSESSMENT) {
2684 $task->completed = false;
2686 $phase->tasks['instructreviewers'] = $task;
2688 if ($workshop->useexamples and $workshop->examplesmode == workshop::EXAMPLES_BEFORE_SUBMISSION
2689 and has_capability('mod/workshop:submit', $workshop->context, $userid, false)
2690 and !has_capability('mod/workshop:manageexamples', $workshop->context, $userid)) {
2691 $task = new stdclass();
2692 $task->title = get_string('exampleassesstask', 'workshop');
2693 $examples = $this->get_examples();
2694 $a = new stdclass();
2695 $a->expected = count($examples);
2696 $a->assessed = 0;
2697 foreach ($examples as $exampleid => $example) {
2698 if (!is_null($example->grade)) {
2699 $a->assessed++;
2702 $task->details = get_string('exampleassesstaskdetails', 'workshop', $a);
2703 if ($a->assessed == $a->expected) {
2704 $task->completed = true;
2705 } elseif ($workshop->phase >= workshop::PHASE_ASSESSMENT) {
2706 $task->completed = false;
2708 $phase->tasks['examples'] = $task;
2710 if (has_capability('mod/workshop:submit', $workshop->context, $userid, false)) {
2711 $task = new stdclass();
2712 $task->title = get_string('tasksubmit', 'workshop');
2713 $task->link = $workshop->submission_url();
2714 if ($DB->record_exists('workshop_submissions', array('workshopid'=>$workshop->id, 'example'=>0, 'authorid'=>$userid))) {
2715 $task->completed = true;
2716 } elseif ($workshop->phase >= workshop::PHASE_ASSESSMENT) {
2717 $task->completed = false;
2718 } else {
2719 $task->completed = null; // still has a chance to submit
2721 $phase->tasks['submit'] = $task;
2723 if (has_capability('mod/workshop:allocate', $workshop->context, $userid)) {
2724 if ($workshop->phaseswitchassessment) {
2725 $task = new stdClass();
2726 $allocator = $DB->get_record('workshopallocation_scheduled', array('workshopid' => $workshop->id));
2727 if (empty($allocator)) {
2728 $task->completed = false;
2729 } else if ($allocator->enabled and is_null($allocator->resultstatus)) {
2730 $task->completed = true;
2731 } else if ($workshop->submissionend > time()) {
2732 $task->completed = null;
2733 } else {
2734 $task->completed = false;
2736 $task->title = get_string('setup', 'workshopallocation_scheduled');
2737 $task->link = $workshop->allocation_url('scheduled');
2738 $phase->tasks['allocatescheduled'] = $task;
2740 $task = new stdclass();
2741 $task->title = get_string('allocate', 'workshop');
2742 $task->link = $workshop->allocation_url();
2743 $numofauthors = $workshop->count_potential_authors(false);
2744 $numofsubmissions = $DB->count_records('workshop_submissions', array('workshopid'=>$workshop->id, 'example'=>0));
2745 $sql = 'SELECT COUNT(s.id) AS nonallocated
2746 FROM {workshop_submissions} s
2747 LEFT JOIN {workshop_assessments} a ON (a.submissionid=s.id)
2748 WHERE s.workshopid = :workshopid AND s.example=0 AND a.submissionid IS NULL';
2749 $params['workshopid'] = $workshop->id;
2750 $numnonallocated = $DB->count_records_sql($sql, $params);
2751 if ($numofsubmissions == 0) {
2752 $task->completed = null;
2753 } elseif ($numnonallocated == 0) {
2754 $task->completed = true;
2755 } elseif ($workshop->phase > workshop::PHASE_SUBMISSION) {
2756 $task->completed = false;
2757 } else {
2758 $task->completed = null; // still has a chance to allocate
2760 $a = new stdclass();
2761 $a->expected = $numofauthors;
2762 $a->submitted = $numofsubmissions;
2763 $a->allocate = $numnonallocated;
2764 $task->details = get_string('allocatedetails', 'workshop', $a);
2765 unset($a);
2766 $phase->tasks['allocate'] = $task;
2768 if ($numofsubmissions < $numofauthors and $workshop->phase >= workshop::PHASE_SUBMISSION) {
2769 $task = new stdclass();
2770 $task->title = get_string('someuserswosubmission', 'workshop');
2771 $task->completed = 'info';
2772 $phase->tasks['allocateinfo'] = $task;
2776 if ($workshop->submissionstart) {
2777 $task = new stdclass();
2778 $task->title = get_string('submissionstartdatetime', 'workshop', workshop::timestamp_formats($workshop->submissionstart));
2779 $task->completed = 'info';
2780 $phase->tasks['submissionstartdatetime'] = $task;
2782 if ($workshop->submissionend) {
2783 $task = new stdclass();
2784 $task->title = get_string('submissionenddatetime', 'workshop', workshop::timestamp_formats($workshop->submissionend));
2785 $task->completed = 'info';
2786 $phase->tasks['submissionenddatetime'] = $task;
2788 if (($workshop->submissionstart < time()) and $workshop->latesubmissions) {
2789 $task = new stdclass();
2790 $task->title = get_string('latesubmissionsallowed', 'workshop');
2791 $task->completed = 'info';
2792 $phase->tasks['latesubmissionsallowed'] = $task;
2794 if (isset($phase->tasks['submissionstartdatetime']) or isset($phase->tasks['submissionenddatetime'])) {
2795 if (has_capability('mod/workshop:ignoredeadlines', $workshop->context, $userid)) {
2796 $task = new stdclass();
2797 $task->title = get_string('deadlinesignored', 'workshop');
2798 $task->completed = 'info';
2799 $phase->tasks['deadlinesignored'] = $task;
2802 $this->phases[workshop::PHASE_SUBMISSION] = $phase;
2804 //---------------------------------------------------------
2805 // setup | submission | * ASSESSMENT | evaluation | closed
2806 //---------------------------------------------------------
2807 $phase = new stdclass();
2808 $phase->title = get_string('phaseassessment', 'workshop');
2809 $phase->tasks = array();
2810 $phase->isreviewer = has_capability('mod/workshop:peerassess', $workshop->context, $userid);
2811 if ($workshop->phase == workshop::PHASE_SUBMISSION and $workshop->phaseswitchassessment
2812 and has_capability('mod/workshop:switchphase', $workshop->context, $userid)) {
2813 $task = new stdClass();
2814 $task->title = get_string('switchphase30auto', 'mod_workshop', workshop::timestamp_formats($workshop->submissionend));
2815 $task->completed = 'info';
2816 $phase->tasks['autoswitchinfo'] = $task;
2818 if ($workshop->useexamples and $workshop->examplesmode == workshop::EXAMPLES_BEFORE_ASSESSMENT
2819 and $phase->isreviewer and !has_capability('mod/workshop:manageexamples', $workshop->context, $userid)) {
2820 $task = new stdclass();
2821 $task->title = get_string('exampleassesstask', 'workshop');
2822 $examples = $workshop->get_examples_for_reviewer($userid);
2823 $a = new stdclass();
2824 $a->expected = count($examples);
2825 $a->assessed = 0;
2826 foreach ($examples as $exampleid => $example) {
2827 if (!is_null($example->grade)) {
2828 $a->assessed++;
2831 $task->details = get_string('exampleassesstaskdetails', 'workshop', $a);
2832 if ($a->assessed == $a->expected) {
2833 $task->completed = true;
2834 } elseif ($workshop->phase > workshop::PHASE_ASSESSMENT) {
2835 $task->completed = false;
2837 $phase->tasks['examples'] = $task;
2839 if (empty($phase->tasks['examples']) or !empty($phase->tasks['examples']->completed)) {
2840 $phase->assessments = $workshop->get_assessments_by_reviewer($userid);
2841 $numofpeers = 0; // number of allocated peer-assessments
2842 $numofpeerstodo = 0; // number of peer-assessments to do
2843 $numofself = 0; // number of allocated self-assessments - should be 0 or 1
2844 $numofselftodo = 0; // number of self-assessments to do - should be 0 or 1
2845 foreach ($phase->assessments as $a) {
2846 if ($a->authorid == $userid) {
2847 $numofself++;
2848 if (is_null($a->grade)) {
2849 $numofselftodo++;
2851 } else {
2852 $numofpeers++;
2853 if (is_null($a->grade)) {
2854 $numofpeerstodo++;
2858 unset($a);
2859 if ($numofpeers) {
2860 $task = new stdclass();
2861 if ($numofpeerstodo == 0) {
2862 $task->completed = true;
2863 } elseif ($workshop->phase > workshop::PHASE_ASSESSMENT) {
2864 $task->completed = false;
2866 $a = new stdclass();
2867 $a->total = $numofpeers;
2868 $a->todo = $numofpeerstodo;
2869 $task->title = get_string('taskassesspeers', 'workshop');
2870 $task->details = get_string('taskassesspeersdetails', 'workshop', $a);
2871 unset($a);
2872 $phase->tasks['assesspeers'] = $task;
2874 if ($workshop->useselfassessment and $numofself) {
2875 $task = new stdclass();
2876 if ($numofselftodo == 0) {
2877 $task->completed = true;
2878 } elseif ($workshop->phase > workshop::PHASE_ASSESSMENT) {
2879 $task->completed = false;
2881 $task->title = get_string('taskassessself', 'workshop');
2882 $phase->tasks['assessself'] = $task;
2885 if ($workshop->assessmentstart) {
2886 $task = new stdclass();
2887 $task->title = get_string('assessmentstartdatetime', 'workshop', workshop::timestamp_formats($workshop->assessmentstart));
2888 $task->completed = 'info';
2889 $phase->tasks['assessmentstartdatetime'] = $task;
2891 if ($workshop->assessmentend) {
2892 $task = new stdclass();
2893 $task->title = get_string('assessmentenddatetime', 'workshop', workshop::timestamp_formats($workshop->assessmentend));
2894 $task->completed = 'info';
2895 $phase->tasks['assessmentenddatetime'] = $task;
2897 if (isset($phase->tasks['assessmentstartdatetime']) or isset($phase->tasks['assessmentenddatetime'])) {
2898 if (has_capability('mod/workshop:ignoredeadlines', $workshop->context, $userid)) {
2899 $task = new stdclass();
2900 $task->title = get_string('deadlinesignored', 'workshop');
2901 $task->completed = 'info';
2902 $phase->tasks['deadlinesignored'] = $task;
2905 $this->phases[workshop::PHASE_ASSESSMENT] = $phase;
2907 //---------------------------------------------------------
2908 // setup | submission | assessment | * EVALUATION | closed
2909 //---------------------------------------------------------
2910 $phase = new stdclass();
2911 $phase->title = get_string('phaseevaluation', 'workshop');
2912 $phase->tasks = array();
2913 if (has_capability('mod/workshop:overridegrades', $workshop->context)) {
2914 $expected = $workshop->count_potential_authors(false);
2915 $calculated = $DB->count_records_select('workshop_submissions',
2916 'workshopid = ? AND (grade IS NOT NULL OR gradeover IS NOT NULL)', array($workshop->id));
2917 $task = new stdclass();
2918 $task->title = get_string('calculatesubmissiongrades', 'workshop');
2919 $a = new stdclass();
2920 $a->expected = $expected;
2921 $a->calculated = $calculated;
2922 $task->details = get_string('calculatesubmissiongradesdetails', 'workshop', $a);
2923 if ($calculated >= $expected) {
2924 $task->completed = true;
2925 } elseif ($workshop->phase > workshop::PHASE_EVALUATION) {
2926 $task->completed = false;
2928 $phase->tasks['calculatesubmissiongrade'] = $task;
2930 $expected = $workshop->count_potential_reviewers(false);
2931 $calculated = $DB->count_records_select('workshop_aggregations',
2932 'workshopid = ? AND gradinggrade IS NOT NULL', array($workshop->id));
2933 $task = new stdclass();
2934 $task->title = get_string('calculategradinggrades', 'workshop');
2935 $a = new stdclass();
2936 $a->expected = $expected;
2937 $a->calculated = $calculated;
2938 $task->details = get_string('calculategradinggradesdetails', 'workshop', $a);
2939 if ($calculated >= $expected) {
2940 $task->completed = true;
2941 } elseif ($workshop->phase > workshop::PHASE_EVALUATION) {
2942 $task->completed = false;
2944 $phase->tasks['calculategradinggrade'] = $task;
2946 } elseif ($workshop->phase == workshop::PHASE_EVALUATION) {
2947 $task = new stdclass();
2948 $task->title = get_string('evaluategradeswait', 'workshop');
2949 $task->completed = 'info';
2950 $phase->tasks['evaluateinfo'] = $task;
2953 if (has_capability('moodle/course:manageactivities', $workshop->context, $userid)) {
2954 $task = new stdclass();
2955 $task->title = get_string('taskconclusion', 'workshop');
2956 $task->link = $workshop->updatemod_url();
2957 if (trim($workshop->conclusion)) {
2958 $task->completed = true;
2959 } elseif ($workshop->phase >= workshop::PHASE_EVALUATION) {
2960 $task->completed = false;
2962 $phase->tasks['conclusion'] = $task;
2965 $this->phases[workshop::PHASE_EVALUATION] = $phase;
2967 //---------------------------------------------------------
2968 // setup | submission | assessment | evaluation | * CLOSED
2969 //---------------------------------------------------------
2970 $phase = new stdclass();
2971 $phase->title = get_string('phaseclosed', 'workshop');
2972 $phase->tasks = array();
2973 $this->phases[workshop::PHASE_CLOSED] = $phase;
2975 // Polish data, set default values if not done explicitly
2976 foreach ($this->phases as $phasecode => $phase) {
2977 $phase->title = isset($phase->title) ? $phase->title : '';
2978 $phase->tasks = isset($phase->tasks) ? $phase->tasks : array();
2979 if ($phasecode == $workshop->phase) {
2980 $phase->active = true;
2981 } else {
2982 $phase->active = false;
2984 if (!isset($phase->actions)) {
2985 $phase->actions = array();
2988 foreach ($phase->tasks as $taskcode => $task) {
2989 $task->title = isset($task->title) ? $task->title : '';
2990 $task->link = isset($task->link) ? $task->link : null;
2991 $task->details = isset($task->details) ? $task->details : '';
2992 $task->completed = isset($task->completed) ? $task->completed : null;
2996 // Add phase switching actions
2997 if (has_capability('mod/workshop:switchphase', $workshop->context, $userid)) {
2998 foreach ($this->phases as $phasecode => $phase) {
2999 if (! $phase->active) {
3000 $action = new stdclass();
3001 $action->type = 'switchphase';
3002 $action->url = $workshop->switchphase_url($phasecode);
3003 $phase->actions[] = $action;
3010 * Returns example submissions to be assessed by the owner of the planner
3012 * This is here to cache the DB query because the same list is needed later in view.php
3014 * @see workshop::get_examples_for_reviewer() for the format of returned value
3015 * @return array
3017 public function get_examples() {
3018 if (is_null($this->examples)) {
3019 $this->examples = $this->workshop->get_examples_for_reviewer($this->userid);
3021 return $this->examples;
3026 * Common base class for submissions and example submissions rendering
3028 * Subclasses of this class convert raw submission record from
3029 * workshop_submissions table (as returned by {@see workshop::get_submission_by_id()}
3030 * for example) into renderable objects.
3032 abstract class workshop_submission_base {
3034 /** @var bool is the submission anonymous (i.e. contains author information) */
3035 protected $anonymous;
3037 /* @var array of columns from workshop_submissions that are assigned as properties */
3038 protected $fields = array();
3040 /** @var workshop */
3041 protected $workshop;
3044 * Copies the properties of the given database record into properties of $this instance
3046 * @param workshop $workshop
3047 * @param stdClass $submission full record
3048 * @param bool $showauthor show the author-related information
3049 * @param array $options additional properties
3051 public function __construct(workshop $workshop, stdClass $submission, $showauthor = false) {
3053 $this->workshop = $workshop;
3055 foreach ($this->fields as $field) {
3056 if (!property_exists($submission, $field)) {
3057 throw new coding_exception('Submission record must provide public property ' . $field);
3059 if (!property_exists($this, $field)) {
3060 throw new coding_exception('Renderable component must accept public property ' . $field);
3062 $this->{$field} = $submission->{$field};
3065 if ($showauthor) {
3066 $this->anonymous = false;
3067 } else {
3068 $this->anonymize();
3073 * Unsets all author-related properties so that the renderer does not have access to them
3075 * Usually this is called by the contructor but can be called explicitely, too.
3077 public function anonymize() {
3078 foreach (array('authorid', 'authorfirstname', 'authorlastname',
3079 'authorpicture', 'authorimagealt', 'authoremail') as $field) {
3080 unset($this->{$field});
3082 $this->anonymous = true;
3086 * Does the submission object contain author-related information?
3088 * @return null|boolean
3090 public function is_anonymous() {
3091 return $this->anonymous;
3096 * Renderable object containing a basic set of information needed to display the submission summary
3098 * @see workshop_renderer::render_workshop_submission_summary
3100 class workshop_submission_summary extends workshop_submission_base implements renderable {
3102 /** @var int */
3103 public $id;
3104 /** @var string */
3105 public $title;
3106 /** @var string graded|notgraded */
3107 public $status;
3108 /** @var int */
3109 public $timecreated;
3110 /** @var int */
3111 public $timemodified;
3112 /** @var int */
3113 public $authorid;
3114 /** @var string */
3115 public $authorfirstname;
3116 /** @var string */
3117 public $authorlastname;
3118 /** @var int */
3119 public $authorpicture;
3120 /** @var string */
3121 public $authorimagealt;
3122 /** @var string */
3123 public $authoremail;
3124 /** @var moodle_url to display submission */
3125 public $url;
3128 * @var array of columns from workshop_submissions that are assigned as properties
3129 * of instances of this class
3131 protected $fields = array(
3132 'id', 'title', 'timecreated', 'timemodified',
3133 'authorid', 'authorfirstname', 'authorlastname', 'authorpicture',
3134 'authorimagealt', 'authoremail');
3138 * Renderable object containing all the information needed to display the submission
3140 * @see workshop_renderer::render_workshop_submission()
3142 class workshop_submission extends workshop_submission_summary implements renderable {
3144 /** @var string */
3145 public $content;
3146 /** @var int */
3147 public $contentformat;
3148 /** @var bool */
3149 public $contenttrust;
3150 /** @var array */
3151 public $attachment;
3154 * @var array of columns from workshop_submissions that are assigned as properties
3155 * of instances of this class
3157 protected $fields = array(
3158 'id', 'title', 'timecreated', 'timemodified', 'content', 'contentformat', 'contenttrust',
3159 'attachment', 'authorid', 'authorfirstname', 'authorlastname', 'authorpicture',
3160 'authorimagealt', 'authoremail');
3164 * Renderable object containing a basic set of information needed to display the example submission summary
3166 * @see workshop::prepare_example_summary()
3167 * @see workshop_renderer::render_workshop_example_submission_summary()
3169 class workshop_example_submission_summary extends workshop_submission_base implements renderable {
3171 /** @var int */
3172 public $id;
3173 /** @var string */
3174 public $title;
3175 /** @var string graded|notgraded */
3176 public $status;
3177 /** @var stdClass */
3178 public $gradeinfo;
3179 /** @var moodle_url */
3180 public $url;
3181 /** @var moodle_url */
3182 public $editurl;
3183 /** @var string */
3184 public $assesslabel;
3185 /** @var moodle_url */
3186 public $assessurl;
3187 /** @var bool must be set explicitly by the caller */
3188 public $editable = false;
3191 * @var array of columns from workshop_submissions that are assigned as properties
3192 * of instances of this class
3194 protected $fields = array('id', 'title');
3197 * Example submissions are always anonymous
3199 * @return true
3201 public function is_anonymous() {
3202 return true;
3207 * Renderable object containing all the information needed to display the example submission
3209 * @see workshop_renderer::render_workshop_example_submission()
3211 class workshop_example_submission extends workshop_example_submission_summary implements renderable {
3213 /** @var string */
3214 public $content;
3215 /** @var int */
3216 public $contentformat;
3217 /** @var bool */
3218 public $contenttrust;
3219 /** @var array */
3220 public $attachment;
3223 * @var array of columns from workshop_submissions that are assigned as properties
3224 * of instances of this class
3226 protected $fields = array('id', 'title', 'content', 'contentformat', 'contenttrust', 'attachment');
3231 * Common base class for assessments rendering
3233 * Subclasses of this class convert raw assessment record from
3234 * workshop_assessments table (as returned by {@see workshop::get_assessment_by_id()}
3235 * for example) into renderable objects.
3237 abstract class workshop_assessment_base {
3239 /** @var string the optional title of the assessment */
3240 public $title = '';
3242 /** @var workshop_assessment_form $form as returned by {@link workshop_strategy::get_assessment_form()} */
3243 public $form;
3245 /** @var moodle_url */
3246 public $url;
3248 /** @var float|null the real received grade */
3249 public $realgrade = null;
3251 /** @var float the real maximum grade */
3252 public $maxgrade;
3254 /** @var stdClass|null reviewer user info */
3255 public $reviewer = null;
3257 /** @var stdClass|null assessed submission's author user info */
3258 public $author = null;
3260 /** @var array of actions */
3261 public $actions = array();
3263 /* @var array of columns that are assigned as properties */
3264 protected $fields = array();
3266 /** @var workshop */
3267 protected $workshop;
3270 * Copies the properties of the given database record into properties of $this instance
3272 * The $options keys are: showreviewer, showauthor
3273 * @param workshop $workshop
3274 * @param stdClass $assessment full record
3275 * @param array $options additional properties
3277 public function __construct(workshop $workshop, stdClass $record, array $options = array()) {
3279 $this->workshop = $workshop;
3280 $this->validate_raw_record($record);
3282 foreach ($this->fields as $field) {
3283 if (!property_exists($record, $field)) {
3284 throw new coding_exception('Assessment record must provide public property ' . $field);
3286 if (!property_exists($this, $field)) {
3287 throw new coding_exception('Renderable component must accept public property ' . $field);
3289 $this->{$field} = $record->{$field};
3292 if (!empty($options['showreviewer'])) {
3293 $this->reviewer = user_picture::unalias($record, null, 'revieweridx', 'reviewer');
3296 if (!empty($options['showauthor'])) {
3297 $this->author = user_picture::unalias($record, null, 'authorid', 'author');
3302 * Adds a new action
3304 * @param moodle_url $url action URL
3305 * @param string $label action label
3306 * @param string $method get|post
3308 public function add_action(moodle_url $url, $label, $method = 'get') {
3310 $action = new stdClass();
3311 $action->url = $url;
3312 $action->label = $label;
3313 $action->method = $method;
3315 $this->actions[] = $action;
3319 * Makes sure that we can cook the renderable component from the passed raw database record
3321 * @param stdClass $assessment full assessment record
3322 * @throws coding_exception if the caller passed unexpected data
3324 protected function validate_raw_record(stdClass $record) {
3325 // nothing to do here
3331 * Represents a rendarable full assessment
3333 class workshop_assessment extends workshop_assessment_base implements renderable {
3335 /** @var int */
3336 public $id;
3338 /** @var int */
3339 public $submissionid;
3341 /** @var int */
3342 public $weight;
3344 /** @var int */
3345 public $timecreated;
3347 /** @var int */
3348 public $timemodified;
3350 /** @var float */
3351 public $grade;
3353 /** @var float */
3354 public $gradinggrade;
3356 /** @var float */
3357 public $gradinggradeover;
3359 /** @var string */
3360 public $feedbackauthor;
3362 /** @var int */
3363 public $feedbackauthorformat;
3365 /** @var int */
3366 public $feedbackauthorattachment;
3368 /** @var array */
3369 protected $fields = array('id', 'submissionid', 'weight', 'timecreated',
3370 'timemodified', 'grade', 'gradinggrade', 'gradinggradeover', 'feedbackauthor',
3371 'feedbackauthorformat', 'feedbackauthorattachment');
3374 * Format the overall feedback text content
3376 * False is returned if the overall feedback feature is disabled. Null is returned
3377 * if the overall feedback content has not been found. Otherwise, string with
3378 * formatted feedback text is returned.
3380 * @return string|bool|null
3382 public function get_overall_feedback_content() {
3384 if ($this->workshop->overallfeedbackmode == 0) {
3385 return false;
3388 if (trim($this->feedbackauthor) === '') {
3389 return null;
3392 $content = format_text($this->feedbackauthor, $this->feedbackauthorformat,
3393 array('overflowdiv' => true, 'context' => $this->workshop->context));
3394 $content = file_rewrite_pluginfile_urls($content, 'pluginfile.php', $this->workshop->context->id,
3395 'mod_workshop', 'overallfeedback_content', $this->id);
3397 return $content;
3401 * Prepares the list of overall feedback attachments
3403 * Returns false if overall feedback attachments are not allowed. Otherwise returns
3404 * list of attachments (may be empty).
3406 * @return bool|array of stdClass
3408 public function get_overall_feedback_attachments() {
3410 if ($this->workshop->overallfeedbackmode == 0) {
3411 return false;
3414 if ($this->workshop->overallfeedbackfiles == 0) {
3415 return false;
3418 if (empty($this->feedbackauthorattachment)) {
3419 return array();
3422 $attachments = array();
3423 $fs = get_file_storage();
3424 $files = $fs->get_area_files($this->workshop->context->id, 'mod_workshop', 'overallfeedback_attachment', $this->id);
3425 foreach ($files as $file) {
3426 if ($file->is_directory()) {
3427 continue;
3429 $filepath = $file->get_filepath();
3430 $filename = $file->get_filename();
3431 $fileurl = moodle_url::make_pluginfile_url($this->workshop->context->id, 'mod_workshop',
3432 'overallfeedback_attachment', $this->id, $filepath, $filename, true);
3433 $previewurl = new moodle_url(moodle_url::make_pluginfile_url($this->workshop->context->id, 'mod_workshop',
3434 'overallfeedback_attachment', $this->id, $filepath, $filename, false), array('preview' => 'bigthumb'));
3435 $attachments[] = (object)array(
3436 'filepath' => $filepath,
3437 'filename' => $filename,
3438 'fileurl' => $fileurl,
3439 'previewurl' => $previewurl,
3440 'mimetype' => $file->get_mimetype(),
3445 return $attachments;
3451 * Represents a renderable training assessment of an example submission
3453 class workshop_example_assessment extends workshop_assessment implements renderable {
3456 * @see parent::validate_raw_record()
3458 protected function validate_raw_record(stdClass $record) {
3459 if ($record->weight != 0) {
3460 throw new coding_exception('Invalid weight of example submission assessment');
3462 parent::validate_raw_record($record);
3468 * Represents a renderable reference assessment of an example submission
3470 class workshop_example_reference_assessment extends workshop_assessment implements renderable {
3473 * @see parent::validate_raw_record()
3475 protected function validate_raw_record(stdClass $record) {
3476 if ($record->weight != 1) {
3477 throw new coding_exception('Invalid weight of the reference example submission assessment');
3479 parent::validate_raw_record($record);
3485 * Renderable message to be displayed to the user
3487 * Message can contain an optional action link with a label that is supposed to be rendered
3488 * as a button or a link.
3490 * @see workshop::renderer::render_workshop_message()
3492 class workshop_message implements renderable {
3494 const TYPE_INFO = 10;
3495 const TYPE_OK = 20;
3496 const TYPE_ERROR = 30;
3498 /** @var string */
3499 protected $text = '';
3500 /** @var int */
3501 protected $type = self::TYPE_INFO;
3502 /** @var moodle_url */
3503 protected $actionurl = null;
3504 /** @var string */
3505 protected $actionlabel = '';
3508 * @param string $text short text to be displayed
3509 * @param string $type optional message type info|ok|error
3511 public function __construct($text = null, $type = self::TYPE_INFO) {
3512 $this->set_text($text);
3513 $this->set_type($type);
3517 * Sets the message text
3519 * @param string $text short text to be displayed
3521 public function set_text($text) {
3522 $this->text = $text;
3526 * Sets the message type
3528 * @param int $type
3530 public function set_type($type = self::TYPE_INFO) {
3531 if (in_array($type, array(self::TYPE_OK, self::TYPE_ERROR, self::TYPE_INFO))) {
3532 $this->type = $type;
3533 } else {
3534 throw new coding_exception('Unknown message type.');
3539 * Sets the optional message action
3541 * @param moodle_url $url to follow on action
3542 * @param string $label action label
3544 public function set_action(moodle_url $url, $label) {
3545 $this->actionurl = $url;
3546 $this->actionlabel = $label;
3550 * Returns message text with HTML tags quoted
3552 * @return string
3554 public function get_message() {
3555 return s($this->text);
3559 * Returns message type
3561 * @return int
3563 public function get_type() {
3564 return $this->type;
3568 * Returns action URL
3570 * @return moodle_url|null
3572 public function get_action_url() {
3573 return $this->actionurl;
3577 * Returns action label
3579 * @return string
3581 public function get_action_label() {
3582 return $this->actionlabel;
3588 * Renderable component containing all the data needed to display the grading report
3590 class workshop_grading_report implements renderable {
3592 /** @var stdClass returned by {@see workshop::prepare_grading_report_data()} */
3593 protected $data;
3594 /** @var stdClass rendering options */
3595 protected $options;
3598 * Grades in $data must be already rounded to the set number of decimals or must be null
3599 * (in which later case, the [mod_workshop,nullgrade] string shall be displayed)
3601 * @param stdClass $data prepared by {@link workshop::prepare_grading_report_data()}
3602 * @param stdClass $options display options (showauthornames, showreviewernames, sortby, sorthow, showsubmissiongrade, showgradinggrade)
3604 public function __construct(stdClass $data, stdClass $options) {
3605 $this->data = $data;
3606 $this->options = $options;
3610 * @return stdClass grading report data
3612 public function get_data() {
3613 return $this->data;
3617 * @return stdClass rendering options
3619 public function get_options() {
3620 return $this->options;
3626 * Base class for renderable feedback for author and feedback for reviewer
3628 abstract class workshop_feedback {
3630 /** @var stdClass the user info */
3631 protected $provider = null;
3633 /** @var string the feedback text */
3634 protected $content = null;
3636 /** @var int format of the feedback text */
3637 protected $format = null;
3640 * @return stdClass the user info
3642 public function get_provider() {
3644 if (is_null($this->provider)) {
3645 throw new coding_exception('Feedback provider not set');
3648 return $this->provider;
3652 * @return string the feedback text
3654 public function get_content() {
3656 if (is_null($this->content)) {
3657 throw new coding_exception('Feedback content not set');
3660 return $this->content;
3664 * @return int format of the feedback text
3666 public function get_format() {
3668 if (is_null($this->format)) {
3669 throw new coding_exception('Feedback text format not set');
3672 return $this->format;
3678 * Renderable feedback for the author of submission
3680 class workshop_feedback_author extends workshop_feedback implements renderable {
3683 * Extracts feedback from the given submission record
3685 * @param stdClass $submission record as returned by {@see self::get_submission_by_id()}
3687 public function __construct(stdClass $submission) {
3689 $this->provider = user_picture::unalias($submission, null, 'gradeoverbyx', 'gradeoverby');
3690 $this->content = $submission->feedbackauthor;
3691 $this->format = $submission->feedbackauthorformat;
3697 * Renderable feedback for the reviewer
3699 class workshop_feedback_reviewer extends workshop_feedback implements renderable {
3702 * Extracts feedback from the given assessment record
3704 * @param stdClass $assessment record as returned by eg {@see self::get_assessment_by_id()}
3706 public function __construct(stdClass $assessment) {
3708 $this->provider = user_picture::unalias($assessment, null, 'gradinggradeoverbyx', 'overby');
3709 $this->content = $assessment->feedbackreviewer;
3710 $this->format = $assessment->feedbackreviewerformat;
3716 * Holds the final grades for the activity as are stored in the gradebook
3718 class workshop_final_grades implements renderable {
3720 /** @var object the info from the gradebook about the grade for submission */
3721 public $submissiongrade = null;
3723 /** @var object the infor from the gradebook about the grade for assessment */
3724 public $assessmentgrade = null;