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