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