3 // This file is part of Moodle - http://moodle.org/
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.
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/>.
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.
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');
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.
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 */
65 /** @var stdclass course record */
68 /** @var stdclass context object */
71 /** @var int workshop instance identifier */
74 /** @var string workshop activity name */
77 /** @var string introduction or description of the activity */
80 /** @var int format of the {@link $intro} */
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 */
98 /** @var int current phase of workshop, for example {@link workshop::PHASE_SETUP} */
101 /** @var bool optional feature: students practise evaluating on example submissions from teacher */
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 */
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' */
119 /** @var string the name of the evaluation plugin to use for grading grades calculation */
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 */
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 */
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;
197 $this->course
= $course;
198 if (is_null($context)) {
199 $this->context
= context_module
::instance($this->cm
->id
);
201 $this->context
= $context;
205 ////////////////////////////////////////////////////////////////////////////////
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');
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);
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
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
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() {
265 for ($i=100; $i>=0; $i--) {
272 * Returns the localized list of supported examples modes
276 public static function available_example_modes_list() {
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');
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');
292 foreach ($installed as $strategy => $strategypath) {
293 if (file_exists($strategypath . '/lib.php')) {
294 $forms[$strategy] = get_string('pluginname', 'workshopform_' . $strategy);
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() {
307 foreach (core_component
::get_plugin_list_with_file('workshopeval', 'lib.php', false) as $eval => $evalpath) {
308 $evals[$eval] = get_string('pluginname', 'workshopeval_' . $eval);
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() {
320 for ($i=16; $i>=0; $i--) {
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() {
339 for ($i=16; $i>=0; $i--) {
346 * Helper function returning the greatest common divisor
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
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
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');
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);
405 ////////////////////////////////////////////////////////////////////////////////
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) {
424 list($sql, $params) = $this->get_users_with_capability_sql('mod/workshop:submit', $musthavesubmission, $groupid);
430 list($sort, $sortparams) = users_order_by_sql('tmp');
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
445 public function count_potential_authors($musthavesubmission=true, $groupid=0) {
448 list($sql, $params) = $this->get_users_with_capability_sql('mod/workshop:submit', $musthavesubmission, $groupid);
454 $sql = "SELECT COUNT(*)
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) {
475 list($sql, $params) = $this->get_users_with_capability_sql('mod/workshop:peerassess', $musthavesubmission, $groupid);
481 list($sort, $sortparams) = users_order_by_sql('tmp');
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
496 public function count_potential_reviewers($musthavesubmission=false, $groupid=0) {
499 list($sql, $params) = $this->get_users_with_capability_sql('mod/workshop:peerassess', $musthavesubmission, $groupid);
505 $sql = "SELECT COUNT(*)
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) {
528 list($sql, $params) = $this->get_participants_sql($musthavesubmission, $groupid);
534 list($sort, $sortparams) = users_order_by_sql('tmp');
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
549 public function count_participants($musthavesubmission=false, $groupid=0) {
552 list($sql, $params) = $this->get_participants_sql($musthavesubmission, $groupid);
558 $sql = "SELECT COUNT(*)
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
570 public function is_participant($userid=null) {
573 if (is_null($userid)) {
577 list($sql, $params) = $this->get_participants_sql();
583 $sql = "SELECT COUNT(*)
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)) {
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) {
610 $grouped = array(); // grouped users to be returned
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();
624 // there is no need to be member of a group so $grouped[0] will contain
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
];
641 * Returns the list of all allocations (i.e. assigned assessments) in the workshop
643 * Assessments of example submissions are ignored
647 public function get_allocations() {
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) {
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)";
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);
686 // $authorid is empty
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) {
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)";
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);
732 // $authorid is empty
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
747 public function get_submission_by_id($id) {
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) {
772 if (empty($authorid)) {
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') {
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,
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
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
812 public function get_example_by_id($id) {
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() {
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
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) {
846 if (empty($reviewerid)) {
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
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
);
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
);
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);
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');
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()));
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
);
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;
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;
1043 * Removes the submission and all relevant data
1045 * @param stdClass $submission record to delete
1048 public function delete_submission(stdclass
$submission) {
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
1062 * @return array [assessmentid] => assessment stdclass
1064 public function get_all_assessments() {
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,
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
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
1093 public function get_assessment_by_id($id) {
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) {
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
1142 public function get_assessments_of_submission($submissionid) {
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
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
1167 public function get_assessments_by_reviewer($reviewerid) {
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
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]);
1204 if (!empty($exclude)) {
1205 if (is_array($exclude) and in_array($id, $exclude)) {
1206 unset($assessments[$id]);
1208 } else if ($id == $exclude) {
1209 unset($assessments[$id]);
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) {
1230 if ($DB->record_exists('workshop_assessments', array('submissionid' => $submission->id
, 'reviewerid' => $reviewerid))) {
1231 return self
::ALLOCATION_EXISTS
;
1234 $weight = (int)$weight;
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) {
1263 // todo remove all given grades from workshop_grades;
1265 if (is_array($id)) {
1266 return $DB->delete_records_list('workshop_assessments', 'id', $id);
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);
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) {
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
));
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);
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);
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);
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() {
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() {
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() {
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) {
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) {
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) {
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) {
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) {
1441 $url = new moodle_url('/mod/workshop/compare.php', array('cmid' => $this->cm
->id
, 'sid' => $sid));
1443 foreach ($aids as $aid) {
1444 $url->param("aid{$i}", $aid);
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) {
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() {
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) {
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) {
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() {
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) {
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)) {
1526 $logurl = $this->log_convert_url($url);
1527 $args = array($this->course
->id
, 'workshop', $action, $logurl, $info, $this->cm
->id
);
1531 call_user_func_array('add_to_log', $args);
1535 * Is the given user allowed to create their submission?
1537 * @param int $userid
1540 public function creating_submission_allowed($userid) {
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
1550 if (!$ignoredeadlines and !empty($this->submissionstart
) and $this->submissionstart
> $now) {
1551 // late submissions are not allowed before the submission start
1557 if ($this->phase
!= self
::PHASE_SUBMISSION
) {
1558 // submissions are allowed during the submission phase only
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
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
1574 * Is the given user allowed to modify their existing submission?
1576 * @param int $userid
1579 public function modifying_submission_allowed($userid) {
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
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
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
1600 * Is the given reviewer allowed to create/edit their assessments?
1602 * @param int $userid
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)) {
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
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
1626 // here we go, assessing is allowed
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.
1639 public function assessing_examples_allowed() {
1640 if (empty($this->useexamples
)) {
1643 if (self
::EXAMPLES_VOLUNTARY
== $this->examplesmode
) {
1646 if (self
::EXAMPLES_BEFORE_SUBMISSION
== $this->examplesmode
and self
::PHASE_SUBMISSION
== $this->phase
) {
1649 if (self
::EXAMPLES_BEFORE_ASSESSMENT
== $this->examplesmode
and self
::PHASE_ASSESSMENT
== $this->phase
) {
1656 * Are the peer-reviews available to the authors?
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) {
1675 $known = $this->available_phases_list();
1676 if (!isset($known[$newphase])) {
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;
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) {
1707 if (is_null($grade)) {
1710 $data = new stdclass();
1711 $data->id
= $assessmentid;
1712 $data->grade
= $grade;
1713 $data->timemodified
= time();
1714 $DB->update_record('workshop_assessments', $data);
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) {
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?
1740 if (!in_array($sortby, array('lastname','firstname','submissiontitle','submissiongrade','gradinggrade'))) {
1741 $sortby = 'lastname';
1744 if (!($sorthow === 'ASC' or $sorthow === 'DESC')) {
1748 // get the list of user ids to be displayed
1750 $participants = $this->get_participants(false, $groupid);
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
;
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
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
1777 $participants = $DB->get_records_sql($sql, $params, $page * $perpage, $perpage);
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();
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
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);
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;
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;
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);
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
1953 public function real_grade_value($value, $max) {
1955 if (is_null($value) or $value === '') {
1957 } elseif ($max == 0) {
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 === '') {
1977 if ($max == 0 or $value < 0) {
1980 $p = $value / $max * 100;
1984 return grade_floatval($p);
1988 * Calculates the real value of grade for submission
1990 * @param float $value percentual value from 0 to 100
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
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.
2016 public function clear_assessments() {
2019 $submissions = $this->get_submissions();
2020 if (empty($submissions)) {
2021 // no money, no love
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)
2037 public function clear_submission_grades($restrict=null) {
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);
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)
2062 public function aggregate_submission_grades($restrict=null) {
2065 // fetch a recordset with all assessments to process
2066 $sql = 'SELECT s.id AS submissionid, s.grade AS submissiongrade,
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);
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;
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);
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)
2116 public function clear_grading_grades($restrict=null) {
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);
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)
2144 public function aggregate_grading_grades($restrict=null) {
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);
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;
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);
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()) {
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
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()) {
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
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) {
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
)) {
2319 * Return the editor options for the overall feedback for the author.
2323 public function overall_feedback_content_options() {
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.
2338 public function overall_feedback_attachment_options() {
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)
2360 protected function aggregate_submission_grades_process(array $assessments) {
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
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
2382 if ($assessment->weight
== 0) {
2383 // this does not influence the calculation
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
2413 protected function aggregate_grading_grades_process(array $assessments, $timegraded = null) {
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
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
;
2445 if (!is_null($assessment->gradinggrade
)) {
2446 $sumgrades +
= $assessment->gradinggrade
;
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);
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) {
2488 /** @var int static counter used to generate unique parameter holders */
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'));
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);
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
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)) {
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() {
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
2575 protected function log_convert_url(moodle_url
$fullurl) {
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 */
2601 /** @var 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) {
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);
2697 foreach ($examples as $exampleid => $example) {
2698 if (!is_null($example->grade
)) {
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;
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;
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;
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);
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);
2826 foreach ($examples as $exampleid => $example) {
2827 if (!is_null($example->grade
)) {
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) {
2848 if (is_null($a->grade
)) {
2853 if (is_null($a->grade
)) {
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);
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;
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
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};
3066 $this->anonymous
= false;
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
{
3106 /** @var string graded|notgraded */
3109 public $timecreated;
3111 public $timemodified;
3115 public $authorfirstname;
3117 public $authorlastname;
3119 public $authorpicture;
3121 public $authorimagealt;
3123 public $authoremail;
3124 /** @var moodle_url to display submission */
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
{
3147 public $contentformat;
3149 public $contenttrust;
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
{
3175 /** @var string graded|notgraded */
3177 /** @var stdClass */
3179 /** @var moodle_url */
3181 /** @var moodle_url */
3184 public $assesslabel;
3185 /** @var moodle_url */
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
3201 public function is_anonymous() {
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
{
3216 public $contentformat;
3218 public $contenttrust;
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 */
3242 /** @var workshop_assessment_form $form as returned by {@link workshop_strategy::get_assessment_form()} */
3245 /** @var moodle_url */
3248 /** @var float|null the real received grade */
3249 public $realgrade = null;
3251 /** @var float the real maximum grade */
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');
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
{
3339 public $submissionid;
3345 public $timecreated;
3348 public $timemodified;
3354 public $gradinggrade;
3357 public $gradinggradeover;
3360 public $feedbackauthor;
3363 public $feedbackauthorformat;
3366 public $feedbackauthorattachment;
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) {
3388 if (trim($this->feedbackauthor
) === '') {
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
);
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) {
3414 if ($this->workshop
->overallfeedbackfiles
== 0) {
3418 if (empty($this->feedbackauthorattachment
)) {
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()) {
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;
3496 const TYPE_ERROR
= 30;
3499 protected $text = '';
3501 protected $type = self
::TYPE_INFO
;
3502 /** @var moodle_url */
3503 protected $actionurl = null;
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
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;
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
3554 public function get_message() {
3555 return s($this->text
);
3559 * Returns message type
3563 public function get_type() {
3568 * Returns action URL
3570 * @return moodle_url|null
3572 public function get_action_url() {
3573 return $this->actionurl
;
3577 * Returns action label
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()} */
3594 /** @var stdClass rendering 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() {
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;