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