Commit | Line | Data |
---|---|---|
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 | ||
31 | defined('MOODLE_INTERNAL') || die(); | |
32 | ||
b13142da | 33 | require_once(dirname(__FILE__).'/lib.php'); // we extend this library here |
0968b1a3 | 34 | |
6e309973 DM |
35 | /** |
36 | * Full-featured workshop API | |
37 | * | |
a39d7d87 | 38 | * This wraps the workshop database record with a set of methods that are called |
6e309973 | 39 | * from the module itself. The class should be initialized right after you get |
a39d7d87 | 40 | * $workshop, $cm and $course records at the begining of the script. |
6e309973 | 41 | */ |
a39d7d87 DM |
42 | class workshop { |
43 | ||
b761e6d9 DM |
44 | /** return statuses of {@link add_allocation} to be passed to a workshop renderer method */ |
45 | const ALLOCATION_EXISTS = -1; | |
46 | const ALLOCATION_ERROR = -2; | |
47 | ||
48 | /** the internal code of the workshop phases as are stored in the database */ | |
49 | const PHASE_SETUP = 10; | |
50 | const PHASE_SUBMISSION = 20; | |
51 | const PHASE_ASSESSMENT = 30; | |
52 | const PHASE_EVALUATION = 40; | |
53 | const PHASE_CLOSED = 50; | |
54 | ||
65ba104c | 55 | /** @var stdClass course module record */ |
a39d7d87 DM |
56 | public $cm = null; |
57 | ||
65ba104c | 58 | /** @var stdClass course record */ |
a39d7d87 | 59 | public $course = null; |
6e309973 | 60 | |
b761e6d9 DM |
61 | /** |
62 | * @var workshop_strategy grading strategy instance | |
63 | * Do not use directly, get the instance using {@link workshop::grading_strategy_instance()} | |
64 | */ | |
b13142da DM |
65 | protected $strategyinstance = null; |
66 | ||
45d24d39 DM |
67 | /** |
68 | * @var workshop_evaluation grading evaluation instance | |
69 | * Do not use directly, get the instance using {@link workshop::grading_evaluation_instance()} | |
70 | */ | |
71 | protected $evaluationinstance = null; | |
72 | ||
6e309973 | 73 | /** |
65ba104c | 74 | * Initializes the workshop API instance using the data from DB |
a39d7d87 DM |
75 | * |
76 | * Makes deep copy of all passed records properties. Replaces integer $course attribute | |
77 | * with a full database record (course should not be stored in instances table anyway). | |
6e309973 | 78 | * |
b13142da | 79 | * @param stdClass $dbrecord Workshop instance data from {workshop} table |
06d73dd5 DM |
80 | * @param stdClass $cm Course module record as returned by {@link get_coursemodule_from_id()} |
81 | * @param stdClass $course Course record from {course} table | |
0dc47fb9 | 82 | */ |
b13142da | 83 | public function __construct(stdClass $dbrecord, stdClass $cm, stdClass $course) { |
f05c168d DM |
84 | foreach ($dbrecord as $field => $value) { |
85 | $this->{$field} = $value; | |
a39d7d87 | 86 | } |
45d24d39 DM |
87 | $this->evaluation = 'best'; // todo make this configurable |
88 | $this->cm = $cm; | |
89 | $this->course = $course; // beware - this replaces the standard course field in the instance table | |
90 | // this is intentional - IMO there should be no such field as it violates | |
91 | // 3rd normal form with no real performance gain | |
6e309973 DM |
92 | } |
93 | ||
aa40adbf DM |
94 | //////////////////////////////////////////////////////////////////////////////// |
95 | // Static methods // | |
96 | //////////////////////////////////////////////////////////////////////////////// | |
97 | ||
da0b1f70 | 98 | /** |
aa40adbf | 99 | * Return list of available allocation methods |
da0b1f70 | 100 | * |
aa40adbf | 101 | * @return array Array ['string' => 'string'] of localized allocation method names |
da0b1f70 | 102 | */ |
aa40adbf DM |
103 | public static function installed_allocators() { |
104 | $installed = get_plugin_list('workshopallocation'); | |
105 | $forms = array(); | |
106 | foreach ($installed as $allocation => $allocationpath) { | |
107 | if (file_exists($allocationpath . '/lib.php')) { | |
108 | $forms[$allocation] = get_string('pluginname', 'workshopallocation_' . $allocation); | |
109 | } | |
f05c168d | 110 | } |
aa40adbf DM |
111 | // usability - make sure that manual allocation appears the first |
112 | if (isset($forms['manual'])) { | |
113 | $m = array('manual' => $forms['manual']); | |
114 | unset($forms['manual']); | |
115 | $forms = array_merge($m, $forms); | |
da0b1f70 | 116 | } |
aa40adbf DM |
117 | return $forms; |
118 | } | |
da0b1f70 | 119 | |
aa40adbf DM |
120 | /** |
121 | * Returns an array of options for the editors that are used for submitting and assessing instructions | |
122 | * | |
123 | * @param stdClass $context | |
124 | * @return array | |
125 | */ | |
126 | public static function instruction_editors_options(stdClass $context) { | |
127 | return array('subdirs' => 1, 'maxbytes' => 0, 'maxfiles' => EDITOR_UNLIMITED_FILES, | |
128 | 'changeformat' => 1, 'context' => $context, 'noclean' => 1, 'trusttext' => 0); | |
da0b1f70 DM |
129 | } |
130 | ||
aa40adbf DM |
131 | //////////////////////////////////////////////////////////////////////////////// |
132 | // Workshop API // | |
133 | //////////////////////////////////////////////////////////////////////////////// | |
134 | ||
6e309973 DM |
135 | /** |
136 | * Fetches all users with the capability mod/workshop:submit in the current context | |
137 | * | |
3d2924e9 | 138 | * The returned objects contain id, lastname and firstname properties and are ordered by lastname,firstname |
53fad4b9 | 139 | * |
aa40adbf | 140 | * @todo handle with limits and groups |
f05c168d | 141 | * @param stdClass $context |
53fad4b9 | 142 | * @param bool $musthavesubmission If true, return only users who have already submitted. All possible authors otherwise. |
65ba104c | 143 | * @return array array[userid] => stdClass{->id ->lastname ->firstname} |
6e309973 | 144 | */ |
f05c168d DM |
145 | public function get_potential_authors(stdClass $context, $musthavesubmission=true) { |
146 | $users = get_users_by_capability($context, 'mod/workshop:submit', | |
aa40adbf | 147 | 'u.id,u.lastname,u.firstname', 'u.lastname,u.firstname,u.id', 0, 1000, '', '', false, false, true); |
3d2924e9 | 148 | if ($musthavesubmission) { |
da0b1f70 | 149 | $users = array_intersect_key($users, $this->users_with_submission(array_keys($users))); |
66c9894d | 150 | } |
da0b1f70 | 151 | return $users; |
6e309973 DM |
152 | } |
153 | ||
6e309973 DM |
154 | /** |
155 | * Fetches all users with the capability mod/workshop:peerassess in the current context | |
156 | * | |
b13142da | 157 | * The returned objects contain id, lastname and firstname properties and are ordered by lastname,firstname |
53fad4b9 | 158 | * |
aa40adbf | 159 | * @todo handle with limits and groups |
f05c168d | 160 | * @param stdClass $context |
53fad4b9 | 161 | * @param bool $musthavesubmission If true, return only users who have already submitted. All possible users otherwise. |
65ba104c | 162 | * @return array array[userid] => stdClass{->id ->lastname ->firstname} |
6e309973 | 163 | */ |
f05c168d DM |
164 | public function get_potential_reviewers(stdClass $context, $musthavesubmission=false) { |
165 | $users = get_users_by_capability($context, 'mod/workshop:peerassess', | |
aa40adbf | 166 | 'u.id, u.lastname, u.firstname', 'u.lastname,u.firstname,u.id', 0, 1000, '', '', false, false, true); |
3d2924e9 DM |
167 | if ($musthavesubmission) { |
168 | // users without their own submission can not be reviewers | |
da0b1f70 | 169 | $users = array_intersect_key($users, $this->users_with_submission(array_keys($users))); |
0968b1a3 | 170 | } |
da0b1f70 | 171 | return $users; |
0968b1a3 DM |
172 | } |
173 | ||
b8ead2e6 DM |
174 | /** |
175 | * Groups the given users by the group membership | |
176 | * | |
177 | * This takes the module grouping settings into account. If "Available for group members only" | |
178 | * is set, returns only groups withing the course module grouping. Always returns group [0] with | |
179 | * all the given users. | |
180 | * | |
65ba104c DM |
181 | * @param array $users array[userid] => stdClass{->id ->lastname ->firstname} |
182 | * @return array array[groupid][userid] => stdClass{->id ->lastname ->firstname} | |
53fad4b9 | 183 | */ |
3d2924e9 | 184 | public function get_grouped($users) { |
53fad4b9 | 185 | global $DB; |
3d2924e9 | 186 | global $CFG; |
53fad4b9 | 187 | |
b8ead2e6 DM |
188 | $grouped = array(); // grouped users to be returned |
189 | if (empty($users)) { | |
190 | return $grouped; | |
a7c5b918 | 191 | } |
3d2924e9 | 192 | if (!empty($CFG->enablegroupings) and $this->cm->groupmembersonly) { |
53fad4b9 DM |
193 | // Available for group members only - the workshop is available only |
194 | // to users assigned to groups within the selected grouping, or to | |
195 | // any group if no grouping is selected. | |
196 | $groupingid = $this->cm->groupingid; | |
b8ead2e6 | 197 | // All users that are members of at least one group will be |
53fad4b9 | 198 | // added into a virtual group id 0 |
b8ead2e6 | 199 | $grouped[0] = array(); |
53fad4b9 DM |
200 | } else { |
201 | $groupingid = 0; | |
b8ead2e6 DM |
202 | // there is no need to be member of a group so $grouped[0] will contain |
203 | // all users | |
204 | $grouped[0] = $users; | |
53fad4b9 | 205 | } |
b8ead2e6 | 206 | $gmemberships = groups_get_all_groups($this->cm->course, array_keys($users), $groupingid, |
53fad4b9 DM |
207 | 'gm.id,gm.groupid,gm.userid'); |
208 | foreach ($gmemberships as $gmembership) { | |
b8ead2e6 DM |
209 | if (!isset($grouped[$gmembership->groupid])) { |
210 | $grouped[$gmembership->groupid] = array(); | |
53fad4b9 | 211 | } |
b8ead2e6 DM |
212 | $grouped[$gmembership->groupid][$gmembership->userid] = $users[$gmembership->userid]; |
213 | $grouped[0][$gmembership->userid] = $users[$gmembership->userid]; | |
53fad4b9 | 214 | } |
b8ead2e6 | 215 | return $grouped; |
53fad4b9 | 216 | } |
6e309973 | 217 | |
aa40adbf DM |
218 | /** |
219 | * Returns the list of all allocations (it est assigned assessments) in the workshop | |
220 | * | |
221 | * Assessments of example submissions are ignored | |
222 | * | |
223 | * @return array | |
224 | */ | |
225 | public function get_allocations() { | |
226 | global $DB; | |
227 | ||
228 | $sql = 'SELECT a.id, a.submissionid, a.userid AS reviewerid, | |
229 | s.userid AS authorid | |
230 | FROM {workshop_assessments} a | |
231 | INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id) | |
232 | WHERE s.example = 0 AND s.workshopid = :workshopid'; | |
233 | $params = array('workshopid' => $this->id); | |
234 | ||
235 | return $DB->get_records_sql($sql, $params); | |
236 | } | |
237 | ||
6e309973 DM |
238 | /** |
239 | * Returns submissions from this workshop | |
240 | * | |
3dc78e5b DM |
241 | * Fetches data from {workshop_submissions} and adds some useful information from other |
242 | * tables. Does not return textual fields to prevent possible memory lack issues. | |
53fad4b9 DM |
243 | * |
244 | * @param mixed $userid int|array|'all' If set to [array of] integer, return submission[s] of the given user[s] only | |
245 | * @param mixed $examples false|true|'all' Only regular submissions, only examples, all submissions | |
f05c168d | 246 | * @return array |
6e309973 | 247 | */ |
3dc78e5b | 248 | public function get_submissions($userid='all', $examples=false) { |
6e309973 DM |
249 | global $DB; |
250 | ||
e9b0f0ab DM |
251 | $sql = 'SELECT s.id, s.workshopid, s.example, s.userid, s.timecreated, s.timemodified, |
252 | s.title, s.grade, s.gradeover, s.gradeoverby, s.gradinggrade, | |
253 | u.lastname AS authorlastname, u.firstname AS authorfirstname, u.id AS authorid, | |
254 | u.picture AS authorpicture, u.imagealt AS authorimagealt | |
3d2924e9 DM |
255 | FROM {workshop_submissions} s |
256 | INNER JOIN {user} u ON (s.userid = u.id) | |
235b31c8 | 257 | WHERE s.workshopid = :workshopid'; |
3d2924e9 | 258 | $params = array('workshopid' => $this->id); |
6e309973 | 259 | |
b13142da DM |
260 | if ('all' === $examples) { |
261 | // no additional conditions | |
262 | } elseif ($examples === true) { | |
235b31c8 | 263 | $sql .= ' AND example = 1'; |
b13142da | 264 | } elseif ($examples === false) { |
235b31c8 | 265 | $sql .= ' AND example = 0'; |
b13142da DM |
266 | } else { |
267 | throw new coding_exception('Illegal parameter value: $examples may be false|true|"all"'); | |
6e309973 | 268 | } |
3d2924e9 DM |
269 | |
270 | if ('all' === $userid) { | |
271 | // no additional conditions | |
272 | } elseif (is_array($userid)) { | |
273 | list($usql, $uparams) = $DB->get_in_or_equal($userid, SQL_PARAMS_NAMED); | |
274 | $sql .= " AND userid $usql"; | |
6e309973 | 275 | $params = array_merge($params, $uparams); |
3d2924e9 | 276 | } else { |
235b31c8 | 277 | $sql .= ' AND userid = :userid'; |
3d2924e9 | 278 | $params['userid'] = $userid; |
6e309973 | 279 | } |
3dc78e5b | 280 | $sql .= ' ORDER BY u.lastname, u.firstname'; |
6e309973 | 281 | |
3dc78e5b | 282 | return $DB->get_records_sql($sql, $params); |
6e309973 DM |
283 | } |
284 | ||
51508f25 DM |
285 | /** |
286 | * Returns a submission record with the author's data | |
287 | * | |
288 | * @param int $id submission id | |
289 | * @return stdClass | |
290 | */ | |
291 | public function get_submission_by_id($id) { | |
292 | global $DB; | |
293 | ||
294 | $sql = 'SELECT s.*, | |
295 | u.lastname AS authorlastname, u.firstname AS authorfirstname, u.id AS authorid, | |
296 | u.picture AS authorpicture, u.imagealt AS authorimagealt | |
297 | FROM {workshop_submissions} s | |
298 | INNER JOIN {user} u ON (s.userid = u.id) | |
299 | WHERE s.workshopid = :workshopid AND s.id = :id'; | |
300 | $params = array('workshopid' => $this->id, 'id' => $id); | |
301 | return $DB->get_record_sql($sql, $params, MUST_EXIST); | |
302 | } | |
303 | ||
53fad4b9 | 304 | /** |
3dc78e5b | 305 | * Returns a submission submitted by the given author |
53fad4b9 | 306 | * |
3dc78e5b DM |
307 | * @param int $id author id |
308 | * @return stdClass|false | |
53fad4b9 DM |
309 | */ |
310 | public function get_submission_by_author($id) { | |
e9b0f0ab DM |
311 | global $DB; |
312 | ||
53fad4b9 DM |
313 | if (empty($id)) { |
314 | return false; | |
315 | } | |
3dc78e5b DM |
316 | $sql = 'SELECT s.*, |
317 | u.lastname AS authorlastname, u.firstname AS authorfirstname, u.id AS authorid, | |
318 | u.picture AS authorpicture, u.imagealt AS authorimagealt | |
319 | FROM {workshop_submissions} s | |
320 | INNER JOIN {user} u ON (s.userid = u.id) | |
321 | WHERE s.example = 0 AND s.workshopid = :workshopid AND s.userid = :userid'; | |
322 | $params = array('workshopid' => $this->id, 'userid' => $id); | |
323 | return $DB->get_record_sql($sql, $params); | |
53fad4b9 | 324 | } |
6e309973 DM |
325 | |
326 | /** | |
3dc78e5b | 327 | * Returns the list of all assessments in the workshop with some data added |
6e309973 DM |
328 | * |
329 | * Fetches data from {workshop_assessments} and adds some useful information from other | |
3dc78e5b DM |
330 | * tables. The returned object does not contain textual fields (ie comments) to prevent memory |
331 | * lack issues. | |
332 | * | |
333 | * @return array [assessmentid] => assessment stdClass | |
6e309973 | 334 | */ |
3dc78e5b | 335 | public function get_all_assessments() { |
6e309973 | 336 | global $DB; |
53fad4b9 | 337 | |
3dc78e5b DM |
338 | $sql = 'SELECT a.id, a.submissionid, a.userid, a.timecreated, a.timemodified, a.timeagreed, |
339 | a.grade, a.gradinggrade, a.gradinggradeover, a.gradinggradeoverby, | |
3d2924e9 DM |
340 | reviewer.id AS reviewerid,reviewer.firstname AS reviewerfirstname,reviewer.lastname as reviewerlastname, |
341 | s.title, | |
ddb59c77 | 342 | author.id AS authorid, author.firstname AS authorfirstname,author.lastname AS authorlastname |
3d2924e9 DM |
343 | FROM {workshop_assessments} a |
344 | INNER JOIN {user} reviewer ON (a.userid = reviewer.id) | |
345 | INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id) | |
346 | INNER JOIN {user} author ON (s.userid = author.id) | |
3dc78e5b DM |
347 | WHERE s.workshopid = :workshopid AND s.example = 0 |
348 | ORDER BY reviewer.lastname, reviewer.firstname'; | |
3d2924e9 DM |
349 | $params = array('workshopid' => $this->id); |
350 | ||
3dc78e5b | 351 | return $DB->get_records_sql($sql, $params); |
53fad4b9 DM |
352 | } |
353 | ||
354 | /** | |
3dc78e5b | 355 | * Get the complete information about the given assessment |
53fad4b9 DM |
356 | * |
357 | * @param int $id Assessment ID | |
65ba104c | 358 | * @return mixed false if not found, stdClass otherwise |
53fad4b9 DM |
359 | */ |
360 | public function get_assessment_by_id($id) { | |
3dc78e5b DM |
361 | global $DB; |
362 | ||
363 | $sql = 'SELECT a.*, | |
364 | reviewer.id AS reviewerid,reviewer.firstname AS reviewerfirstname,reviewer.lastname as reviewerlastname, | |
365 | s.title, | |
366 | author.id AS authorid, author.firstname AS authorfirstname,author.lastname as authorlastname | |
367 | FROM {workshop_assessments} a | |
368 | INNER JOIN {user} reviewer ON (a.userid = reviewer.id) | |
369 | INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id) | |
370 | INNER JOIN {user} author ON (s.userid = author.id) | |
371 | WHERE a.id = :id AND s.workshopid = :workshopid'; | |
372 | $params = array('id' => $id, 'workshopid' => $this->id); | |
373 | ||
374 | return $DB->get_record_sql($sql, $params, MUST_EXIST); | |
53fad4b9 DM |
375 | } |
376 | ||
377 | /** | |
3dc78e5b | 378 | * Get the complete information about all assessments allocated to the given reviewer |
53fad4b9 | 379 | * |
3dc78e5b DM |
380 | * @param int $userid reviewer id |
381 | * @return array | |
53fad4b9 | 382 | */ |
3dc78e5b DM |
383 | public function get_assessments_by_reviewer($userid) { |
384 | global $DB; | |
385 | ||
386 | $sql = 'SELECT a.*, | |
ddb59c77 DM |
387 | reviewer.id AS reviewerid,reviewer.firstname AS reviewerfirstname,reviewer.lastname AS reviewerlastname, |
388 | s.id AS submissionid, s.title AS submissiontitle, s.timecreated AS submissioncreated, | |
389 | s.timemodified AS submissionmodified, | |
390 | author.id AS authorid, author.firstname AS authorfirstname,author.lastname AS authorlastname, | |
391 | author.picture AS authorpicture, author.imagealt AS authorimagealt | |
3dc78e5b DM |
392 | FROM {workshop_assessments} a |
393 | INNER JOIN {user} reviewer ON (a.userid = reviewer.id) | |
394 | INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id) | |
395 | INNER JOIN {user} author ON (s.userid = author.id) | |
396 | WHERE s.example = 0 AND reviewer.id = :userid AND s.workshopid = :workshopid'; | |
397 | $params = array('userid' => $userid, 'workshopid' => $this->id); | |
398 | ||
399 | return $DB->get_records_sql($sql, $params); | |
53fad4b9 | 400 | } |
6e309973 | 401 | |
6e309973 DM |
402 | /** |
403 | * Allocate a submission to a user for review | |
53fad4b9 | 404 | * |
65ba104c | 405 | * @param stdClass $submission Submission record |
6e309973 | 406 | * @param int $reviewerid User ID |
53fad4b9 | 407 | * @param bool $bulk repeated inserts into DB expected |
6e309973 DM |
408 | * @return int ID of the new assessment or an error code |
409 | */ | |
65ba104c | 410 | public function add_allocation(stdClass $submission, $reviewerid, $bulk=false) { |
6e309973 DM |
411 | global $DB; |
412 | ||
413 | if ($DB->record_exists('workshop_assessments', array('submissionid' => $submission->id, 'userid' => $reviewerid))) { | |
b761e6d9 | 414 | return self::ALLOCATION_EXISTS; |
6e309973 DM |
415 | } |
416 | ||
6e309973 | 417 | $now = time(); |
65ba104c | 418 | $assessment = new stdClass(); |
53fad4b9 | 419 | $assessment->submissionid = $submission->id; |
6e309973 DM |
420 | $assessment->userid = $reviewerid; |
421 | $assessment->timecreated = $now; | |
422 | $assessment->timemodified = $now; | |
423 | ||
235b31c8 | 424 | return $DB->insert_record('workshop_assessments', $assessment, true, $bulk); |
6e309973 DM |
425 | } |
426 | ||
6e309973 | 427 | /** |
53fad4b9 | 428 | * Delete assessment record or records |
6e309973 | 429 | * |
53fad4b9 DM |
430 | * @param mixed $id int|array assessment id or array of assessments ids |
431 | * @return bool false if $id not a valid parameter, true otherwise | |
6e309973 DM |
432 | */ |
433 | public function delete_assessment($id) { | |
434 | global $DB; | |
435 | ||
436 | // todo remove all given grades from workshop_grades; | |
6e309973 | 437 | |
53fad4b9 | 438 | if (is_array($id)) { |
235b31c8 | 439 | return $DB->delete_records_list('workshop_assessments', 'id', $id); |
3d2924e9 | 440 | } else { |
235b31c8 | 441 | return $DB->delete_records('workshop_assessments', array('id' => $id)); |
53fad4b9 | 442 | } |
53fad4b9 | 443 | } |
6e309973 DM |
444 | |
445 | /** | |
446 | * Returns instance of grading strategy class | |
53fad4b9 | 447 | * |
65ba104c | 448 | * @return stdClass Instance of a grading strategy |
6e309973 DM |
449 | */ |
450 | public function grading_strategy_instance() { | |
3d2924e9 DM |
451 | global $CFG; // because we require other libs here |
452 | ||
3fd2b0e1 | 453 | if (is_null($this->strategyinstance)) { |
f05c168d | 454 | $strategylib = dirname(__FILE__) . '/form/' . $this->strategy . '/lib.php'; |
6e309973 DM |
455 | if (is_readable($strategylib)) { |
456 | require_once($strategylib); | |
457 | } else { | |
f05c168d | 458 | throw new coding_exception('the grading forms subplugin must contain library ' . $strategylib); |
6e309973 | 459 | } |
0dc47fb9 | 460 | $classname = 'workshop_' . $this->strategy . '_strategy'; |
3fd2b0e1 DM |
461 | $this->strategyinstance = new $classname($this); |
462 | if (!in_array('workshop_strategy', class_implements($this->strategyinstance))) { | |
b761e6d9 | 463 | throw new coding_exception($classname . ' does not implement workshop_strategy interface'); |
6e309973 DM |
464 | } |
465 | } | |
3fd2b0e1 | 466 | return $this->strategyinstance; |
6e309973 DM |
467 | } |
468 | ||
45d24d39 DM |
469 | /** |
470 | * Returns instance of grading evaluation class | |
471 | * | |
472 | * @return stdClass Instance of a grading evaluation | |
473 | */ | |
474 | public function grading_evaluation_instance() { | |
475 | global $CFG; // because we require other libs here | |
476 | ||
477 | if (is_null($this->evaluationinstance)) { | |
478 | $evaluationlib = dirname(__FILE__) . '/eval/' . $this->evaluation . '/lib.php'; | |
479 | if (is_readable($evaluationlib)) { | |
480 | require_once($evaluationlib); | |
481 | } else { | |
482 | throw new coding_exception('the grading evaluation subplugin must contain library ' . $evaluationlib); | |
483 | } | |
484 | $classname = 'workshop_' . $this->evaluation . '_evaluation'; | |
485 | $this->evaluationinstance = new $classname($this); | |
486 | if (!in_array('workshop_evaluation', class_implements($this->evaluationinstance))) { | |
487 | throw new coding_exception($classname . ' does not implement workshop_evaluation interface'); | |
488 | } | |
489 | } | |
490 | return $this->evaluationinstance; | |
491 | } | |
492 | ||
66c9894d DM |
493 | /** |
494 | * Returns instance of submissions allocator | |
53fad4b9 | 495 | * |
65ba104c DM |
496 | * @param stdClass $method The name of the allocation method, must be PARAM_ALPHA |
497 | * @return stdClass Instance of submissions allocator | |
66c9894d DM |
498 | */ |
499 | public function allocator_instance($method) { | |
3d2924e9 DM |
500 | global $CFG; // because we require other libs here |
501 | ||
f05c168d | 502 | $allocationlib = dirname(__FILE__) . '/allocation/' . $method . '/lib.php'; |
66c9894d DM |
503 | if (is_readable($allocationlib)) { |
504 | require_once($allocationlib); | |
505 | } else { | |
f05c168d | 506 | throw new coding_exception('Unable to find the allocation library ' . $allocationlib); |
66c9894d DM |
507 | } |
508 | $classname = 'workshop_' . $method . '_allocator'; | |
509 | return new $classname($this); | |
510 | } | |
511 | ||
b8ead2e6 | 512 | /** |
454e8dd9 | 513 | * @return moodle_url of this workshop's view page |
b8ead2e6 DM |
514 | */ |
515 | public function view_url() { | |
516 | global $CFG; | |
517 | return new moodle_url($CFG->wwwroot . '/mod/workshop/view.php', array('id' => $this->cm->id)); | |
518 | } | |
519 | ||
520 | /** | |
454e8dd9 | 521 | * @return moodle_url of the page for editing this workshop's grading form |
b8ead2e6 DM |
522 | */ |
523 | public function editform_url() { | |
524 | global $CFG; | |
525 | return new moodle_url($CFG->wwwroot . '/mod/workshop/editform.php', array('cmid' => $this->cm->id)); | |
526 | } | |
527 | ||
528 | /** | |
454e8dd9 | 529 | * @return moodle_url of the page for previewing this workshop's grading form |
b8ead2e6 DM |
530 | */ |
531 | public function previewform_url() { | |
532 | global $CFG; | |
533 | return new moodle_url($CFG->wwwroot . '/mod/workshop/assessment.php', array('preview' => $this->cm->id)); | |
534 | } | |
535 | ||
536 | /** | |
537 | * @param int $assessmentid The ID of assessment record | |
454e8dd9 | 538 | * @return moodle_url of the assessment page |
b8ead2e6 | 539 | */ |
a39d7d87 | 540 | public function assess_url($assessmentid) { |
b8ead2e6 | 541 | global $CFG; |
454e8dd9 | 542 | $assessmentid = clean_param($assessmentid, PARAM_INT); |
a39d7d87 | 543 | return new moodle_url($CFG->wwwroot . '/mod/workshop/assessment.php', array('asid' => $assessmentid)); |
b8ead2e6 DM |
544 | } |
545 | ||
39861053 | 546 | /** |
454e8dd9 | 547 | * @return moodle_url of the page to view own submission |
39861053 DM |
548 | */ |
549 | public function submission_url() { | |
550 | global $CFG; | |
551 | return new moodle_url($CFG->wwwroot . '/mod/workshop/submission.php', array('cmid' => $this->cm->id)); | |
552 | } | |
553 | ||
da0b1f70 | 554 | /** |
454e8dd9 | 555 | * @return moodle_url of the mod_edit form |
da0b1f70 DM |
556 | */ |
557 | public function updatemod_url() { | |
558 | global $CFG; | |
559 | return new moodle_url($CFG->wwwroot . '/course/modedit.php', array('update' => $this->cm->id, 'return' => 1)); | |
560 | } | |
561 | ||
454e8dd9 DM |
562 | /** |
563 | * @return moodle_url to the allocation page | |
564 | */ | |
da0b1f70 DM |
565 | public function allocation_url() { |
566 | global $CFG; | |
567 | return new moodle_url($CFG->wwwroot . '/mod/workshop/allocation.php', array('cmid' => $this->cm->id)); | |
568 | } | |
569 | ||
454e8dd9 DM |
570 | /** |
571 | * @param int $phasecode The internal phase code | |
572 | * @return moodle_url of the script to change the current phase to $phasecode | |
573 | */ | |
574 | public function switchphase_url($phasecode) { | |
575 | global $CFG; | |
576 | $phasecode = clean_param($phasecode, PARAM_INT); | |
577 | return new moodle_url($CFG->wwwroot . '/mod/workshop/switchphase.php', array('cmid' => $this->cm->id, 'phase' => $phasecode)); | |
578 | } | |
579 | ||
b8ead2e6 DM |
580 | /** |
581 | * Returns an object containing all data to display the user's full name and picture | |
582 | * | |
583 | * @param int $id optional user id, defaults to the current user | |
65ba104c | 584 | * @return stdClass containing properties lastname, firstname, picture and imagealt |
b8ead2e6 DM |
585 | */ |
586 | public function user_info($id=null) { | |
587 | global $USER, $DB; | |
588 | ||
589 | if (is_null($id) || ($id == $USER->id)) { | |
590 | return $USER; | |
591 | } else { | |
592 | return $DB->get_record('user', array('id' => $id), 'id,lastname,firstname,picture,imagealt', MUST_EXIST); | |
593 | } | |
594 | } | |
595 | ||
c1e883bb | 596 | /** |
b13142da | 597 | * Are users allowed to create/edit their submissions? |
c1e883bb DM |
598 | * |
599 | * TODO: this depends on the workshop phase, phase deadlines, submitting after deadlines possibility | |
600 | * | |
601 | * @return bool | |
602 | */ | |
603 | public function submitting_allowed() { | |
604 | return true; | |
605 | } | |
606 | ||
3dc78e5b DM |
607 | /** |
608 | * Are the peer-reviews available to the authors? | |
609 | * | |
610 | * TODO: this depends on the workshop phase | |
611 | * | |
612 | * @return bool | |
613 | */ | |
614 | public function assessments_available() { | |
615 | return true; | |
616 | } | |
617 | ||
618 | /** | |
619 | * Can the given grades be displayed to the authors? | |
620 | * | |
621 | * Grades are not displayed if {@link self::assessments_available()} return false. The returned | |
622 | * value may be true (if yes, display grades), false (no, hide grades yet) or null (only | |
623 | * display grades if the assessment has been agreed by the author). | |
624 | * | |
625 | * @return bool|null | |
626 | */ | |
627 | public function grades_available() { | |
628 | return true; | |
629 | } | |
630 | ||
b13142da DM |
631 | /** |
632 | * Returns the localized name of the grading strategy method to be displayed to the users | |
633 | * | |
634 | * @return string | |
635 | */ | |
636 | public function strategy_name() { | |
f05c168d | 637 | return get_string('pluginname', 'workshopform_' . $this->strategy); |
b13142da | 638 | } |
b761e6d9 DM |
639 | |
640 | /** | |
641 | * Prepare an individual workshop plan for the given user. | |
642 | * | |
f05c168d DM |
643 | * @param int $userid whom the plan is prepared for |
644 | * @param stdClass context of the planned workshop | |
645 | * @return stdClass data object to be passed to the renderer | |
b761e6d9 | 646 | */ |
f05c168d | 647 | public function prepare_user_plan($userid, stdClass $context) { |
b761e6d9 DM |
648 | global $DB; |
649 | ||
650 | $phases = array(); | |
651 | ||
652 | // Prepare tasks for the setup phase | |
653 | $phase = new stdClass(); | |
654 | $phase->title = get_string('phasesetup', 'workshop'); | |
655 | $phase->tasks = array(); | |
f05c168d | 656 | if (has_capability('moodle/course:manageactivities', $context, $userid)) { |
da0b1f70 DM |
657 | $task = new stdClass(); |
658 | $task->title = get_string('taskintro', 'workshop'); | |
659 | $task->link = $this->updatemod_url(); | |
660 | $task->completed = !(trim(strip_tags($this->intro)) == ''); | |
661 | $phase->tasks['intro'] = $task; | |
662 | } | |
f05c168d | 663 | if (has_capability('moodle/course:manageactivities', $context, $userid)) { |
454e8dd9 DM |
664 | $task = new stdClass(); |
665 | $task->title = get_string('taskinstructauthors', 'workshop'); | |
666 | $task->link = $this->updatemod_url(); | |
667 | $task->completed = !(trim(strip_tags($this->instructauthors)) == ''); | |
668 | $phase->tasks['instructauthors'] = $task; | |
669 | } | |
f05c168d | 670 | if (has_capability('mod/workshop:editdimensions', $context, $userid)) { |
b761e6d9 | 671 | $task = new stdClass(); |
da0b1f70 DM |
672 | $task->title = get_string('editassessmentform', 'workshop'); |
673 | $task->link = $this->editform_url(); | |
674 | if ($this->assessment_form_ready()) { | |
675 | $task->completed = true; | |
676 | } elseif ($this->phase > self::PHASE_SETUP) { | |
677 | $task->completed = false; | |
678 | } | |
b761e6d9 DM |
679 | $phase->tasks['editform'] = $task; |
680 | } | |
da0b1f70 DM |
681 | if (empty($phase->tasks) and $this->phase == self::PHASE_SETUP) { |
682 | // if we are in the setup phase and there is no task (typical for students), let us | |
683 | // display some explanation what is going on | |
684 | $task = new stdClass(); | |
685 | $task->title = get_string('undersetup', 'workshop'); | |
686 | $task->completed = 'info'; | |
687 | $phase->tasks['setupinfo'] = $task; | |
688 | } | |
b761e6d9 DM |
689 | $phases[self::PHASE_SETUP] = $phase; |
690 | ||
691 | // Prepare tasks for the submission phase | |
692 | $phase = new stdClass(); | |
693 | $phase->title = get_string('phasesubmission', 'workshop'); | |
694 | $phase->tasks = array(); | |
f05c168d | 695 | if (has_capability('mod/workshop:submit', $context, $userid)) { |
b761e6d9 DM |
696 | $task = new stdClass(); |
697 | $task->title = get_string('tasksubmit', 'workshop'); | |
da0b1f70 DM |
698 | $task->link = $this->submission_url(); |
699 | if ($DB->record_exists('workshop_submissions', array('workshopid'=>$this->id, 'example'=>0, 'userid'=>$userid))) { | |
700 | $task->completed = true; | |
701 | } elseif ($this->phase >= self::PHASE_ASSESSMENT) { | |
702 | $task->completed = false; | |
703 | } else { | |
704 | $task->completed = null; // still has a chance to submit | |
705 | } | |
b761e6d9 DM |
706 | $phase->tasks['submit'] = $task; |
707 | } | |
f05c168d | 708 | if (has_capability('moodle/course:manageactivities', $context, $userid)) { |
da0b1f70 DM |
709 | $task = new stdClass(); |
710 | $task->title = get_string('taskinstructreviewers', 'workshop'); | |
711 | $task->link = $this->updatemod_url(); | |
712 | if (trim(strip_tags($this->instructreviewers))) { | |
713 | $task->completed = true; | |
714 | } elseif ($this->phase >= self::PHASE_ASSESSMENT) { | |
715 | $task->completed = false; | |
716 | } | |
717 | $phase->tasks['instructreviewers'] = $task; | |
718 | } | |
b761e6d9 | 719 | $phases[self::PHASE_SUBMISSION] = $phase; |
f05c168d | 720 | if (has_capability('mod/workshop:allocate', $context, $userid)) { |
da0b1f70 DM |
721 | $task = new stdClass(); |
722 | $task->title = get_string('allocate', 'workshop'); | |
723 | $task->link = $this->allocation_url(); | |
3189fb2d | 724 | $authors = array(); |
da0b1f70 | 725 | $allocations = array(); // 'submissionid' => isallocated |
aa40adbf DM |
726 | $records = $this->get_allocations(); |
727 | foreach ($records as $allocation) { | |
728 | if (!isset($authors[$allocation->authorid])) { | |
729 | $authors[$allocation->authorid] = true; | |
730 | } | |
731 | if (isset($allocation->submissionid)) { | |
732 | if (!isset($allocations[$allocation->submissionid])) { | |
733 | $allocations[$allocation->submissionid] = false; | |
3189fb2d | 734 | } |
aa40adbf DM |
735 | if (!empty($allocation->reviewerid)) { |
736 | $allocations[$allocation->submissionid] = true; | |
3189fb2d | 737 | } |
da0b1f70 DM |
738 | } |
739 | } | |
3189fb2d | 740 | $numofauthors = count($authors); |
da0b1f70 DM |
741 | $numofsubmissions = count($allocations); |
742 | $numofallocated = count(array_filter($allocations)); | |
da0b1f70 DM |
743 | if ($numofsubmissions == 0) { |
744 | $task->completed = null; | |
3dc78e5b | 745 | } elseif ($numofsubmissions == $numofallocated) { |
da0b1f70 DM |
746 | $task->completed = true; |
747 | } elseif ($this->phase > self::PHASE_SUBMISSION) { | |
748 | $task->completed = false; | |
749 | } else { | |
750 | $task->completed = null; // still has a chance to allocate | |
751 | } | |
752 | $a = new stdClass(); | |
3189fb2d DM |
753 | $a->expected = $numofauthors; |
754 | $a->submitted = $numofsubmissions; | |
755 | $a->allocated = $numofallocated; | |
756 | $task->details = get_string('allocatedetails', 'workshop', $a); | |
da0b1f70 | 757 | unset($a); |
3189fb2d | 758 | $phase->tasks['allocate'] = $task; |
3dc78e5b DM |
759 | |
760 | if ($numofsubmissions < $numofauthors and $this->phase >= self::PHASE_SUBMISSION) { | |
761 | $task = new stdClass(); | |
762 | $task->title = get_string('someuserswosubmission', 'workshop'); | |
763 | $task->completed = 'info'; | |
764 | $phase->tasks['allocateinfo'] = $task; | |
765 | } | |
da0b1f70 | 766 | } |
b761e6d9 DM |
767 | |
768 | // Prepare tasks for the peer-assessment phase (includes eventual self-assessments) | |
769 | $phase = new stdClass(); | |
770 | $phase->title = get_string('phaseassessment', 'workshop'); | |
771 | $phase->tasks = array(); | |
f05c168d | 772 | $phase->isreviewer = has_capability('mod/workshop:peerassess', $context, $userid); |
3dc78e5b | 773 | $phase->assessments = $this->get_assessments_by_reviewer($userid); |
b761e6d9 DM |
774 | $numofpeers = 0; // number of allocated peer-assessments |
775 | $numofpeerstodo = 0; // number of peer-assessments to do | |
776 | $numofself = 0; // number of allocated self-assessments - should be 0 or 1 | |
777 | $numofselftodo = 0; // number of self-assessments to do - should be 0 or 1 | |
778 | foreach ($phase->assessments as $a) { | |
779 | if ($a->authorid == $userid) { | |
780 | $numofself++; | |
781 | if (is_null($a->grade)) { | |
782 | $numofselftodo++; | |
783 | } | |
784 | } else { | |
785 | $numofpeers++; | |
786 | if (is_null($a->grade)) { | |
787 | $numofpeerstodo++; | |
788 | } | |
789 | } | |
790 | } | |
791 | unset($a); | |
792 | if ($numofpeers) { | |
793 | $task = new stdClass(); | |
3dc78e5b DM |
794 | if ($numofpeerstodo == 0) { |
795 | $task->completed = true; | |
796 | } elseif ($this->phase > self::PHASE_ASSESSMENT) { | |
797 | $task->completed = false; | |
798 | } | |
b761e6d9 DM |
799 | $a = new stdClass(); |
800 | $a->total = $numofpeers; | |
801 | $a->todo = $numofpeerstodo; | |
802 | $task->title = get_string('taskassesspeers', 'workshop'); | |
da0b1f70 | 803 | $task->details = get_string('taskassesspeersdetails', 'workshop', $a); |
b761e6d9 DM |
804 | unset($a); |
805 | $phase->tasks['assesspeers'] = $task; | |
806 | } | |
807 | if ($numofself) { | |
808 | $task = new stdClass(); | |
3dc78e5b DM |
809 | if ($numofselftodo == 0) { |
810 | $task->completed = true; | |
811 | } elseif ($this->phase > self::PHASE_ASSESSMENT) { | |
812 | $task->completed = false; | |
813 | } | |
b761e6d9 DM |
814 | $task->title = get_string('taskassessself', 'workshop'); |
815 | $phase->tasks['assessself'] = $task; | |
816 | } | |
817 | $phases[self::PHASE_ASSESSMENT] = $phase; | |
818 | ||
819 | // Prepare tasks for the grading evaluation phase - todo | |
820 | $phase = new stdClass(); | |
821 | $phase->title = get_string('phaseevaluation', 'workshop'); | |
822 | $phase->tasks = array(); | |
823 | $phases[self::PHASE_EVALUATION] = $phase; | |
824 | ||
825 | // Prepare tasks for the "workshop closed" phase - todo | |
826 | $phase = new stdClass(); | |
827 | $phase->title = get_string('phaseclosed', 'workshop'); | |
828 | $phase->tasks = array(); | |
829 | $phases[self::PHASE_CLOSED] = $phase; | |
830 | ||
831 | // Polish data, set default values if not done explicitly | |
832 | foreach ($phases as $phasecode => $phase) { | |
833 | $phase->title = isset($phase->title) ? $phase->title : ''; | |
834 | $phase->tasks = isset($phase->tasks) ? $phase->tasks : array(); | |
835 | if ($phasecode == $this->phase) { | |
836 | $phase->active = true; | |
837 | } else { | |
838 | $phase->active = false; | |
839 | } | |
454e8dd9 DM |
840 | if (!isset($phase->actions)) { |
841 | $phase->actions = array(); | |
842 | } | |
b761e6d9 DM |
843 | |
844 | foreach ($phase->tasks as $taskcode => $task) { | |
845 | $task->title = isset($task->title) ? $task->title : ''; | |
da0b1f70 DM |
846 | $task->link = isset($task->link) ? $task->link : null; |
847 | $task->details = isset($task->details) ? $task->details : ''; | |
b761e6d9 DM |
848 | $task->completed = isset($task->completed) ? $task->completed : null; |
849 | } | |
850 | } | |
454e8dd9 DM |
851 | |
852 | // Add phase swithing actions | |
f05c168d | 853 | if (has_capability('mod/workshop:switchphase', $context, $userid)) { |
454e8dd9 DM |
854 | foreach ($phases as $phasecode => $phase) { |
855 | if (! $phase->active) { | |
856 | $action = new stdClass(); | |
857 | $action->type = 'switchphase'; | |
858 | $action->url = $this->switchphase_url($phasecode); | |
859 | $phase->actions[] = $action; | |
860 | } | |
861 | } | |
862 | } | |
863 | ||
b761e6d9 DM |
864 | return $phases; |
865 | } | |
866 | ||
867 | /** | |
868 | * Has the assessment form been defined? | |
869 | * | |
870 | * @return bool | |
871 | */ | |
872 | public function assessment_form_ready() { | |
873 | return $this->grading_strategy_instance()->form_ready(); | |
874 | } | |
875 | ||
454e8dd9 DM |
876 | /** |
877 | * Switch to a new workshop phase | |
878 | * | |
879 | * Modifies the underlying database record. You should terminate the script shortly after calling this. | |
880 | * | |
881 | * @param int $newphase new phase code | |
882 | * @return bool true if success, false otherwise | |
883 | */ | |
884 | public function switch_phase($newphase) { | |
885 | global $DB; | |
886 | ||
887 | $known = $this->available_phases(); | |
888 | if (!isset($known[$newphase])) { | |
889 | return false; | |
890 | } | |
891 | $DB->set_field('workshop', 'phase', $newphase, array('id' => $this->id)); | |
892 | return true; | |
893 | } | |
ddb59c77 DM |
894 | |
895 | /** | |
896 | * Saves a raw grade for submission as calculated from the assessment form fields | |
897 | * | |
898 | * @param array $assessmentid assessment record id, must exists | |
899 | * @param mixed $grade raw percentual grade from 0 to 1 | |
900 | * @return false|float the saved grade | |
901 | */ | |
902 | public function set_peer_grade($assessmentid, $grade) { | |
903 | global $DB; | |
904 | ||
905 | if (is_null($grade)) { | |
906 | return false; | |
907 | } | |
908 | $data = new stdClass(); | |
909 | $data->id = $assessmentid; | |
910 | $data->grade = $grade; | |
911 | $DB->update_record('workshop_assessments', $data); | |
912 | return $grade; | |
913 | } | |
6516b9e9 | 914 | |
aa40adbf DM |
915 | //////////////////////////////////////////////////////////////////////////////// |
916 | // Internal methods (implementation details) // | |
917 | //////////////////////////////////////////////////////////////////////////////// | |
6516b9e9 DM |
918 | |
919 | /** | |
aa40adbf | 920 | * Given a list of user ids, returns the filtered one containing just ids of users with own submission |
6516b9e9 | 921 | * |
aa40adbf DM |
922 | * Example submissions are ignored. |
923 | * | |
924 | * @param array $userids | |
6516b9e9 DM |
925 | * @return array |
926 | */ | |
aa40adbf DM |
927 | protected function users_with_submission(array $userids) { |
928 | global $DB; | |
929 | ||
930 | if (empty($userids)) { | |
931 | return array(); | |
932 | } | |
933 | $userswithsubmission = array(); | |
934 | list($usql, $uparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED); | |
935 | $sql = "SELECT id,userid | |
936 | FROM {workshop_submissions} | |
937 | WHERE example = 0 AND workshopid = :workshopid AND userid $usql"; | |
938 | $params = array('workshopid' => $this->id); | |
939 | $params = array_merge($params, $uparams); | |
940 | $submissions = $DB->get_records_sql($sql, $params); | |
941 | foreach ($submissions as $submission) { | |
942 | $userswithsubmission[$submission->userid] = null; | |
943 | } | |
944 | ||
945 | return $userswithsubmission; | |
6516b9e9 DM |
946 | } |
947 | ||
aa40adbf DM |
948 | /** |
949 | * @return array of available workshop phases | |
950 | */ | |
951 | protected function available_phases() { | |
952 | return array( | |
953 | self::PHASE_SETUP => true, | |
954 | self::PHASE_SUBMISSION => true, | |
955 | self::PHASE_ASSESSMENT => true, | |
956 | self::PHASE_EVALUATION => true, | |
957 | self::PHASE_CLOSED => true, | |
958 | ); | |
959 | } | |
960 | ||
961 | ||
66c9894d | 962 | } |