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