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