MDL-20058 totalgrade stuff removed from workshop code
[moodle.git] / mod / workshop / locallib.php
CommitLineData
de811c0c 1<?php
53fad4b9
DM
2
3// This file is part of Moodle - http://moodle.org/
4//
de811c0c
DM
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
53fad4b9 14//
de811c0c
DM
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
53fad4b9 17
de811c0c 18/**
6e309973 19 * Library of internal classes and functions for module workshop
de811c0c 20 *
53fad4b9 21 * All the workshop specific functions, needed to implement the module
6e309973 22 * logic, should go to here. Instead of having bunch of function named
53fad4b9 23 * workshop_something() taking the workshop instance as the first
a39d7d87 24 * parameter, we use a class workshop that provides all methods.
53fad4b9 25 *
de811c0c
DM
26 * @package mod-workshop
27 * @copyright 2009 David Mudrak <david.mudrak@gmail.com>
28 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
29 */
30
31defined('MOODLE_INTERNAL') || die();
32
89c1aa97
DM
33require_once(dirname(__FILE__).'/lib.php'); // we extend this library here
34require_once($CFG->libdir . '/gradelib.php'); // we use some rounding and comparing routines here
0968b1a3 35
6e309973
DM
36/**
37 * Full-featured workshop API
38 *
a39d7d87 39 * This wraps the workshop database record with a set of methods that are called
6e309973 40 * from the module itself. The class should be initialized right after you get
a39d7d87 41 * $workshop, $cm and $course records at the begining of the script.
6e309973 42 */
a39d7d87
DM
43class workshop {
44
b761e6d9 45 /** return statuses of {@link add_allocation} to be passed to a workshop renderer method */
f6e8b318
DM
46 const ALLOCATION_EXISTS = -1;
47 const ALLOCATION_ERROR = -2;
b761e6d9
DM
48
49 /** the internal code of the workshop phases as are stored in the database */
f6e8b318
DM
50 const PHASE_SETUP = 10;
51 const PHASE_SUBMISSION = 20;
52 const PHASE_ASSESSMENT = 30;
53 const PHASE_EVALUATION = 40;
54 const PHASE_CLOSED = 50;
55
56 /** the internal code of the examples modes as are stored in the database */
57 const EXAMPLES_VOLUNTARY = 0;
58 const EXAMPLES_BEFORE_SUBMISSION = 1;
59 const EXAMPLES_BEFORE_ASSESSMENT = 2;
b761e6d9 60
65ba104c 61 /** @var stdClass course module record */
a39d7d87
DM
62 public $cm = null;
63
65ba104c 64 /** @var stdClass course record */
a39d7d87 65 public $course = null;
6e309973 66
f6e8b318
DM
67 /** @var stdClass context object */
68 public $context = null;
69
b761e6d9
DM
70 /**
71 * @var workshop_strategy grading strategy instance
72 * Do not use directly, get the instance using {@link workshop::grading_strategy_instance()}
73 */
b13142da
DM
74 protected $strategyinstance = null;
75
45d24d39
DM
76 /**
77 * @var workshop_evaluation grading evaluation instance
78 * Do not use directly, get the instance using {@link workshop::grading_evaluation_instance()}
79 */
80 protected $evaluationinstance = null;
81
6e309973 82 /**
65ba104c 83 * Initializes the workshop API instance using the data from DB
a39d7d87
DM
84 *
85 * Makes deep copy of all passed records properties. Replaces integer $course attribute
86 * with a full database record (course should not be stored in instances table anyway).
6e309973 87 *
b13142da 88 * @param stdClass $dbrecord Workshop instance data from {workshop} table
06d73dd5
DM
89 * @param stdClass $cm Course module record as returned by {@link get_coursemodule_from_id()}
90 * @param stdClass $course Course record from {course} table
4efd7b5d 91 * @param stdClass $context The context of the workshop instance
0dc47fb9 92 */
4efd7b5d 93 public function __construct(stdClass $dbrecord, stdClass $cm, stdClass $course, stdClass $context=null) {
f05c168d
DM
94 foreach ($dbrecord as $field => $value) {
95 $this->{$field} = $value;
a39d7d87 96 }
45d24d39
DM
97 $this->cm = $cm;
98 $this->course = $course; // beware - this replaces the standard course field in the instance table
99 // this is intentional - IMO there should be no such field as it violates
4efd7b5d
DM
100 // 3rd normal form with no real performance gain
101 if (is_null($context)) {
102 $this->context = get_context_instance(CONTEXT_MODULE, $this->cm->id);
103 } else {
104 $this->context = $context;
105 }
106 $this->evaluation = 'best'; // todo make this configurable although we have no alternatives yet
6e309973
DM
107 }
108
aa40adbf
DM
109 ////////////////////////////////////////////////////////////////////////////////
110 // Static methods //
111 ////////////////////////////////////////////////////////////////////////////////
112
da0b1f70 113 /**
aa40adbf 114 * Return list of available allocation methods
da0b1f70 115 *
aa40adbf 116 * @return array Array ['string' => 'string'] of localized allocation method names
da0b1f70 117 */
aa40adbf
DM
118 public static function installed_allocators() {
119 $installed = get_plugin_list('workshopallocation');
120 $forms = array();
121 foreach ($installed as $allocation => $allocationpath) {
122 if (file_exists($allocationpath . '/lib.php')) {
123 $forms[$allocation] = get_string('pluginname', 'workshopallocation_' . $allocation);
124 }
f05c168d 125 }
aa40adbf
DM
126 // usability - make sure that manual allocation appears the first
127 if (isset($forms['manual'])) {
128 $m = array('manual' => $forms['manual']);
129 unset($forms['manual']);
130 $forms = array_merge($m, $forms);
da0b1f70 131 }
aa40adbf
DM
132 return $forms;
133 }
da0b1f70 134
aa40adbf
DM
135 /**
136 * Returns an array of options for the editors that are used for submitting and assessing instructions
137 *
138 * @param stdClass $context
139 * @return array
140 */
141 public static function instruction_editors_options(stdClass $context) {
142 return array('subdirs' => 1, 'maxbytes' => 0, 'maxfiles' => EDITOR_UNLIMITED_FILES,
143 'changeformat' => 1, 'context' => $context, 'noclean' => 1, 'trusttext' => 0);
da0b1f70
DM
144 }
145
61b737a5
DM
146 /**
147 * Given the percent and the total, returns the number
148 *
149 * @param float $percent from 0 to 100
150 * @param float $total the 100% value
151 * @return float
152 */
153 public static function percent_to_value($percent, $total) {
154 if ($percent < 0 or $percent > 100) {
155 throw new coding_exception('The percent can not be less than 0 or higher than 100');
156 }
157
158 return $total * $percent / 100;
159 }
160
f6e8b318
DM
161 /**
162 * Returns an array of numeric values that can be used as maximum grades
163 *
164 * @return array Array of integers
165 */
166 public static function available_maxgrades_list() {
167 $grades = array();
168 for ($i=100; $i>=0; $i--) {
169 $grades[$i] = $i;
170 }
171 return $grades;
172 }
173
174 /**
175 * Returns the localized list of supported examples modes
176 *
177 * @return array
178 */
179 public static function available_example_modes_list() {
180 $options = array();
181 $options[self::EXAMPLES_VOLUNTARY] = get_string('examplesvoluntary', 'workshop');
182 $options[self::EXAMPLES_BEFORE_SUBMISSION] = get_string('examplesbeforesubmission', 'workshop');
183 $options[self::EXAMPLES_BEFORE_ASSESSMENT] = get_string('examplesbeforeassessment', 'workshop');
184 return $options;
185 }
186
187 /**
188 * Returns the list of available grading strategy methods
189 *
190 * @return array ['string' => 'string']
191 */
192 public static function available_strategies_list() {
193 $installed = get_plugin_list('workshopform');
194 $forms = array();
195 foreach ($installed as $strategy => $strategypath) {
196 if (file_exists($strategypath . '/lib.php')) {
197 $forms[$strategy] = get_string('pluginname', 'workshopform_' . $strategy);
198 }
199 }
200 return $forms;
201 }
202
203 /**
204 * Return an array of possible values of assessment dimension weight
205 *
206 * @return array of integers 0, 1, 2, ..., 16
207 */
208 public static function available_dimension_weights_list() {
209 $weights = array();
210 for ($i=16; $i>=0; $i--) {
211 $weights[$i] = $i;
212 }
213 return $weights;
214 }
215
216 /**
217 * Helper function returning the greatest common divisor
218 *
219 * @param int $a
220 * @param int $b
221 * @return int
222 */
223 public static function gcd($a, $b) {
224 return ($b == 0) ? ($a):(self::gcd($b, $a % $b));
225 }
226
227 /**
228 * Helper function returning the least common multiple
229 *
230 * @param int $a
231 * @param int $b
232 * @return int
233 */
234 public static function lcm($a, $b) {
235 return ($a / self::gcd($a,$b)) * $b;
236 }
237
aa40adbf
DM
238 ////////////////////////////////////////////////////////////////////////////////
239 // Workshop API //
240 ////////////////////////////////////////////////////////////////////////////////
241
6e309973
DM
242 /**
243 * Fetches all users with the capability mod/workshop:submit in the current context
244 *
3d2924e9 245 * The returned objects contain id, lastname and firstname properties and are ordered by lastname,firstname
53fad4b9 246 *
aa40adbf 247 * @todo handle with limits and groups
53fad4b9 248 * @param bool $musthavesubmission If true, return only users who have already submitted. All possible authors otherwise.
65ba104c 249 * @return array array[userid] => stdClass{->id ->lastname ->firstname}
6e309973 250 */
d895c6aa
DM
251 public function get_potential_authors($musthavesubmission=true) {
252 $users = get_users_by_capability($this->context, 'mod/workshop:submit',
1fed6ce3 253 'u.id,u.lastname,u.firstname', 'u.lastname,u.firstname,u.id', '', '', '', '', false, false, true);
3d2924e9 254 if ($musthavesubmission) {
da0b1f70 255 $users = array_intersect_key($users, $this->users_with_submission(array_keys($users)));
66c9894d 256 }
da0b1f70 257 return $users;
6e309973
DM
258 }
259
6e309973
DM
260 /**
261 * Fetches all users with the capability mod/workshop:peerassess in the current context
262 *
b13142da 263 * The returned objects contain id, lastname and firstname properties and are ordered by lastname,firstname
53fad4b9 264 *
aa40adbf 265 * @todo handle with limits and groups
53fad4b9 266 * @param bool $musthavesubmission If true, return only users who have already submitted. All possible users otherwise.
65ba104c 267 * @return array array[userid] => stdClass{->id ->lastname ->firstname}
6e309973 268 */
d895c6aa
DM
269 public function get_potential_reviewers($musthavesubmission=false) {
270 $users = get_users_by_capability($this->context, 'mod/workshop:peerassess',
1fed6ce3 271 'u.id, u.lastname, u.firstname', 'u.lastname,u.firstname,u.id', '', '', '', '', false, false, true);
3d2924e9
DM
272 if ($musthavesubmission) {
273 // users without their own submission can not be reviewers
da0b1f70 274 $users = array_intersect_key($users, $this->users_with_submission(array_keys($users)));
0968b1a3 275 }
da0b1f70 276 return $users;
0968b1a3
DM
277 }
278
b8ead2e6
DM
279 /**
280 * Groups the given users by the group membership
281 *
282 * This takes the module grouping settings into account. If "Available for group members only"
283 * is set, returns only groups withing the course module grouping. Always returns group [0] with
284 * all the given users.
285 *
65ba104c
DM
286 * @param array $users array[userid] => stdClass{->id ->lastname ->firstname}
287 * @return array array[groupid][userid] => stdClass{->id ->lastname ->firstname}
53fad4b9 288 */
3d2924e9 289 public function get_grouped($users) {
53fad4b9 290 global $DB;
3d2924e9 291 global $CFG;
53fad4b9 292
b8ead2e6
DM
293 $grouped = array(); // grouped users to be returned
294 if (empty($users)) {
295 return $grouped;
a7c5b918 296 }
3d2924e9 297 if (!empty($CFG->enablegroupings) and $this->cm->groupmembersonly) {
53fad4b9
DM
298 // Available for group members only - the workshop is available only
299 // to users assigned to groups within the selected grouping, or to
300 // any group if no grouping is selected.
301 $groupingid = $this->cm->groupingid;
b8ead2e6 302 // All users that are members of at least one group will be
53fad4b9 303 // added into a virtual group id 0
b8ead2e6 304 $grouped[0] = array();
53fad4b9
DM
305 } else {
306 $groupingid = 0;
b8ead2e6
DM
307 // there is no need to be member of a group so $grouped[0] will contain
308 // all users
309 $grouped[0] = $users;
53fad4b9 310 }
b8ead2e6 311 $gmemberships = groups_get_all_groups($this->cm->course, array_keys($users), $groupingid,
53fad4b9
DM
312 'gm.id,gm.groupid,gm.userid');
313 foreach ($gmemberships as $gmembership) {
b8ead2e6
DM
314 if (!isset($grouped[$gmembership->groupid])) {
315 $grouped[$gmembership->groupid] = array();
53fad4b9 316 }
b8ead2e6
DM
317 $grouped[$gmembership->groupid][$gmembership->userid] = $users[$gmembership->userid];
318 $grouped[0][$gmembership->userid] = $users[$gmembership->userid];
53fad4b9 319 }
b8ead2e6 320 return $grouped;
53fad4b9 321 }
6e309973 322
aa40adbf
DM
323 /**
324 * Returns the list of all allocations (it est assigned assessments) in the workshop
325 *
326 * Assessments of example submissions are ignored
327 *
328 * @return array
329 */
330 public function get_allocations() {
331 global $DB;
332
00aca3c1 333 $sql = 'SELECT a.id, a.submissionid, a.reviewerid, s.authorid
aa40adbf
DM
334 FROM {workshop_assessments} a
335 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
336 WHERE s.example = 0 AND s.workshopid = :workshopid';
337 $params = array('workshopid' => $this->id);
338
339 return $DB->get_records_sql($sql, $params);
340 }
341
6e309973
DM
342 /**
343 * Returns submissions from this workshop
344 *
3dc78e5b
DM
345 * Fetches data from {workshop_submissions} and adds some useful information from other
346 * tables. Does not return textual fields to prevent possible memory lack issues.
53fad4b9 347 *
00aca3c1 348 * @param mixed $authorid int|array|'all' If set to [array of] integer, return submission[s] of the given user[s] only
934329e5 349 * @return array of records or an empty array
6e309973 350 */
29dc43e7 351 public function get_submissions($authorid='all') {
6e309973
DM
352 global $DB;
353
00aca3c1
DM
354 $sql = 'SELECT s.id, s.workshopid, s.example, s.authorid, s.timecreated, s.timemodified,
355 s.title, s.grade, s.gradeover, s.gradeoverby,
356 u.lastname AS authorlastname, u.firstname AS authorfirstname,
357 u.picture AS authorpicture, u.imagealt AS authorimagealt,
358 t.lastname AS overlastname, t.firstname AS overfirstname,
359 t.picture AS overpicture, t.imagealt AS overimagealt
3d2924e9 360 FROM {workshop_submissions} s
00aca3c1 361 INNER JOIN {user} u ON (s.authorid = u.id)
29dc43e7
DM
362 LEFT JOIN {user} t ON (s.gradeoverby = t.id)
363 WHERE s.example = 0 AND s.workshopid = :workshopid';
3d2924e9 364 $params = array('workshopid' => $this->id);
6e309973 365
00aca3c1 366 if ('all' === $authorid) {
3d2924e9 367 // no additional conditions
934329e5 368 } elseif (!empty($authorid)) {
00aca3c1
DM
369 list($usql, $uparams) = $DB->get_in_or_equal($authorid, SQL_PARAMS_NAMED);
370 $sql .= " AND authorid $usql";
6e309973 371 $params = array_merge($params, $uparams);
3d2924e9 372 } else {
934329e5
DM
373 // $authorid is empty
374 return array();
6e309973 375 }
3dc78e5b 376 $sql .= ' ORDER BY u.lastname, u.firstname';
6e309973 377
3dc78e5b 378 return $DB->get_records_sql($sql, $params);
6e309973
DM
379 }
380
51508f25
DM
381 /**
382 * Returns a submission record with the author's data
383 *
384 * @param int $id submission id
385 * @return stdClass
386 */
387 public function get_submission_by_id($id) {
388 global $DB;
389
29dc43e7
DM
390 // we intentionally check the workshopid here, too, so the workshop can't touch submissions
391 // from other instances
51508f25
DM
392 $sql = 'SELECT s.*,
393 u.lastname AS authorlastname, u.firstname AS authorfirstname, u.id AS authorid,
394 u.picture AS authorpicture, u.imagealt AS authorimagealt
395 FROM {workshop_submissions} s
00aca3c1 396 INNER JOIN {user} u ON (s.authorid = u.id)
51508f25
DM
397 WHERE s.workshopid = :workshopid AND s.id = :id';
398 $params = array('workshopid' => $this->id, 'id' => $id);
399 return $DB->get_record_sql($sql, $params, MUST_EXIST);
400 }
401
53fad4b9 402 /**
3dc78e5b 403 * Returns a submission submitted by the given author
53fad4b9 404 *
3dc78e5b
DM
405 * @param int $id author id
406 * @return stdClass|false
53fad4b9 407 */
00aca3c1 408 public function get_submission_by_author($authorid) {
e9b0f0ab
DM
409 global $DB;
410
00aca3c1 411 if (empty($authorid)) {
53fad4b9
DM
412 return false;
413 }
3dc78e5b
DM
414 $sql = 'SELECT s.*,
415 u.lastname AS authorlastname, u.firstname AS authorfirstname, u.id AS authorid,
416 u.picture AS authorpicture, u.imagealt AS authorimagealt
417 FROM {workshop_submissions} s
00aca3c1
DM
418 INNER JOIN {user} u ON (s.authorid = u.id)
419 WHERE s.example = 0 AND s.workshopid = :workshopid AND s.authorid = :authorid';
420 $params = array('workshopid' => $this->id, 'authorid' => $authorid);
3dc78e5b 421 return $DB->get_record_sql($sql, $params);
53fad4b9 422 }
6e309973
DM
423
424 /**
3dc78e5b 425 * Returns the list of all assessments in the workshop with some data added
6e309973
DM
426 *
427 * Fetches data from {workshop_assessments} and adds some useful information from other
3dc78e5b
DM
428 * tables. The returned object does not contain textual fields (ie comments) to prevent memory
429 * lack issues.
430 *
431 * @return array [assessmentid] => assessment stdClass
6e309973 432 */
3dc78e5b 433 public function get_all_assessments() {
6e309973 434 global $DB;
53fad4b9 435
f6e8b318 436 $sql = 'SELECT a.id, a.submissionid, a.reviewerid, a.timecreated, a.timemodified,
3dc78e5b 437 a.grade, a.gradinggrade, a.gradinggradeover, a.gradinggradeoverby,
3d2924e9
DM
438 reviewer.id AS reviewerid,reviewer.firstname AS reviewerfirstname,reviewer.lastname as reviewerlastname,
439 s.title,
ddb59c77 440 author.id AS authorid, author.firstname AS authorfirstname,author.lastname AS authorlastname
3d2924e9 441 FROM {workshop_assessments} a
00aca3c1 442 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id)
3d2924e9 443 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
00aca3c1 444 INNER JOIN {user} author ON (s.authorid = author.id)
3dc78e5b
DM
445 WHERE s.workshopid = :workshopid AND s.example = 0
446 ORDER BY reviewer.lastname, reviewer.firstname';
3d2924e9
DM
447 $params = array('workshopid' => $this->id);
448
3dc78e5b 449 return $DB->get_records_sql($sql, $params);
53fad4b9
DM
450 }
451
452 /**
3dc78e5b 453 * Get the complete information about the given assessment
53fad4b9
DM
454 *
455 * @param int $id Assessment ID
65ba104c 456 * @return mixed false if not found, stdClass otherwise
53fad4b9
DM
457 */
458 public function get_assessment_by_id($id) {
3dc78e5b
DM
459 global $DB;
460
461 $sql = 'SELECT a.*,
462 reviewer.id AS reviewerid,reviewer.firstname AS reviewerfirstname,reviewer.lastname as reviewerlastname,
463 s.title,
464 author.id AS authorid, author.firstname AS authorfirstname,author.lastname as authorlastname
465 FROM {workshop_assessments} a
00aca3c1 466 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id)
3dc78e5b 467 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
00aca3c1 468 INNER JOIN {user} author ON (s.authorid = author.id)
3dc78e5b
DM
469 WHERE a.id = :id AND s.workshopid = :workshopid';
470 $params = array('id' => $id, 'workshopid' => $this->id);
471
472 return $DB->get_record_sql($sql, $params, MUST_EXIST);
53fad4b9
DM
473 }
474
475 /**
3dc78e5b 476 * Get the complete information about all assessments allocated to the given reviewer
53fad4b9 477 *
00aca3c1 478 * @param int $reviewerid
3dc78e5b 479 * @return array
53fad4b9 480 */
00aca3c1 481 public function get_assessments_by_reviewer($reviewerid) {
3dc78e5b
DM
482 global $DB;
483
484 $sql = 'SELECT a.*,
ddb59c77
DM
485 reviewer.id AS reviewerid,reviewer.firstname AS reviewerfirstname,reviewer.lastname AS reviewerlastname,
486 s.id AS submissionid, s.title AS submissiontitle, s.timecreated AS submissioncreated,
487 s.timemodified AS submissionmodified,
488 author.id AS authorid, author.firstname AS authorfirstname,author.lastname AS authorlastname,
489 author.picture AS authorpicture, author.imagealt AS authorimagealt
3dc78e5b 490 FROM {workshop_assessments} a
00aca3c1 491 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id)
3dc78e5b 492 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
00aca3c1
DM
493 INNER JOIN {user} author ON (s.authorid = author.id)
494 WHERE s.example = 0 AND reviewer.id = :reviewerid AND s.workshopid = :workshopid';
495 $params = array('reviewerid' => $reviewerid, 'workshopid' => $this->id);
3dc78e5b
DM
496
497 return $DB->get_records_sql($sql, $params);
53fad4b9 498 }
6e309973 499
6e309973
DM
500 /**
501 * Allocate a submission to a user for review
53fad4b9 502 *
65ba104c 503 * @param stdClass $submission Submission record
6e309973 504 * @param int $reviewerid User ID
53fad4b9 505 * @param bool $bulk repeated inserts into DB expected
6e309973
DM
506 * @return int ID of the new assessment or an error code
507 */
65ba104c 508 public function add_allocation(stdClass $submission, $reviewerid, $bulk=false) {
6e309973
DM
509 global $DB;
510
00aca3c1 511 if ($DB->record_exists('workshop_assessments', array('submissionid' => $submission->id, 'reviewerid' => $reviewerid))) {
b761e6d9 512 return self::ALLOCATION_EXISTS;
6e309973
DM
513 }
514
6e309973 515 $now = time();
65ba104c 516 $assessment = new stdClass();
e554671d
DM
517 $assessment->submissionid = $submission->id;
518 $assessment->reviewerid = $reviewerid;
519 $assessment->timecreated = $now;
520 $assessment->timemodified = $now;
521 $assessment->weight = 1;
522 $assessment->generalcommentformat = FORMAT_HTML; // todo better default handling
523 $assessment->feedbackreviewerformat = FORMAT_HTML; // todo better default handling
6e309973 524
235b31c8 525 return $DB->insert_record('workshop_assessments', $assessment, true, $bulk);
6e309973
DM
526 }
527
6e309973 528 /**
53fad4b9 529 * Delete assessment record or records
6e309973 530 *
53fad4b9
DM
531 * @param mixed $id int|array assessment id or array of assessments ids
532 * @return bool false if $id not a valid parameter, true otherwise
6e309973
DM
533 */
534 public function delete_assessment($id) {
535 global $DB;
536
537 // todo remove all given grades from workshop_grades;
6e309973 538
53fad4b9 539 if (is_array($id)) {
235b31c8 540 return $DB->delete_records_list('workshop_assessments', 'id', $id);
3d2924e9 541 } else {
235b31c8 542 return $DB->delete_records('workshop_assessments', array('id' => $id));
53fad4b9 543 }
53fad4b9 544 }
6e309973
DM
545
546 /**
547 * Returns instance of grading strategy class
53fad4b9 548 *
65ba104c 549 * @return stdClass Instance of a grading strategy
6e309973
DM
550 */
551 public function grading_strategy_instance() {
3d2924e9
DM
552 global $CFG; // because we require other libs here
553
3fd2b0e1 554 if (is_null($this->strategyinstance)) {
f05c168d 555 $strategylib = dirname(__FILE__) . '/form/' . $this->strategy . '/lib.php';
6e309973
DM
556 if (is_readable($strategylib)) {
557 require_once($strategylib);
558 } else {
f05c168d 559 throw new coding_exception('the grading forms subplugin must contain library ' . $strategylib);
6e309973 560 }
0dc47fb9 561 $classname = 'workshop_' . $this->strategy . '_strategy';
3fd2b0e1
DM
562 $this->strategyinstance = new $classname($this);
563 if (!in_array('workshop_strategy', class_implements($this->strategyinstance))) {
b761e6d9 564 throw new coding_exception($classname . ' does not implement workshop_strategy interface');
6e309973
DM
565 }
566 }
3fd2b0e1 567 return $this->strategyinstance;
6e309973
DM
568 }
569
45d24d39
DM
570 /**
571 * Returns instance of grading evaluation class
572 *
573 * @return stdClass Instance of a grading evaluation
574 */
575 public function grading_evaluation_instance() {
576 global $CFG; // because we require other libs here
577
578 if (is_null($this->evaluationinstance)) {
579 $evaluationlib = dirname(__FILE__) . '/eval/' . $this->evaluation . '/lib.php';
580 if (is_readable($evaluationlib)) {
581 require_once($evaluationlib);
582 } else {
583 throw new coding_exception('the grading evaluation subplugin must contain library ' . $evaluationlib);
584 }
585 $classname = 'workshop_' . $this->evaluation . '_evaluation';
586 $this->evaluationinstance = new $classname($this);
587 if (!in_array('workshop_evaluation', class_implements($this->evaluationinstance))) {
588 throw new coding_exception($classname . ' does not implement workshop_evaluation interface');
589 }
590 }
591 return $this->evaluationinstance;
592 }
593
66c9894d
DM
594 /**
595 * Returns instance of submissions allocator
53fad4b9 596 *
130ae619 597 * @param string $method The name of the allocation method, must be PARAM_ALPHA
65ba104c 598 * @return stdClass Instance of submissions allocator
66c9894d
DM
599 */
600 public function allocator_instance($method) {
3d2924e9
DM
601 global $CFG; // because we require other libs here
602
f05c168d 603 $allocationlib = dirname(__FILE__) . '/allocation/' . $method . '/lib.php';
66c9894d
DM
604 if (is_readable($allocationlib)) {
605 require_once($allocationlib);
606 } else {
f05c168d 607 throw new coding_exception('Unable to find the allocation library ' . $allocationlib);
66c9894d
DM
608 }
609 $classname = 'workshop_' . $method . '_allocator';
610 return new $classname($this);
611 }
612
b8ead2e6 613 /**
454e8dd9 614 * @return moodle_url of this workshop's view page
b8ead2e6
DM
615 */
616 public function view_url() {
617 global $CFG;
618 return new moodle_url($CFG->wwwroot . '/mod/workshop/view.php', array('id' => $this->cm->id));
619 }
620
621 /**
454e8dd9 622 * @return moodle_url of the page for editing this workshop's grading form
b8ead2e6
DM
623 */
624 public function editform_url() {
625 global $CFG;
626 return new moodle_url($CFG->wwwroot . '/mod/workshop/editform.php', array('cmid' => $this->cm->id));
627 }
628
629 /**
454e8dd9 630 * @return moodle_url of the page for previewing this workshop's grading form
b8ead2e6
DM
631 */
632 public function previewform_url() {
633 global $CFG;
43b34576 634 return new moodle_url($CFG->wwwroot . '/mod/workshop/editformpreview.php', array('cmid' => $this->cm->id));
b8ead2e6
DM
635 }
636
637 /**
638 * @param int $assessmentid The ID of assessment record
454e8dd9 639 * @return moodle_url of the assessment page
b8ead2e6 640 */
a39d7d87 641 public function assess_url($assessmentid) {
b8ead2e6 642 global $CFG;
454e8dd9 643 $assessmentid = clean_param($assessmentid, PARAM_INT);
a39d7d87 644 return new moodle_url($CFG->wwwroot . '/mod/workshop/assessment.php', array('asid' => $assessmentid));
b8ead2e6
DM
645 }
646
39861053 647 /**
67cd00ba 648 * @return moodle_url of the page to view a submission, defaults to the own one
39861053 649 */
67cd00ba 650 public function submission_url($id=null) {
39861053 651 global $CFG;
67cd00ba 652 return new moodle_url($CFG->wwwroot . '/mod/workshop/submission.php', array('cmid' => $this->cm->id, 'id' => $id));
39861053
DM
653 }
654
da0b1f70 655 /**
454e8dd9 656 * @return moodle_url of the mod_edit form
da0b1f70
DM
657 */
658 public function updatemod_url() {
659 global $CFG;
660 return new moodle_url($CFG->wwwroot . '/course/modedit.php', array('update' => $this->cm->id, 'return' => 1));
661 }
662
454e8dd9
DM
663 /**
664 * @return moodle_url to the allocation page
665 */
da0b1f70
DM
666 public function allocation_url() {
667 global $CFG;
668 return new moodle_url($CFG->wwwroot . '/mod/workshop/allocation.php', array('cmid' => $this->cm->id));
669 }
670
454e8dd9
DM
671 /**
672 * @param int $phasecode The internal phase code
673 * @return moodle_url of the script to change the current phase to $phasecode
674 */
675 public function switchphase_url($phasecode) {
676 global $CFG;
677 $phasecode = clean_param($phasecode, PARAM_INT);
678 return new moodle_url($CFG->wwwroot . '/mod/workshop/switchphase.php', array('cmid' => $this->cm->id, 'phase' => $phasecode));
679 }
680
89c1aa97
DM
681 /**
682 * @return moodle_url to the aggregation page
683 */
684 public function aggregate_url() {
685 global $CFG;
686 return new moodle_url($CFG->wwwroot . '/mod/workshop/aggregate.php', array('cmid' => $this->cm->id));
687 }
688
b8ead2e6 689 /**
407b1e91
DM
690 * Are users allowed to create/edit their submissions?
691 *
692 * TODO: this depends on the workshop phase, phase deadlines, submitting after deadlines possibility
b8ead2e6 693 *
407b1e91 694 * @return bool
b8ead2e6 695 */
407b1e91
DM
696 public function submitting_allowed() {
697 return true;
b8ead2e6
DM
698 }
699
c1e883bb 700 /**
407b1e91 701 * Are reviewers allowed to create/edit their assessments?
c1e883bb 702 *
407b1e91 703 * TODO: this depends on the workshop phase, phase deadlines
c1e883bb
DM
704 *
705 * @return bool
706 */
407b1e91 707 public function assessing_allowed() {
c1e883bb
DM
708 return true;
709 }
710
407b1e91 711
3dc78e5b
DM
712 /**
713 * Are the peer-reviews available to the authors?
714 *
715 * TODO: this depends on the workshop phase
716 *
717 * @return bool
718 */
719 public function assessments_available() {
720 return true;
721 }
722
723 /**
724 * Can the given grades be displayed to the authors?
725 *
726 * Grades are not displayed if {@link self::assessments_available()} return false. The returned
f6e8b318 727 * value may be true (if yes, display grades) or false (no, hide grades yet)
3dc78e5b 728 *
f6e8b318 729 * @return bool
3dc78e5b
DM
730 */
731 public function grades_available() {
732 return true;
733 }
734
b761e6d9
DM
735 /**
736 * Prepare an individual workshop plan for the given user.
737 *
f05c168d
DM
738 * @param int $userid whom the plan is prepared for
739 * @param stdClass context of the planned workshop
740 * @return stdClass data object to be passed to the renderer
b761e6d9 741 */
d895c6aa 742 public function prepare_user_plan($userid) {
b761e6d9
DM
743 global $DB;
744
745 $phases = array();
746
747 // Prepare tasks for the setup phase
748 $phase = new stdClass();
749 $phase->title = get_string('phasesetup', 'workshop');
750 $phase->tasks = array();
d895c6aa 751 if (has_capability('moodle/course:manageactivities', $this->context, $userid)) {
da0b1f70
DM
752 $task = new stdClass();
753 $task->title = get_string('taskintro', 'workshop');
754 $task->link = $this->updatemod_url();
755 $task->completed = !(trim(strip_tags($this->intro)) == '');
756 $phase->tasks['intro'] = $task;
757 }
d895c6aa 758 if (has_capability('moodle/course:manageactivities', $this->context, $userid)) {
454e8dd9
DM
759 $task = new stdClass();
760 $task->title = get_string('taskinstructauthors', 'workshop');
761 $task->link = $this->updatemod_url();
762 $task->completed = !(trim(strip_tags($this->instructauthors)) == '');
763 $phase->tasks['instructauthors'] = $task;
764 }
d895c6aa 765 if (has_capability('mod/workshop:editdimensions', $this->context, $userid)) {
b761e6d9 766 $task = new stdClass();
da0b1f70
DM
767 $task->title = get_string('editassessmentform', 'workshop');
768 $task->link = $this->editform_url();
f6e8b318 769 if ($this->grading_strategy_instance()->form_ready()) {
da0b1f70
DM
770 $task->completed = true;
771 } elseif ($this->phase > self::PHASE_SETUP) {
772 $task->completed = false;
773 }
b761e6d9
DM
774 $phase->tasks['editform'] = $task;
775 }
da0b1f70
DM
776 if (empty($phase->tasks) and $this->phase == self::PHASE_SETUP) {
777 // if we are in the setup phase and there is no task (typical for students), let us
778 // display some explanation what is going on
779 $task = new stdClass();
780 $task->title = get_string('undersetup', 'workshop');
781 $task->completed = 'info';
782 $phase->tasks['setupinfo'] = $task;
783 }
b761e6d9
DM
784 $phases[self::PHASE_SETUP] = $phase;
785
786 // Prepare tasks for the submission phase
787 $phase = new stdClass();
788 $phase->title = get_string('phasesubmission', 'workshop');
789 $phase->tasks = array();
d895c6aa 790 if (has_capability('moodle/course:manageactivities', $this->context, $userid)) {
b761e6d9 791 $task = new stdClass();
00aca3c1
DM
792 $task->title = get_string('taskinstructreviewers', 'workshop');
793 $task->link = $this->updatemod_url();
794 if (trim(strip_tags($this->instructreviewers))) {
da0b1f70
DM
795 $task->completed = true;
796 } elseif ($this->phase >= self::PHASE_ASSESSMENT) {
797 $task->completed = false;
da0b1f70 798 }
00aca3c1 799 $phase->tasks['instructreviewers'] = $task;
b761e6d9 800 }
d895c6aa 801 if (has_capability('mod/workshop:submit', $this->context, $userid, false)) {
da0b1f70 802 $task = new stdClass();
00aca3c1
DM
803 $task->title = get_string('tasksubmit', 'workshop');
804 $task->link = $this->submission_url();
805 if ($DB->record_exists('workshop_submissions', array('workshopid'=>$this->id, 'example'=>0, 'authorid'=>$userid))) {
da0b1f70
DM
806 $task->completed = true;
807 } elseif ($this->phase >= self::PHASE_ASSESSMENT) {
808 $task->completed = false;
00aca3c1
DM
809 } else {
810 $task->completed = null; // still has a chance to submit
da0b1f70 811 }
00aca3c1 812 $phase->tasks['submit'] = $task;
da0b1f70 813 }
b761e6d9 814 $phases[self::PHASE_SUBMISSION] = $phase;
d895c6aa 815 if (has_capability('mod/workshop:allocate', $this->context, $userid)) {
da0b1f70
DM
816 $task = new stdClass();
817 $task->title = get_string('allocate', 'workshop');
818 $task->link = $this->allocation_url();
d895c6aa 819 $numofauthors = count(get_users_by_capability($this->context, 'mod/workshop:submit', 'u.id', '', '', '',
a3610b08
DM
820 '', '', false, true));
821 $numofsubmissions = $DB->count_records('workshop_submissions', array('workshopid'=>$this->id, 'example'=>0));
822 $sql = 'SELECT COUNT(s.id) AS nonallocated
823 FROM {workshop_submissions} s
824 LEFT JOIN {workshop_assessments} a ON (a.submissionid=s.id)
825 WHERE s.workshopid = :workshopid AND s.example=0 AND a.submissionid IS NULL';
826 $params['workshopid'] = $this->id;
827 $numnonallocated = $DB->count_records_sql($sql, $params);
da0b1f70
DM
828 if ($numofsubmissions == 0) {
829 $task->completed = null;
a3610b08 830 } elseif ($numnonallocated == 0) {
da0b1f70
DM
831 $task->completed = true;
832 } elseif ($this->phase > self::PHASE_SUBMISSION) {
833 $task->completed = false;
834 } else {
835 $task->completed = null; // still has a chance to allocate
836 }
837 $a = new stdClass();
3189fb2d
DM
838 $a->expected = $numofauthors;
839 $a->submitted = $numofsubmissions;
a3610b08 840 $a->allocate = $numnonallocated;
3189fb2d 841 $task->details = get_string('allocatedetails', 'workshop', $a);
da0b1f70 842 unset($a);
3189fb2d 843 $phase->tasks['allocate'] = $task;
3dc78e5b
DM
844
845 if ($numofsubmissions < $numofauthors and $this->phase >= self::PHASE_SUBMISSION) {
846 $task = new stdClass();
847 $task->title = get_string('someuserswosubmission', 'workshop');
848 $task->completed = 'info';
849 $phase->tasks['allocateinfo'] = $task;
850 }
da0b1f70 851 }
b761e6d9
DM
852
853 // Prepare tasks for the peer-assessment phase (includes eventual self-assessments)
854 $phase = new stdClass();
855 $phase->title = get_string('phaseassessment', 'workshop');
856 $phase->tasks = array();
d895c6aa 857 $phase->isreviewer = has_capability('mod/workshop:peerassess', $this->context, $userid);
3dc78e5b 858 $phase->assessments = $this->get_assessments_by_reviewer($userid);
b761e6d9
DM
859 $numofpeers = 0; // number of allocated peer-assessments
860 $numofpeerstodo = 0; // number of peer-assessments to do
861 $numofself = 0; // number of allocated self-assessments - should be 0 or 1
862 $numofselftodo = 0; // number of self-assessments to do - should be 0 or 1
863 foreach ($phase->assessments as $a) {
864 if ($a->authorid == $userid) {
865 $numofself++;
866 if (is_null($a->grade)) {
867 $numofselftodo++;
868 }
869 } else {
870 $numofpeers++;
871 if (is_null($a->grade)) {
872 $numofpeerstodo++;
873 }
874 }
875 }
876 unset($a);
877 if ($numofpeers) {
878 $task = new stdClass();
3dc78e5b
DM
879 if ($numofpeerstodo == 0) {
880 $task->completed = true;
881 } elseif ($this->phase > self::PHASE_ASSESSMENT) {
882 $task->completed = false;
883 }
b761e6d9
DM
884 $a = new stdClass();
885 $a->total = $numofpeers;
886 $a->todo = $numofpeerstodo;
887 $task->title = get_string('taskassesspeers', 'workshop');
da0b1f70 888 $task->details = get_string('taskassesspeersdetails', 'workshop', $a);
b761e6d9
DM
889 unset($a);
890 $phase->tasks['assesspeers'] = $task;
891 }
892 if ($numofself) {
893 $task = new stdClass();
3dc78e5b
DM
894 if ($numofselftodo == 0) {
895 $task->completed = true;
896 } elseif ($this->phase > self::PHASE_ASSESSMENT) {
897 $task->completed = false;
898 }
b761e6d9
DM
899 $task->title = get_string('taskassessself', 'workshop');
900 $phase->tasks['assessself'] = $task;
901 }
902 $phases[self::PHASE_ASSESSMENT] = $phase;
903
1fed6ce3 904 // Prepare tasks for the grading evaluation phase
b761e6d9
DM
905 $phase = new stdClass();
906 $phase->title = get_string('phaseevaluation', 'workshop');
907 $phase->tasks = array();
1fed6ce3 908 if (has_capability('mod/workshop:overridegrades', $this->context)) {
f27b70fb
DM
909 $expected = count($this->get_potential_authors(false));
910 $calculated = $DB->count_records_select('workshop_submissions',
911 'workshopid = ? AND (grade IS NOT NULL OR gradeover IS NOT NULL)', array($this->id));
1fed6ce3 912 $task = new stdClass();
f27b70fb 913 $task->title = get_string('calculategrades', 'workshop');
1fed6ce3
DM
914 $a = new stdClass();
915 $a->expected = $expected;
f27b70fb
DM
916 $a->calculated = $calculated;
917 $task->details = get_string('calculategradesdetails', 'workshop', $a);
918 if ($calculated >= $expected) {
1fed6ce3
DM
919 $task->completed = true;
920 } elseif ($this->phase > self::PHASE_EVALUATION) {
921 $task->completed = false;
922 }
f27b70fb
DM
923 $phase->tasks['calculategradinggrade'] = $task;
924
925 $expected = count($this->get_potential_reviewers(false));
926 $calculated = $DB->count_records_select('workshop_aggregations',
927 'workshopid = ? AND gradinggrade IS NOT NULL', array($this->id));
928 $task = new stdClass();
929 $task->title = get_string('calculategradinggrades', 'workshop');
930 $a = new stdClass();
931 $a->expected = $expected;
932 $a->calculated = $calculated;
933 $task->details = get_string('calculategradinggradesdetails', 'workshop', $a);
934 if ($calculated >= $expected) {
935 $task->completed = true;
936 } elseif ($this->phase > self::PHASE_EVALUATION) {
937 $task->completed = false;
f55650e6 938 }
f27b70fb
DM
939 $phase->tasks['calculategradinggrade'] = $task;
940
d183140d 941 } elseif ($this->phase == self::PHASE_EVALUATION) {
1fed6ce3
DM
942 $task = new stdClass();
943 $task->title = get_string('evaluategradeswait', 'workshop');
944 $task->completed = 'info';
945 $phase->tasks['evaluateinfo'] = $task;
946 }
b761e6d9
DM
947 $phases[self::PHASE_EVALUATION] = $phase;
948
949 // Prepare tasks for the "workshop closed" phase - todo
950 $phase = new stdClass();
951 $phase->title = get_string('phaseclosed', 'workshop');
952 $phase->tasks = array();
953 $phases[self::PHASE_CLOSED] = $phase;
954
955 // Polish data, set default values if not done explicitly
956 foreach ($phases as $phasecode => $phase) {
957 $phase->title = isset($phase->title) ? $phase->title : '';
958 $phase->tasks = isset($phase->tasks) ? $phase->tasks : array();
959 if ($phasecode == $this->phase) {
960 $phase->active = true;
961 } else {
962 $phase->active = false;
963 }
454e8dd9
DM
964 if (!isset($phase->actions)) {
965 $phase->actions = array();
966 }
b761e6d9
DM
967
968 foreach ($phase->tasks as $taskcode => $task) {
969 $task->title = isset($task->title) ? $task->title : '';
da0b1f70
DM
970 $task->link = isset($task->link) ? $task->link : null;
971 $task->details = isset($task->details) ? $task->details : '';
b761e6d9
DM
972 $task->completed = isset($task->completed) ? $task->completed : null;
973 }
974 }
454e8dd9
DM
975
976 // Add phase swithing actions
d895c6aa 977 if (has_capability('mod/workshop:switchphase', $this->context, $userid)) {
454e8dd9
DM
978 foreach ($phases as $phasecode => $phase) {
979 if (! $phase->active) {
980 $action = new stdClass();
981 $action->type = 'switchphase';
982 $action->url = $this->switchphase_url($phasecode);
983 $phase->actions[] = $action;
984 }
985 }
986 }
987
b761e6d9
DM
988 return $phases;
989 }
990
454e8dd9
DM
991 /**
992 * Switch to a new workshop phase
993 *
994 * Modifies the underlying database record. You should terminate the script shortly after calling this.
995 *
996 * @param int $newphase new phase code
997 * @return bool true if success, false otherwise
998 */
999 public function switch_phase($newphase) {
1000 global $DB;
1001
365c2cc2 1002 $known = $this->available_phases_list();
454e8dd9
DM
1003 if (!isset($known[$newphase])) {
1004 return false;
1005 }
f6e8b318
DM
1006
1007 if (self::PHASE_CLOSED == $newphase) {
f27b70fb 1008 // push the grades into the gradebook
f6e8b318
DM
1009
1010 }
1011
454e8dd9
DM
1012 $DB->set_field('workshop', 'phase', $newphase, array('id' => $this->id));
1013 return true;
1014 }
ddb59c77
DM
1015
1016 /**
1017 * Saves a raw grade for submission as calculated from the assessment form fields
1018 *
1019 * @param array $assessmentid assessment record id, must exists
00aca3c1 1020 * @param mixed $grade raw percentual grade from 0.00000 to 100.00000
ddb59c77
DM
1021 * @return false|float the saved grade
1022 */
1023 public function set_peer_grade($assessmentid, $grade) {
1024 global $DB;
1025
1026 if (is_null($grade)) {
1027 return false;
1028 }
1029 $data = new stdClass();
1030 $data->id = $assessmentid;
1031 $data->grade = $grade;
1032 $DB->update_record('workshop_assessments', $data);
1033 return $grade;
1034 }
6516b9e9 1035
29dc43e7
DM
1036 /**
1037 * Prepares data object with all workshop grades to be rendered
1038 *
5e71cefb
DM
1039 * @param int $userid the user we are preparing the report for
1040 * @param mixed $groups single group or array of groups - only show users who are in one of these group(s). Defaults to all
29dc43e7 1041 * @param int $page the current page (for the pagination)
5e71cefb 1042 * @param int $perpage participants per page (for the pagination)
f27b70fb 1043 * @param string $sortby lastname|firstname|submissiontitle|submissiongrade|gradinggrade
5e71cefb 1044 * @param string $sorthow ASC|DESC
29dc43e7
DM
1045 * @return stdClass data for the renderer
1046 */
d895c6aa 1047 public function prepare_grading_report($userid, $groups, $page, $perpage, $sortby, $sorthow) {
29dc43e7
DM
1048 global $DB;
1049
d895c6aa
DM
1050 $canviewall = has_capability('mod/workshop:viewallassessments', $this->context, $userid);
1051 $isparticipant = has_any_capability(array('mod/workshop:submit', 'mod/workshop:peerassess'), $this->context, $userid);
29dc43e7
DM
1052
1053 if (!$canviewall and !$isparticipant) {
1054 // who the hell is this?
1055 return array();
1056 }
1057
f27b70fb 1058 if (!in_array($sortby, array('lastname','firstname','submissiontitle','submissiongrade','gradinggrade'))) {
5e71cefb
DM
1059 $sortby = 'lastname';
1060 }
1061
1062 if (!($sorthow === 'ASC' or $sorthow === 'DESC')) {
1063 $sorthow = 'ASC';
1064 }
1065
1066 // get the list of user ids to be displayed
29dc43e7
DM
1067 if ($canviewall) {
1068 // fetch the list of ids of all workshop participants - this may get really long so fetch just id
d895c6aa 1069 $participants = get_users_by_capability($this->context, array('mod/workshop:submit', 'mod/workshop:peerassess'),
5e71cefb 1070 'u.id', '', '', '', $groups, '', false, false, true);
29dc43e7
DM
1071 } else {
1072 // this is an ordinary workshop participant (aka student) - display the report just for him/her
1073 $participants = array($userid => (object)array('id' => $userid));
1074 }
1075
5e71cefb 1076 // we will need to know the number of all records later for the pagination purposes
29dc43e7
DM
1077 $numofparticipants = count($participants);
1078
d183140d 1079 // load all fields which can be used for sorting and paginate the records
5e71cefb 1080 list($participantids, $params) = $DB->get_in_or_equal(array_keys($participants), SQL_PARAMS_NAMED);
d183140d
DM
1081 $params['workshopid1'] = $this->id;
1082 $params['workshopid2'] = $this->id;
5e71cefb
DM
1083 $sqlsort = $sortby . ' ' . $sorthow . ',u.lastname,u.firstname,u.id';
1084 $sql = "SELECT u.id AS userid,u.firstname,u.lastname,u.picture,u.imagealt,
f27b70fb 1085 s.title AS submissiontitle, s.grade AS submissiongrade, ag.gradinggrade
5e71cefb 1086 FROM {user} u
d183140d
DM
1087 LEFT JOIN {workshop_submissions} s ON (s.authorid = u.id AND s.workshopid = :workshopid1 AND s.example = 0)
1088 LEFT JOIN {workshop_aggregations} ag ON (ag.userid = u.id AND ag.workshopid = :workshopid2)
1089 WHERE u.id $participantids
5e71cefb
DM
1090 ORDER BY $sqlsort";
1091 $participants = $DB->get_records_sql($sql, $params, $page * $perpage, $perpage);
29dc43e7
DM
1092
1093 // this will hold the information needed to display user names and pictures
5e71cefb
DM
1094 $userinfo = array();
1095
1096 // get the user details for all participants to display
1097 foreach ($participants as $participant) {
1098 if (!isset($userinfo[$participant->userid])) {
1099 $userinfo[$participant->userid] = new stdClass();
1100 $userinfo[$participant->userid]->id = $participant->userid;
1101 $userinfo[$participant->userid]->firstname = $participant->firstname;
1102 $userinfo[$participant->userid]->lastname = $participant->lastname;
1103 $userinfo[$participant->userid]->picture = $participant->picture;
1104 $userinfo[$participant->userid]->imagealt = $participant->imagealt;
1105 }
1106 }
29dc43e7 1107
5e71cefb 1108 // load the submissions details
29dc43e7 1109 $submissions = $this->get_submissions(array_keys($participants));
5e71cefb
DM
1110
1111 // get the user details for all moderators (teachers) that have overridden a submission grade
29dc43e7 1112 foreach ($submissions as $submission) {
29dc43e7
DM
1113 if (!isset($userinfo[$submission->gradeoverby])) {
1114 $userinfo[$submission->gradeoverby] = new stdClass();
1115 $userinfo[$submission->gradeoverby]->id = $submission->gradeoverby;
1116 $userinfo[$submission->gradeoverby]->firstname = $submission->overfirstname;
1117 $userinfo[$submission->gradeoverby]->lastname = $submission->overlastname;
1118 $userinfo[$submission->gradeoverby]->picture = $submission->overpicture;
1119 $userinfo[$submission->gradeoverby]->imagealt = $submission->overimagealt;
1120 }
1121 }
1122
5e71cefb 1123 // get the user details for all reviewers of the displayed participants
29dc43e7
DM
1124 $reviewers = array();
1125 if ($submissions) {
1126 list($submissionids, $params) = $DB->get_in_or_equal(array_keys($submissions), SQL_PARAMS_NAMED);
1127 $sql = "SELECT a.id AS assessmentid, a.submissionid, a.grade, a.gradinggrade, a.gradinggradeover,
1128 r.id AS reviewerid, r.lastname, r.firstname, r.picture, r.imagealt,
1129 s.id AS submissionid, s.authorid
1130 FROM {workshop_assessments} a
1131 JOIN {user} r ON (a.reviewerid = r.id)
1132 JOIN {workshop_submissions} s ON (a.submissionid = s.id)
1133 WHERE a.submissionid $submissionids";
1134 $reviewers = $DB->get_records_sql($sql, $params);
1135 foreach ($reviewers as $reviewer) {
1136 if (!isset($userinfo[$reviewer->reviewerid])) {
1137 $userinfo[$reviewer->reviewerid] = new stdClass();
1138 $userinfo[$reviewer->reviewerid]->id = $reviewer->reviewerid;
1139 $userinfo[$reviewer->reviewerid]->firstname = $reviewer->firstname;
1140 $userinfo[$reviewer->reviewerid]->lastname = $reviewer->lastname;
1141 $userinfo[$reviewer->reviewerid]->picture = $reviewer->picture;
1142 $userinfo[$reviewer->reviewerid]->imagealt = $reviewer->imagealt;
1143 }
1144 }
1145 }
1146
5e71cefb 1147 // get the user details for all reviewees of the displayed participants
934329e5
DM
1148 $reviewees = array();
1149 if ($participants) {
1150 list($participantids, $params) = $DB->get_in_or_equal(array_keys($participants), SQL_PARAMS_NAMED);
1151 $params['workshopid'] = $this->id;
d183140d 1152 $sql = "SELECT a.id AS assessmentid, a.submissionid, a.grade, a.gradinggrade, a.gradinggradeover, a.reviewerid,
934329e5
DM
1153 s.id AS submissionid,
1154 e.id AS authorid, e.lastname, e.firstname, e.picture, e.imagealt
1155 FROM {user} u
1156 JOIN {workshop_assessments} a ON (a.reviewerid = u.id)
1157 JOIN {workshop_submissions} s ON (a.submissionid = s.id)
1158 JOIN {user} e ON (s.authorid = e.id)
1159 WHERE u.id $participantids AND s.workshopid = :workshopid";
1160 $reviewees = $DB->get_records_sql($sql, $params);
1161 foreach ($reviewees as $reviewee) {
1162 if (!isset($userinfo[$reviewee->authorid])) {
1163 $userinfo[$reviewee->authorid] = new stdClass();
1164 $userinfo[$reviewee->authorid]->id = $reviewee->authorid;
1165 $userinfo[$reviewee->authorid]->firstname = $reviewee->firstname;
1166 $userinfo[$reviewee->authorid]->lastname = $reviewee->lastname;
1167 $userinfo[$reviewee->authorid]->picture = $reviewee->picture;
1168 $userinfo[$reviewee->authorid]->imagealt = $reviewee->imagealt;
1169 }
29dc43e7
DM
1170 }
1171 }
1172
5e71cefb
DM
1173 // finally populate the object to be rendered
1174 $grades = $participants;
29dc43e7
DM
1175
1176 foreach ($participants as $participant) {
1177 // set up default (null) values
d183140d
DM
1178 $grades[$participant->userid]->submissionid = null;
1179 $grades[$participant->userid]->submissiontitle = null;
1180 $grades[$participant->userid]->submissiongrade = null;
1181 $grades[$participant->userid]->submissiongradeover = null;
1182 $grades[$participant->userid]->submissiongradeoverby = null;
5e71cefb
DM
1183 $grades[$participant->userid]->reviewedby = array();
1184 $grades[$participant->userid]->reviewerof = array();
29dc43e7
DM
1185 }
1186 unset($participants);
1187 unset($participant);
1188
1189 foreach ($submissions as $submission) {
1190 $grades[$submission->authorid]->submissionid = $submission->id;
1191 $grades[$submission->authorid]->submissiontitle = $submission->title;
b4857acb
DM
1192 $grades[$submission->authorid]->submissiongrade = $this->real_grade($submission->grade);
1193 $grades[$submission->authorid]->submissiongradeover = $this->real_grade($submission->gradeover);
29dc43e7
DM
1194 $grades[$submission->authorid]->submissiongradeoverby = $submission->gradeoverby;
1195 }
1196 unset($submissions);
1197 unset($submission);
1198
1199 foreach($reviewers as $reviewer) {
1200 $info = new stdClass();
1201 $info->userid = $reviewer->reviewerid;
1202 $info->assessmentid = $reviewer->assessmentid;
1203 $info->submissionid = $reviewer->submissionid;
b4857acb
DM
1204 $info->grade = $this->real_grade($reviewer->grade);
1205 $info->gradinggrade = $this->real_grading_grade($reviewer->gradinggrade);
1206 $info->gradinggradeover = $this->real_grading_grade($reviewer->gradinggradeover);
29dc43e7
DM
1207 $grades[$reviewer->authorid]->reviewedby[$reviewer->reviewerid] = $info;
1208 }
1209 unset($reviewers);
1210 unset($reviewer);
1211
1212 foreach($reviewees as $reviewee) {
1213 $info = new stdClass();
1214 $info->userid = $reviewee->authorid;
1215 $info->assessmentid = $reviewee->assessmentid;
1216 $info->submissionid = $reviewee->submissionid;
b4857acb
DM
1217 $info->grade = $this->real_grade($reviewee->grade);
1218 $info->gradinggrade = $this->real_grading_grade($reviewee->gradinggrade);
1219 $info->gradinggradeover = $this->real_grading_grade($reviewee->gradinggradeover);
29dc43e7
DM
1220 $grades[$reviewee->reviewerid]->reviewerof[$reviewee->authorid] = $info;
1221 }
1222 unset($reviewees);
1223 unset($reviewee);
1224
b4857acb
DM
1225 foreach ($grades as $grade) {
1226 $grade->gradinggrade = $this->real_grading_grade($grade->gradinggrade);
b4857acb
DM
1227 }
1228
29dc43e7
DM
1229 $data = new stdClass();
1230 $data->grades = $grades;
1231 $data->userinfo = $userinfo;
1232 $data->totalcount = $numofparticipants;
b4857acb
DM
1233 $data->maxgrade = $this->real_grade(100);
1234 $data->maxgradinggrade = $this->real_grading_grade(100);
29dc43e7
DM
1235 return $data;
1236 }
1237
29dc43e7 1238 /**
b4857acb 1239 * Calculates the real value of a grade
29dc43e7 1240 *
b4857acb
DM
1241 * @param float $value percentual value from 0 to 100
1242 * @param float $max the maximal grade
1243 * @return string
1244 */
1245 public function real_grade_value($value, $max) {
1246 $localized = true;
557a1100 1247 if (is_null($value) or $value === '') {
b4857acb
DM
1248 return null;
1249 } elseif ($max == 0) {
1250 return 0;
1251 } else {
1252 return format_float($max * $value / 100, $this->gradedecimals, $localized);
1253 }
1254 }
1255
e554671d
DM
1256 /**
1257 * Calculates the raw (percentual) value from a real grade
1258 *
1259 * This is used in cases when a user wants to give a grade such as 12 of 20 and we need to save
1260 * this value in a raw percentual form into DB
1261 * @param float $value given grade
1262 * @param float $max the maximal grade
1263 * @return float suitable to be stored as numeric(10,5)
1264 */
1265 public function raw_grade_value($value, $max) {
557a1100 1266 if (is_null($value) or $value === '') {
e554671d
DM
1267 return null;
1268 }
1269 if ($max == 0 or $value < 0) {
1270 return 0;
1271 }
1272 $p = $value / $max * 100;
1273 if ($p > 100) {
1274 return $max;
1275 }
1276 return grade_floatval($p);
1277 }
1278
b4857acb
DM
1279 /**
1280 * Calculates the real value of grade for submission
1281 *
1282 * @param float $value percentual value from 0 to 100
1283 * @return string
1284 */
1285 public function real_grade($value) {
1286 return $this->real_grade_value($value, $this->grade);
1287 }
1288
1289 /**
1290 * Calculates the real value of grade for assessment
1291 *
1292 * @param float $value percentual value from 0 to 100
1293 * @return string
1294 */
1295 public function real_grading_grade($value) {
1296 return $this->real_grade_value($value, $this->gradinggrade);
29dc43e7
DM
1297 }
1298
89c1aa97 1299 /**
e9a90e69 1300 * Calculates grades for submission for the given participant(s) and updates it in the database
89c1aa97
DM
1301 *
1302 * @param null|int|array $restrict If null, update all authors, otherwise update just grades for the given author(s)
1303 * @return void
1304 */
8a1ba8ac 1305 public function aggregate_submission_grades($restrict=null) {
89c1aa97
DM
1306 global $DB;
1307
1308 // fetch a recordset with all assessments to process
1696f36c 1309 $sql = 'SELECT s.id AS submissionid, s.grade AS submissiongrade,
89c1aa97
DM
1310 a.weight, a.grade
1311 FROM {workshop_submissions} s
1312 LEFT JOIN {workshop_assessments} a ON (a.submissionid = s.id)
1313 WHERE s.example=0 AND s.workshopid=:workshopid'; // to be cont.
1314 $params = array('workshopid' => $this->id);
1315
1316 if (is_null($restrict)) {
1317 // update all users - no more conditions
1318 } elseif (!empty($restrict)) {
1319 list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED);
1320 $sql .= " AND s.authorid $usql";
1321 $params = array_merge($params, $uparams);
1322 } else {
1323 throw new coding_exception('Empty value is not a valid parameter here');
1324 }
1325
1326 $sql .= ' ORDER BY s.id'; // this is important for bulk processing
89c1aa97 1327
e9a90e69
DM
1328 $rs = $DB->get_recordset_sql($sql, $params);
1329 $batch = array(); // will contain a set of all assessments of a single submission
1330 $previous = null; // a previous record in the recordset
1331
89c1aa97
DM
1332 foreach ($rs as $current) {
1333 if (is_null($previous)) {
1334 // we are processing the very first record in the recordset
1335 $previous = $current;
89c1aa97 1336 }
e9a90e69 1337 if ($current->submissionid == $previous->submissionid) {
89c1aa97 1338 // we are still processing the current submission
e9a90e69
DM
1339 $batch[] = $current;
1340 } else {
1341 // process all the assessments of a sigle submission
1342 $this->aggregate_submission_grades_process($batch);
1343 // and then start to process another submission
1344 $batch = array($current);
1345 $previous = $current;
89c1aa97
DM
1346 }
1347 }
e9a90e69
DM
1348 // do not forget to process the last batch!
1349 $this->aggregate_submission_grades_process($batch);
89c1aa97
DM
1350 $rs->close();
1351 }
1352
1353 /**
1354 * Calculates grades for assessment for the given participant(s)
1355 *
39411930
DM
1356 * Grade for assessment is calculated as a simple mean of all grading grades calculated by the grading evaluator.
1357 * The assessment weight is not taken into account here.
89c1aa97
DM
1358 *
1359 * @param null|int|array $restrict If null, update all reviewers, otherwise update just grades for the given reviewer(s)
1360 * @return void
1361 */
8a1ba8ac 1362 public function aggregate_grading_grades($restrict=null) {
89c1aa97
DM
1363 global $DB;
1364
39411930
DM
1365 // fetch a recordset with all assessments to process
1366 $sql = 'SELECT a.reviewerid, a.gradinggrade, a.gradinggradeover,
1367 ag.id AS aggregationid, ag.gradinggrade AS aggregatedgrade
1368 FROM {workshop_assessments} a
1369 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
1370 LEFT JOIN {workshop_aggregations} ag ON (ag.userid = a.reviewerid AND ag.workshopid = s.workshopid)
1371 WHERE s.example=0 AND s.workshopid=:workshopid'; // to be cont.
1372 $params = array('workshopid' => $this->id);
1373
1374 if (is_null($restrict)) {
1375 // update all users - no more conditions
1376 } elseif (!empty($restrict)) {
1377 list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED);
1378 $sql .= " AND a.reviewerid $usql";
1379 $params = array_merge($params, $uparams);
1380 } else {
1381 throw new coding_exception('Empty value is not a valid parameter here');
1382 }
1383
1384 $sql .= ' ORDER BY a.reviewerid'; // this is important for bulk processing
1385
1386 $rs = $DB->get_recordset_sql($sql, $params);
1387 $batch = array(); // will contain a set of all assessments of a single submission
1388 $previous = null; // a previous record in the recordset
1389
1390 foreach ($rs as $current) {
1391 if (is_null($previous)) {
1392 // we are processing the very first record in the recordset
1393 $previous = $current;
1394 }
1395 if ($current->reviewerid == $previous->reviewerid) {
1396 // we are still processing the current reviewer
1397 $batch[] = $current;
1398 } else {
1399 // process all the assessments of a sigle submission
1400 $this->aggregate_grading_grades_process($batch);
1401 // and then start to process another reviewer
1402 $batch = array($current);
1403 $previous = $current;
1404 }
1405 }
1406 // do not forget to process the last batch!
1407 $this->aggregate_grading_grades_process($batch);
1408 $rs->close();
89c1aa97
DM
1409 }
1410
77f43e7d 1411 /**
f6e8b318 1412 * Returns the mform the teachers use to put a feedback for the reviewer
77f43e7d 1413 *
f6e8b318 1414 * @return workshop_feedbackreviewer_form
77f43e7d 1415 */
e554671d 1416 public function get_feedbackreviewer_form(moodle_url $actionurl, stdClass $assessment, $editable=true) {
77f43e7d
DM
1417 global $CFG;
1418 require_once(dirname(__FILE__) . '/feedbackreviewer_form.php');
1419
e554671d
DM
1420 $current = new stdClass();
1421 $current->asid = $assessment->id;
1422 $current->gradinggrade = $this->real_grading_grade($assessment->gradinggrade);
1423 $current->gradinggradeover = $this->real_grading_grade($assessment->gradinggradeover);
1424 $current->feedbackreviewer = $assessment->feedbackreviewer;
1425 $current->feedbackreviewerformat = $assessment->feedbackreviewerformat;
1426 if (is_null($current->gradinggrade)) {
1427 $current->gradinggrade = get_string('nullgrade', 'workshop');
1428 }
1429
1430 // prepare wysiwyg editor
1431 $current = file_prepare_standard_editor($current, 'feedbackreviewer', array());
1432
77f43e7d 1433 return new workshop_feedbackreviewer_form($actionurl,
e554671d 1434 array('workshop' => $this, 'current' => $current, 'feedbackopts' => array()),
77f43e7d
DM
1435 'post', '', null, $editable);
1436 }
1437
67cd00ba
DM
1438 /**
1439 * Returns the mform the teachers use to put a feedback for the author on their submission
1440 *
1441 * @return workshop_feedbackauthor_form
1442 */
1443 public function get_feedbackauthor_form(moodle_url $actionurl, stdClass $submission, $editable=true) {
1444 global $CFG;
1445 require_once(dirname(__FILE__) . '/feedbackauthor_form.php');
1446
1447 $current = new stdClass();
1448 $current->submissionid = $submission->id;
557a1100
DM
1449 $current->grade = $this->real_grade($submission->grade);
1450 $current->gradeover = $this->real_grade($submission->gradeover);
1451 $current->feedbackauthor = $submission->feedbackauthor;
1452 $current->feedbackauthorformat = $submission->feedbackauthorformat;
67cd00ba
DM
1453 if (is_null($current->grade)) {
1454 $current->grade = get_string('nullgrade', 'workshop');
1455 }
1456
1457 // prepare wysiwyg editor
1458 $current = file_prepare_standard_editor($current, 'feedbackauthor', array());
1459
1460 return new workshop_feedbackauthor_form($actionurl,
1461 array('workshop' => $this, 'current' => $current, 'feedbackopts' => array()),
1462 'post', '', null, $editable);
1463 }
1464
aa40adbf
DM
1465 ////////////////////////////////////////////////////////////////////////////////
1466 // Internal methods (implementation details) //
1467 ////////////////////////////////////////////////////////////////////////////////
6516b9e9 1468
e9a90e69
DM
1469 /**
1470 * Given an array of all assessments of a single submission, calculates the final grade for this submission
1471 *
1472 * This calculates the weighted mean of the passed assessment grades. If, however, the submission grade
1473 * was overridden by a teacher, the gradeover value is returned and the rest of grades are ignored.
1474 *
1475 * @param array $assessments of stdClass(->submissionid ->submissiongrade ->gradeover ->weight ->grade)
1fed6ce3 1476 * @return void
e9a90e69
DM
1477 */
1478 protected function aggregate_submission_grades_process(array $assessments) {
1479 global $DB;
1480
1481 $submissionid = null; // the id of the submission being processed
1482 $current = null; // the grade currently saved in database
1483 $finalgrade = null; // the new grade to be calculated
1484 $sumgrades = 0;
1485 $sumweights = 0;
1486
1487 foreach ($assessments as $assessment) {
1488 if (is_null($submissionid)) {
1489 // the id is the same in all records, fetch it during the first loop cycle
1490 $submissionid = $assessment->submissionid;
1491 }
1492 if (is_null($current)) {
1493 // the currently saved grade is the same in all records, fetch it during the first loop cycle
1494 $current = $assessment->submissiongrade;
1495 }
e9a90e69
DM
1496 if (is_null($assessment->grade)) {
1497 // this was not assessed yet
1498 continue;
1499 }
1500 if ($assessment->weight == 0) {
1501 // this does not influence the calculation
1502 continue;
1503 }
1504 $sumgrades += $assessment->grade * $assessment->weight;
1505 $sumweights += $assessment->weight;
1506 }
1507 if ($sumweights > 0 and is_null($finalgrade)) {
1508 $finalgrade = grade_floatval($sumgrades / $sumweights);
1509 }
1510 // check if the new final grade differs from the one stored in the database
1511 if (grade_floats_different($finalgrade, $current)) {
1512 // we need to save new calculation into the database
1513 $DB->set_field('workshop_submissions', 'grade', $finalgrade, array('id' => $submissionid));
1514 }
1515 }
1516
39411930
DM
1517 /**
1518 * Given an array of all assessments done by a single reviewer, calculates the final grading grade
1519 *
1520 * This calculates the simple mean of the passed grading grades. If, however, the grading grade
1521 * was overridden by a teacher, the gradinggradeover value is returned and the rest of grades are ignored.
1522 *
1523 * @param array $assessments of stdClass(->reviewerid ->gradinggrade ->gradinggradeover ->aggregationid ->aggregatedgrade)
1fed6ce3 1524 * @return void
39411930
DM
1525 */
1526 protected function aggregate_grading_grades_process(array $assessments) {
1527 global $DB;
1528
1529 $reviewerid = null; // the id of the reviewer being processed
1530 $current = null; // the gradinggrade currently saved in database
1531 $finalgrade = null; // the new grade to be calculated
1532 $agid = null; // aggregation id
1533 $sumgrades = 0;
1534 $count = 0;
1535
1536 foreach ($assessments as $assessment) {
1537 if (is_null($reviewerid)) {
1538 // the id is the same in all records, fetch it during the first loop cycle
1539 $reviewerid = $assessment->reviewerid;
1540 }
1541 if (is_null($agid)) {
1542 // the id is the same in all records, fetch it during the first loop cycle
1543 $agid = $assessment->aggregationid;
1544 }
1545 if (is_null($current)) {
1546 // the currently saved grade is the same in all records, fetch it during the first loop cycle
1547 $current = $assessment->aggregatedgrade;
1548 }
1549 if (!is_null($assessment->gradinggradeover)) {
1550 // the grading grade for this assessment is overriden by a teacher
1551 $sumgrades += $assessment->gradinggradeover;
1552 $count++;
1553 } else {
1554 if (!is_null($assessment->gradinggrade)) {
1555 $sumgrades += $assessment->gradinggrade;
1556 $count++;
1557 }
1558 }
1559 }
1560 if ($count > 0) {
1561 $finalgrade = grade_floatval($sumgrades / $count);
1562 }
1563 // check if the new final grade differs from the one stored in the database
1564 if (grade_floats_different($finalgrade, $current)) {
1565 // we need to save new calculation into the database
1566 if (is_null($agid)) {
1567 // no aggregation record yet
1568 $record = new stdClass();
1569 $record->workshopid = $this->id;
1570 $record->userid = $reviewerid;
1571 $record->gradinggrade = $finalgrade;
1572 $DB->insert_record('workshop_aggregations', $record);
1573 } else {
1574 $DB->set_field('workshop_aggregations', 'gradinggrade', $finalgrade, array('id' => $agid));
1575 }
1576 }
1577 }
1578
6516b9e9 1579 /**
aa40adbf 1580 * Given a list of user ids, returns the filtered one containing just ids of users with own submission
6516b9e9 1581 *
aa40adbf
DM
1582 * Example submissions are ignored.
1583 *
1584 * @param array $userids
6516b9e9
DM
1585 * @return array
1586 */
aa40adbf
DM
1587 protected function users_with_submission(array $userids) {
1588 global $DB;
1589
1590 if (empty($userids)) {
1591 return array();
1592 }
1593 $userswithsubmission = array();
1594 list($usql, $uparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
00aca3c1 1595 $sql = "SELECT id,authorid
aa40adbf 1596 FROM {workshop_submissions}
00aca3c1 1597 WHERE example = 0 AND workshopid = :workshopid AND authorid $usql";
aa40adbf
DM
1598 $params = array('workshopid' => $this->id);
1599 $params = array_merge($params, $uparams);
1600 $submissions = $DB->get_records_sql($sql, $params);
1601 foreach ($submissions as $submission) {
00aca3c1 1602 $userswithsubmission[$submission->authorid] = true;
aa40adbf
DM
1603 }
1604
1605 return $userswithsubmission;
6516b9e9
DM
1606 }
1607
aa40adbf
DM
1608 /**
1609 * @return array of available workshop phases
1610 */
365c2cc2 1611 protected function available_phases_list() {
aa40adbf
DM
1612 return array(
1613 self::PHASE_SETUP => true,
1614 self::PHASE_SUBMISSION => true,
1615 self::PHASE_ASSESSMENT => true,
1616 self::PHASE_EVALUATION => true,
1617 self::PHASE_CLOSED => true,
1618 );
1619 }
1620
66c9894d 1621}