Added instructions for submitting
[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,
ddb59c77 314 author.id AS authorid, author.firstname AS authorfirstname,author.lastname AS authorlastname
3d2924e9
DM
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.*,
ddb59c77
DM
359 reviewer.id AS reviewerid,reviewer.firstname AS reviewerfirstname,reviewer.lastname AS reviewerlastname,
360 s.id AS submissionid, s.title AS submissiontitle, s.timecreated AS submissioncreated,
361 s.timemodified AS submissionmodified,
362 author.id AS authorid, author.firstname AS authorfirstname,author.lastname AS authorlastname,
363 author.picture AS authorpicture, author.imagealt AS authorimagealt
3dc78e5b
DM
364 FROM {workshop_assessments} a
365 INNER JOIN {user} reviewer ON (a.userid = reviewer.id)
366 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
367 INNER JOIN {user} author ON (s.userid = author.id)
368 WHERE s.example = 0 AND reviewer.id = :userid AND s.workshopid = :workshopid';
369 $params = array('userid' => $userid, 'workshopid' => $this->id);
370
371 return $DB->get_records_sql($sql, $params);
53fad4b9 372 }
6e309973
DM
373
374 /**
375 * Returns the list of allocations in the workshop
376 *
377 * This returns the list of all users who can submit their work or review submissions (or both
378 * which is the common case). So basically this is to return list of all students participating
379 * in the workshop. For every participant, it adds information about their submission and their
3189fb2d 380 * reviews, if such information is available (null elsewhere).
6e309973
DM
381 *
382 * The returned structure is recordset of objects with following properties:
383 * [authorid] [authorfirstname] [authorlastname] [authorpicture] [authorimagealt]
384 * [submissionid] [submissiontitle] [submissiongrade] [assessmentid]
53fad4b9 385 * [timeallocated] [reviewerid] [reviewerfirstname] [reviewerlastname]
6e309973
DM
386 * [reviewerpicture] [reviewerimagealt]
387 *
3d2924e9 388 * TODO This should be refactored when capability handling proposed by Petr is implemented so that
6e309973 389 * we can check capabilities directly in SQL joins.
53fad4b9
DM
390 * Note that the returned recordset includes participants without submission as well as those
391 * without any review allocated yet.
6e309973 392 *
65ba104c 393 * @return stdClass moodle_recordset
6e309973 394 */
66c9894d 395 public function get_allocations_recordset() {
6e309973 396 global $DB;
6e309973 397
b761e6d9 398 $users = get_users_by_capability($this->context, array('mod/workshop:submit', 'mod/workshop:peerassess'),
235b31c8 399 'u.id', 'u.lastname,u.firstname', '', '', '', '', false, false, true);
3d2924e9
DM
400
401 list($usql, $params) = $DB->get_in_or_equal(array_keys($users), SQL_PARAMS_NAMED);
402 $params['workshopid'] = $this->id;
403
1dbbccb7 404 $sql = "SELECT author.id AS authorid, author.firstname AS authorfirstname, author.lastname AS authorlastname,
3d2924e9
DM
405 author.picture AS authorpicture, author.imagealt AS authorimagealt,
406 s.id AS submissionid, s.title AS submissiontitle, s.grade AS submissiongrade,
407 a.id AS assessmentid, a.timecreated AS timeallocated, a.userid AS reviewerid,
408 reviewer.firstname AS reviewerfirstname, reviewer.lastname AS reviewerlastname,
409 reviewer.picture as reviewerpicture, reviewer.imagealt AS reviewerimagealt
410 FROM {user} author
411 LEFT JOIN {workshop_submissions} s ON (s.userid = author.id)
412 LEFT JOIN {workshop_assessments} a ON (s.id = a.submissionid)
413 LEFT JOIN {user} reviewer ON (a.userid = reviewer.id)
3189fb2d 414 WHERE author.id $usql AND (s.id IS NULL OR s.workshopid = :workshopid)
1dbbccb7 415 ORDER BY author.lastname,author.firstname,reviewer.lastname,reviewer.firstname";
3189fb2d 416
6e309973
DM
417 return $DB->get_recordset_sql($sql, $params);
418 }
419
6e309973
DM
420 /**
421 * Allocate a submission to a user for review
53fad4b9 422 *
65ba104c 423 * @param stdClass $submission Submission record
6e309973 424 * @param int $reviewerid User ID
53fad4b9 425 * @param bool $bulk repeated inserts into DB expected
6e309973
DM
426 * @return int ID of the new assessment or an error code
427 */
65ba104c 428 public function add_allocation(stdClass $submission, $reviewerid, $bulk=false) {
6e309973
DM
429 global $DB;
430
431 if ($DB->record_exists('workshop_assessments', array('submissionid' => $submission->id, 'userid' => $reviewerid))) {
b761e6d9 432 return self::ALLOCATION_EXISTS;
6e309973
DM
433 }
434
6e309973 435 $now = time();
65ba104c 436 $assessment = new stdClass();
53fad4b9 437 $assessment->submissionid = $submission->id;
6e309973
DM
438 $assessment->userid = $reviewerid;
439 $assessment->timecreated = $now;
440 $assessment->timemodified = $now;
441
235b31c8 442 return $DB->insert_record('workshop_assessments', $assessment, true, $bulk);
6e309973
DM
443 }
444
6e309973 445 /**
53fad4b9 446 * Delete assessment record or records
6e309973 447 *
53fad4b9
DM
448 * @param mixed $id int|array assessment id or array of assessments ids
449 * @return bool false if $id not a valid parameter, true otherwise
6e309973
DM
450 */
451 public function delete_assessment($id) {
452 global $DB;
453
454 // todo remove all given grades from workshop_grades;
6e309973 455
53fad4b9 456 if (is_array($id)) {
235b31c8 457 return $DB->delete_records_list('workshop_assessments', 'id', $id);
3d2924e9 458 } else {
235b31c8 459 return $DB->delete_records('workshop_assessments', array('id' => $id));
53fad4b9 460 }
53fad4b9 461 }
6e309973
DM
462
463 /**
464 * Returns instance of grading strategy class
53fad4b9 465 *
65ba104c 466 * @return stdClass Instance of a grading strategy
6e309973
DM
467 */
468 public function grading_strategy_instance() {
3d2924e9
DM
469 global $CFG; // because we require other libs here
470
3fd2b0e1 471 if (is_null($this->strategyinstance)) {
0dc47fb9 472 $strategylib = dirname(__FILE__) . '/grading/' . $this->strategy . '/strategy.php';
6e309973
DM
473 if (is_readable($strategylib)) {
474 require_once($strategylib);
475 } else {
b761e6d9 476 throw new coding_exception('the grading subplugin must contain library ' . $strategylib);
6e309973 477 }
0dc47fb9 478 $classname = 'workshop_' . $this->strategy . '_strategy';
3fd2b0e1
DM
479 $this->strategyinstance = new $classname($this);
480 if (!in_array('workshop_strategy', class_implements($this->strategyinstance))) {
b761e6d9 481 throw new coding_exception($classname . ' does not implement workshop_strategy interface');
6e309973
DM
482 }
483 }
3fd2b0e1 484 return $this->strategyinstance;
6e309973
DM
485 }
486
66c9894d
DM
487 /**
488 * Return list of available allocation methods
489 *
490 * @return array Array ['string' => 'string'] of localized allocation method names
491 */
492 public function installed_allocators() {
ed597c77 493 $installed = get_plugin_list('workshopallocation');
66c9894d 494 $forms = array();
ed597c77
DM
495 foreach ($installed as $allocation => $allocationpath) {
496 if (file_exists($allocationpath . '/allocator.php')) {
497 $forms[$allocation] = get_string('pluginname', 'workshopallocation_' . $allocation);
498 }
66c9894d
DM
499 }
500 // usability - make sure that manual allocation appears the first
501 if (isset($forms['manual'])) {
502 $m = array('manual' => $forms['manual']);
503 unset($forms['manual']);
504 $forms = array_merge($m, $forms);
505 }
506 return $forms;
507 }
0968b1a3 508
66c9894d
DM
509 /**
510 * Returns instance of submissions allocator
53fad4b9 511 *
65ba104c
DM
512 * @param stdClass $method The name of the allocation method, must be PARAM_ALPHA
513 * @return stdClass Instance of submissions allocator
66c9894d
DM
514 */
515 public function allocator_instance($method) {
3d2924e9
DM
516 global $CFG; // because we require other libs here
517
66c9894d
DM
518 $allocationlib = dirname(__FILE__) . '/allocation/' . $method . '/allocator.php';
519 if (is_readable($allocationlib)) {
520 require_once($allocationlib);
521 } else {
a39d7d87 522 throw new coding_exception('Unable to find allocator.php');
66c9894d
DM
523 }
524 $classname = 'workshop_' . $method . '_allocator';
525 return new $classname($this);
526 }
527
b8ead2e6 528 /**
454e8dd9 529 * @return moodle_url of this workshop's view page
b8ead2e6
DM
530 */
531 public function view_url() {
532 global $CFG;
533 return new moodle_url($CFG->wwwroot . '/mod/workshop/view.php', array('id' => $this->cm->id));
534 }
535
536 /**
454e8dd9 537 * @return moodle_url of the page for editing this workshop's grading form
b8ead2e6
DM
538 */
539 public function editform_url() {
540 global $CFG;
541 return new moodle_url($CFG->wwwroot . '/mod/workshop/editform.php', array('cmid' => $this->cm->id));
542 }
543
544 /**
454e8dd9 545 * @return moodle_url of the page for previewing this workshop's grading form
b8ead2e6
DM
546 */
547 public function previewform_url() {
548 global $CFG;
549 return new moodle_url($CFG->wwwroot . '/mod/workshop/assessment.php', array('preview' => $this->cm->id));
550 }
551
552 /**
553 * @param int $assessmentid The ID of assessment record
454e8dd9 554 * @return moodle_url of the assessment page
b8ead2e6 555 */
a39d7d87 556 public function assess_url($assessmentid) {
b8ead2e6 557 global $CFG;
454e8dd9 558 $assessmentid = clean_param($assessmentid, PARAM_INT);
a39d7d87 559 return new moodle_url($CFG->wwwroot . '/mod/workshop/assessment.php', array('asid' => $assessmentid));
b8ead2e6
DM
560 }
561
39861053 562 /**
454e8dd9 563 * @return moodle_url of the page to view own submission
39861053
DM
564 */
565 public function submission_url() {
566 global $CFG;
567 return new moodle_url($CFG->wwwroot . '/mod/workshop/submission.php', array('cmid' => $this->cm->id));
568 }
569
da0b1f70 570 /**
454e8dd9 571 * @return moodle_url of the mod_edit form
da0b1f70
DM
572 */
573 public function updatemod_url() {
574 global $CFG;
575 return new moodle_url($CFG->wwwroot . '/course/modedit.php', array('update' => $this->cm->id, 'return' => 1));
576 }
577
454e8dd9
DM
578 /**
579 * @return moodle_url to the allocation page
580 */
da0b1f70
DM
581 public function allocation_url() {
582 global $CFG;
583 return new moodle_url($CFG->wwwroot . '/mod/workshop/allocation.php', array('cmid' => $this->cm->id));
584 }
585
454e8dd9
DM
586 /**
587 * @param int $phasecode The internal phase code
588 * @return moodle_url of the script to change the current phase to $phasecode
589 */
590 public function switchphase_url($phasecode) {
591 global $CFG;
592 $phasecode = clean_param($phasecode, PARAM_INT);
593 return new moodle_url($CFG->wwwroot . '/mod/workshop/switchphase.php', array('cmid' => $this->cm->id, 'phase' => $phasecode));
594 }
595
b8ead2e6
DM
596 /**
597 * Returns an object containing all data to display the user's full name and picture
598 *
599 * @param int $id optional user id, defaults to the current user
65ba104c 600 * @return stdClass containing properties lastname, firstname, picture and imagealt
b8ead2e6
DM
601 */
602 public function user_info($id=null) {
603 global $USER, $DB;
604
605 if (is_null($id) || ($id == $USER->id)) {
606 return $USER;
607 } else {
608 return $DB->get_record('user', array('id' => $id), 'id,lastname,firstname,picture,imagealt', MUST_EXIST);
609 }
610 }
611
c1e883bb 612 /**
b13142da 613 * Are users allowed to create/edit their submissions?
c1e883bb
DM
614 *
615 * TODO: this depends on the workshop phase, phase deadlines, submitting after deadlines possibility
616 *
617 * @return bool
618 */
619 public function submitting_allowed() {
620 return true;
621 }
622
3dc78e5b
DM
623 /**
624 * Are the peer-reviews available to the authors?
625 *
626 * TODO: this depends on the workshop phase
627 *
628 * @return bool
629 */
630 public function assessments_available() {
631 return true;
632 }
633
634 /**
635 * Can the given grades be displayed to the authors?
636 *
637 * Grades are not displayed if {@link self::assessments_available()} return false. The returned
638 * value may be true (if yes, display grades), false (no, hide grades yet) or null (only
639 * display grades if the assessment has been agreed by the author).
640 *
641 * @return bool|null
642 */
643 public function grades_available() {
644 return true;
645 }
646
b13142da
DM
647 /**
648 * Returns the localized name of the grading strategy method to be displayed to the users
649 *
650 * @return string
651 */
652 public function strategy_name() {
653 return get_string('pluginname', 'workshopgrading_' . $this->strategy);
654 }
b761e6d9
DM
655
656 /**
657 * Prepare an individual workshop plan for the given user.
658 *
659 * @param mixed $userid
660 * @return TODO
661 */
662 public function prepare_user_plan($userid) {
663 global $DB;
664
665 $phases = array();
666
667 // Prepare tasks for the setup phase
668 $phase = new stdClass();
669 $phase->title = get_string('phasesetup', 'workshop');
670 $phase->tasks = array();
da0b1f70
DM
671 if (has_capability('moodle/course:manageactivities', $this->context, $userid)) {
672 $task = new stdClass();
673 $task->title = get_string('taskintro', 'workshop');
674 $task->link = $this->updatemod_url();
675 $task->completed = !(trim(strip_tags($this->intro)) == '');
676 $phase->tasks['intro'] = $task;
677 }
454e8dd9
DM
678 if (has_capability('moodle/course:manageactivities', $this->context, $userid)) {
679 $task = new stdClass();
680 $task->title = get_string('taskinstructauthors', 'workshop');
681 $task->link = $this->updatemod_url();
682 $task->completed = !(trim(strip_tags($this->instructauthors)) == '');
683 $phase->tasks['instructauthors'] = $task;
684 }
b761e6d9
DM
685 if (has_capability('mod/workshop:editdimensions', $this->context, $userid)) {
686 $task = new stdClass();
da0b1f70
DM
687 $task->title = get_string('editassessmentform', 'workshop');
688 $task->link = $this->editform_url();
689 if ($this->assessment_form_ready()) {
690 $task->completed = true;
691 } elseif ($this->phase > self::PHASE_SETUP) {
692 $task->completed = false;
693 }
b761e6d9
DM
694 $phase->tasks['editform'] = $task;
695 }
da0b1f70
DM
696 if (empty($phase->tasks) and $this->phase == self::PHASE_SETUP) {
697 // if we are in the setup phase and there is no task (typical for students), let us
698 // display some explanation what is going on
699 $task = new stdClass();
700 $task->title = get_string('undersetup', 'workshop');
701 $task->completed = 'info';
702 $phase->tasks['setupinfo'] = $task;
703 }
b761e6d9
DM
704 $phases[self::PHASE_SETUP] = $phase;
705
706 // Prepare tasks for the submission phase
707 $phase = new stdClass();
708 $phase->title = get_string('phasesubmission', 'workshop');
709 $phase->tasks = array();
710 if (has_capability('mod/workshop:submit', $this->context, $userid)) {
711 $task = new stdClass();
712 $task->title = get_string('tasksubmit', 'workshop');
da0b1f70
DM
713 $task->link = $this->submission_url();
714 if ($DB->record_exists('workshop_submissions', array('workshopid'=>$this->id, 'example'=>0, 'userid'=>$userid))) {
715 $task->completed = true;
716 } elseif ($this->phase >= self::PHASE_ASSESSMENT) {
717 $task->completed = false;
718 } else {
719 $task->completed = null; // still has a chance to submit
720 }
b761e6d9
DM
721 $phase->tasks['submit'] = $task;
722 }
da0b1f70
DM
723 if (has_capability('moodle/course:manageactivities', $this->context, $userid)) {
724 $task = new stdClass();
725 $task->title = get_string('taskinstructreviewers', 'workshop');
726 $task->link = $this->updatemod_url();
727 if (trim(strip_tags($this->instructreviewers))) {
728 $task->completed = true;
729 } elseif ($this->phase >= self::PHASE_ASSESSMENT) {
730 $task->completed = false;
731 }
732 $phase->tasks['instructreviewers'] = $task;
733 }
b761e6d9 734 $phases[self::PHASE_SUBMISSION] = $phase;
da0b1f70
DM
735 if (has_capability('mod/workshop:allocate', $this->context, $userid)) {
736 $task = new stdClass();
737 $task->title = get_string('allocate', 'workshop');
738 $task->link = $this->allocation_url();
739 $rs = $this->get_allocations_recordset();
3189fb2d 740 $authors = array();
da0b1f70
DM
741 $allocations = array(); // 'submissionid' => isallocated
742 foreach ($rs as $allocation) {
3189fb2d
DM
743 if (!isset($authors[$allocation->authorid])) {
744 $authors[$allocation->authorid] = true;
da0b1f70 745 }
3189fb2d
DM
746 if (isset($allocation->submissionid)) {
747 if (!isset($allocations[$allocation->submissionid])) {
748 $allocations[$allocation->submissionid] = false;
749 }
750 if (!empty($allocation->reviewerid)) {
751 $allocations[$allocation->submissionid] = true;
752 }
da0b1f70
DM
753 }
754 }
3189fb2d 755 $numofauthors = count($authors);
da0b1f70
DM
756 $numofsubmissions = count($allocations);
757 $numofallocated = count(array_filter($allocations));
758 $rs->close();
759 if ($numofsubmissions == 0) {
760 $task->completed = null;
3dc78e5b 761 } elseif ($numofsubmissions == $numofallocated) {
da0b1f70
DM
762 $task->completed = true;
763 } elseif ($this->phase > self::PHASE_SUBMISSION) {
764 $task->completed = false;
765 } else {
766 $task->completed = null; // still has a chance to allocate
767 }
768 $a = new stdClass();
3189fb2d
DM
769 $a->expected = $numofauthors;
770 $a->submitted = $numofsubmissions;
771 $a->allocated = $numofallocated;
772 $task->details = get_string('allocatedetails', 'workshop', $a);
da0b1f70 773 unset($a);
3189fb2d 774 $phase->tasks['allocate'] = $task;
3dc78e5b
DM
775
776 if ($numofsubmissions < $numofauthors and $this->phase >= self::PHASE_SUBMISSION) {
777 $task = new stdClass();
778 $task->title = get_string('someuserswosubmission', 'workshop');
779 $task->completed = 'info';
780 $phase->tasks['allocateinfo'] = $task;
781 }
da0b1f70 782 }
b761e6d9
DM
783
784 // Prepare tasks for the peer-assessment phase (includes eventual self-assessments)
785 $phase = new stdClass();
786 $phase->title = get_string('phaseassessment', 'workshop');
787 $phase->tasks = array();
788 $phase->isreviewer = has_capability('mod/workshop:peerassess', $this->context, $userid);
3dc78e5b 789 $phase->assessments = $this->get_assessments_by_reviewer($userid);
b761e6d9
DM
790 $numofpeers = 0; // number of allocated peer-assessments
791 $numofpeerstodo = 0; // number of peer-assessments to do
792 $numofself = 0; // number of allocated self-assessments - should be 0 or 1
793 $numofselftodo = 0; // number of self-assessments to do - should be 0 or 1
794 foreach ($phase->assessments as $a) {
795 if ($a->authorid == $userid) {
796 $numofself++;
797 if (is_null($a->grade)) {
798 $numofselftodo++;
799 }
800 } else {
801 $numofpeers++;
802 if (is_null($a->grade)) {
803 $numofpeerstodo++;
804 }
805 }
806 }
807 unset($a);
808 if ($numofpeers) {
809 $task = new stdClass();
3dc78e5b
DM
810 if ($numofpeerstodo == 0) {
811 $task->completed = true;
812 } elseif ($this->phase > self::PHASE_ASSESSMENT) {
813 $task->completed = false;
814 }
b761e6d9
DM
815 $a = new stdClass();
816 $a->total = $numofpeers;
817 $a->todo = $numofpeerstodo;
818 $task->title = get_string('taskassesspeers', 'workshop');
da0b1f70 819 $task->details = get_string('taskassesspeersdetails', 'workshop', $a);
b761e6d9
DM
820 unset($a);
821 $phase->tasks['assesspeers'] = $task;
822 }
823 if ($numofself) {
824 $task = new stdClass();
3dc78e5b
DM
825 if ($numofselftodo == 0) {
826 $task->completed = true;
827 } elseif ($this->phase > self::PHASE_ASSESSMENT) {
828 $task->completed = false;
829 }
b761e6d9
DM
830 $task->title = get_string('taskassessself', 'workshop');
831 $phase->tasks['assessself'] = $task;
832 }
833 $phases[self::PHASE_ASSESSMENT] = $phase;
834
835 // Prepare tasks for the grading evaluation phase - todo
836 $phase = new stdClass();
837 $phase->title = get_string('phaseevaluation', 'workshop');
838 $phase->tasks = array();
839 $phases[self::PHASE_EVALUATION] = $phase;
840
841 // Prepare tasks for the "workshop closed" phase - todo
842 $phase = new stdClass();
843 $phase->title = get_string('phaseclosed', 'workshop');
844 $phase->tasks = array();
845 $phases[self::PHASE_CLOSED] = $phase;
846
847 // Polish data, set default values if not done explicitly
848 foreach ($phases as $phasecode => $phase) {
849 $phase->title = isset($phase->title) ? $phase->title : '';
850 $phase->tasks = isset($phase->tasks) ? $phase->tasks : array();
851 if ($phasecode == $this->phase) {
852 $phase->active = true;
853 } else {
854 $phase->active = false;
855 }
454e8dd9
DM
856 if (!isset($phase->actions)) {
857 $phase->actions = array();
858 }
b761e6d9
DM
859
860 foreach ($phase->tasks as $taskcode => $task) {
861 $task->title = isset($task->title) ? $task->title : '';
da0b1f70
DM
862 $task->link = isset($task->link) ? $task->link : null;
863 $task->details = isset($task->details) ? $task->details : '';
b761e6d9
DM
864 $task->completed = isset($task->completed) ? $task->completed : null;
865 }
866 }
454e8dd9
DM
867
868 // Add phase swithing actions
869 if (has_capability('mod/workshop:switchphase', $this->context, $userid)) {
870 foreach ($phases as $phasecode => $phase) {
871 if (! $phase->active) {
872 $action = new stdClass();
873 $action->type = 'switchphase';
874 $action->url = $this->switchphase_url($phasecode);
875 $phase->actions[] = $action;
876 }
877 }
878 }
879
b761e6d9
DM
880 return $phases;
881 }
882
883 /**
884 * Has the assessment form been defined?
885 *
886 * @return bool
887 */
888 public function assessment_form_ready() {
889 return $this->grading_strategy_instance()->form_ready();
890 }
891
454e8dd9
DM
892 /**
893 * @return array of available workshop phases
894 */
895 protected function available_phases() {
896 return array(
897 self::PHASE_SETUP => true,
898 self::PHASE_SUBMISSION => true,
899 self::PHASE_ASSESSMENT => true,
900 self::PHASE_EVALUATION => true,
901 self::PHASE_CLOSED => true,
902 );
903 }
904
905 /**
906 * Switch to a new workshop phase
907 *
908 * Modifies the underlying database record. You should terminate the script shortly after calling this.
909 *
910 * @param int $newphase new phase code
911 * @return bool true if success, false otherwise
912 */
913 public function switch_phase($newphase) {
914 global $DB;
915
916 $known = $this->available_phases();
917 if (!isset($known[$newphase])) {
918 return false;
919 }
920 $DB->set_field('workshop', 'phase', $newphase, array('id' => $this->id));
921 return true;
922 }
ddb59c77
DM
923
924 /**
925 * Saves a raw grade for submission as calculated from the assessment form fields
926 *
927 * @param array $assessmentid assessment record id, must exists
928 * @param mixed $grade raw percentual grade from 0 to 1
929 * @return false|float the saved grade
930 */
931 public function set_peer_grade($assessmentid, $grade) {
932 global $DB;
933
934 if (is_null($grade)) {
935 return false;
936 }
937 $data = new stdClass();
938 $data->id = $assessmentid;
939 $data->grade = $grade;
940 $DB->update_record('workshop_assessments', $data);
941 return $grade;
942 }
6516b9e9
DM
943
944// Static methods
945
946 /**
947 * Returns an array of options for the editors that are used for submitting and assessing instructions
948 *
949 * @param stdClass $context
950 * @return array
951 */
952 public static function instruction_editors_options(stdClass $context) {
953 return array('subdirs' => 1, 'maxbytes' => 0, 'maxfiles' => EDITOR_UNLIMITED_FILES,
954 'changeformat' => 1, 'context' => $context, 'noclean' => 1, 'trusttext' => 0);
955 }
956
66c9894d 957}