Improved context handling
[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
29dc43e7 34require_once($CFG->libdir . '/gradelib.php');
0968b1a3 35
6e309973
DM
36/**
37 * Full-featured workshop API
38 *
a39d7d87 39 * This wraps the workshop database record with a set of methods that are called
6e309973 40 * from the module itself. The class should be initialized right after you get
a39d7d87 41 * $workshop, $cm and $course records at the begining of the script.
6e309973 42 */
a39d7d87
DM
43class workshop {
44
b761e6d9
DM
45 /** return statuses of {@link add_allocation} to be passed to a workshop renderer method */
46 const ALLOCATION_EXISTS = -1;
47 const ALLOCATION_ERROR = -2;
48
49 /** the internal code of the workshop phases as are stored in the database */
50 const PHASE_SETUP = 10;
51 const PHASE_SUBMISSION = 20;
52 const PHASE_ASSESSMENT = 30;
53 const PHASE_EVALUATION = 40;
54 const PHASE_CLOSED = 50;
55
65ba104c 56 /** @var stdClass course module record */
a39d7d87
DM
57 public $cm = null;
58
65ba104c 59 /** @var stdClass course record */
a39d7d87 60 public $course = null;
6e309973 61
b761e6d9
DM
62 /**
63 * @var workshop_strategy grading strategy instance
64 * Do not use directly, get the instance using {@link workshop::grading_strategy_instance()}
65 */
b13142da
DM
66 protected $strategyinstance = null;
67
45d24d39
DM
68 /**
69 * @var workshop_evaluation grading evaluation instance
70 * Do not use directly, get the instance using {@link workshop::grading_evaluation_instance()}
71 */
72 protected $evaluationinstance = null;
73
6e309973 74 /**
65ba104c 75 * Initializes the workshop API instance using the data from DB
a39d7d87
DM
76 *
77 * Makes deep copy of all passed records properties. Replaces integer $course attribute
78 * with a full database record (course should not be stored in instances table anyway).
6e309973 79 *
b13142da 80 * @param stdClass $dbrecord Workshop instance data from {workshop} table
06d73dd5
DM
81 * @param stdClass $cm Course module record as returned by {@link get_coursemodule_from_id()}
82 * @param stdClass $course Course record from {course} table
0dc47fb9 83 */
b13142da 84 public function __construct(stdClass $dbrecord, stdClass $cm, stdClass $course) {
f05c168d
DM
85 foreach ($dbrecord as $field => $value) {
86 $this->{$field} = $value;
a39d7d87 87 }
45d24d39
DM
88 $this->evaluation = 'best'; // todo make this configurable
89 $this->cm = $cm;
90 $this->course = $course; // beware - this replaces the standard course field in the instance table
91 // this is intentional - IMO there should be no such field as it violates
d895c6aa
DM
92 // 3rd normal form with no real performance gain. This way I try to
93 // demonstrate how backwards compatibility could be achieved --mudrd8mz
94 $this->context = get_context_instance(CONTEXT_MODULE, $this->id);
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
61b737a5
DM
134 /**
135 * Given the percent and the total, returns the number
136 *
137 * @param float $percent from 0 to 100
138 * @param float $total the 100% value
139 * @return float
140 */
141 public static function percent_to_value($percent, $total) {
142 if ($percent < 0 or $percent > 100) {
143 throw new coding_exception('The percent can not be less than 0 or higher than 100');
144 }
145
146 return $total * $percent / 100;
147 }
148
aa40adbf
DM
149 ////////////////////////////////////////////////////////////////////////////////
150 // Workshop API //
151 ////////////////////////////////////////////////////////////////////////////////
152
6e309973
DM
153 /**
154 * Fetches all users with the capability mod/workshop:submit in the current context
155 *
3d2924e9 156 * The returned objects contain id, lastname and firstname properties and are ordered by lastname,firstname
53fad4b9 157 *
aa40adbf 158 * @todo handle with limits and groups
53fad4b9 159 * @param bool $musthavesubmission If true, return only users who have already submitted. All possible authors otherwise.
65ba104c 160 * @return array array[userid] => stdClass{->id ->lastname ->firstname}
6e309973 161 */
d895c6aa
DM
162 public function get_potential_authors($musthavesubmission=true) {
163 $users = get_users_by_capability($this->context, 'mod/workshop:submit',
aa40adbf 164 'u.id,u.lastname,u.firstname', 'u.lastname,u.firstname,u.id', 0, 1000, '', '', false, false, true);
3d2924e9 165 if ($musthavesubmission) {
da0b1f70 166 $users = array_intersect_key($users, $this->users_with_submission(array_keys($users)));
66c9894d 167 }
da0b1f70 168 return $users;
6e309973
DM
169 }
170
6e309973
DM
171 /**
172 * Fetches all users with the capability mod/workshop:peerassess in the current context
173 *
b13142da 174 * The returned objects contain id, lastname and firstname properties and are ordered by lastname,firstname
53fad4b9 175 *
aa40adbf 176 * @todo handle with limits and groups
53fad4b9 177 * @param bool $musthavesubmission If true, return only users who have already submitted. All possible users otherwise.
65ba104c 178 * @return array array[userid] => stdClass{->id ->lastname ->firstname}
6e309973 179 */
d895c6aa
DM
180 public function get_potential_reviewers($musthavesubmission=false) {
181 $users = get_users_by_capability($this->context, 'mod/workshop:peerassess',
aa40adbf 182 'u.id, u.lastname, u.firstname', 'u.lastname,u.firstname,u.id', 0, 1000, '', '', false, false, true);
3d2924e9
DM
183 if ($musthavesubmission) {
184 // users without their own submission can not be reviewers
da0b1f70 185 $users = array_intersect_key($users, $this->users_with_submission(array_keys($users)));
0968b1a3 186 }
da0b1f70 187 return $users;
0968b1a3
DM
188 }
189
b8ead2e6
DM
190 /**
191 * Groups the given users by the group membership
192 *
193 * This takes the module grouping settings into account. If "Available for group members only"
194 * is set, returns only groups withing the course module grouping. Always returns group [0] with
195 * all the given users.
196 *
65ba104c
DM
197 * @param array $users array[userid] => stdClass{->id ->lastname ->firstname}
198 * @return array array[groupid][userid] => stdClass{->id ->lastname ->firstname}
53fad4b9 199 */
3d2924e9 200 public function get_grouped($users) {
53fad4b9 201 global $DB;
3d2924e9 202 global $CFG;
53fad4b9 203
b8ead2e6
DM
204 $grouped = array(); // grouped users to be returned
205 if (empty($users)) {
206 return $grouped;
a7c5b918 207 }
3d2924e9 208 if (!empty($CFG->enablegroupings) and $this->cm->groupmembersonly) {
53fad4b9
DM
209 // Available for group members only - the workshop is available only
210 // to users assigned to groups within the selected grouping, or to
211 // any group if no grouping is selected.
212 $groupingid = $this->cm->groupingid;
b8ead2e6 213 // All users that are members of at least one group will be
53fad4b9 214 // added into a virtual group id 0
b8ead2e6 215 $grouped[0] = array();
53fad4b9
DM
216 } else {
217 $groupingid = 0;
b8ead2e6
DM
218 // there is no need to be member of a group so $grouped[0] will contain
219 // all users
220 $grouped[0] = $users;
53fad4b9 221 }
b8ead2e6 222 $gmemberships = groups_get_all_groups($this->cm->course, array_keys($users), $groupingid,
53fad4b9
DM
223 'gm.id,gm.groupid,gm.userid');
224 foreach ($gmemberships as $gmembership) {
b8ead2e6
DM
225 if (!isset($grouped[$gmembership->groupid])) {
226 $grouped[$gmembership->groupid] = array();
53fad4b9 227 }
b8ead2e6
DM
228 $grouped[$gmembership->groupid][$gmembership->userid] = $users[$gmembership->userid];
229 $grouped[0][$gmembership->userid] = $users[$gmembership->userid];
53fad4b9 230 }
b8ead2e6 231 return $grouped;
53fad4b9 232 }
6e309973 233
aa40adbf
DM
234 /**
235 * Returns the list of all allocations (it est assigned assessments) in the workshop
236 *
237 * Assessments of example submissions are ignored
238 *
239 * @return array
240 */
241 public function get_allocations() {
242 global $DB;
243
00aca3c1 244 $sql = 'SELECT a.id, a.submissionid, a.reviewerid, s.authorid
aa40adbf
DM
245 FROM {workshop_assessments} a
246 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
247 WHERE s.example = 0 AND s.workshopid = :workshopid';
248 $params = array('workshopid' => $this->id);
249
250 return $DB->get_records_sql($sql, $params);
251 }
252
6e309973
DM
253 /**
254 * Returns submissions from this workshop
255 *
3dc78e5b
DM
256 * Fetches data from {workshop_submissions} and adds some useful information from other
257 * tables. Does not return textual fields to prevent possible memory lack issues.
53fad4b9 258 *
00aca3c1 259 * @param mixed $authorid int|array|'all' If set to [array of] integer, return submission[s] of the given user[s] only
934329e5 260 * @return array of records or an empty array
6e309973 261 */
29dc43e7 262 public function get_submissions($authorid='all') {
6e309973
DM
263 global $DB;
264
00aca3c1
DM
265 $sql = 'SELECT s.id, s.workshopid, s.example, s.authorid, s.timecreated, s.timemodified,
266 s.title, s.grade, s.gradeover, s.gradeoverby,
267 u.lastname AS authorlastname, u.firstname AS authorfirstname,
268 u.picture AS authorpicture, u.imagealt AS authorimagealt,
269 t.lastname AS overlastname, t.firstname AS overfirstname,
270 t.picture AS overpicture, t.imagealt AS overimagealt
3d2924e9 271 FROM {workshop_submissions} s
00aca3c1 272 INNER JOIN {user} u ON (s.authorid = u.id)
29dc43e7
DM
273 LEFT JOIN {user} t ON (s.gradeoverby = t.id)
274 WHERE s.example = 0 AND s.workshopid = :workshopid';
3d2924e9 275 $params = array('workshopid' => $this->id);
6e309973 276
00aca3c1 277 if ('all' === $authorid) {
3d2924e9 278 // no additional conditions
934329e5 279 } elseif (!empty($authorid)) {
00aca3c1
DM
280 list($usql, $uparams) = $DB->get_in_or_equal($authorid, SQL_PARAMS_NAMED);
281 $sql .= " AND authorid $usql";
6e309973 282 $params = array_merge($params, $uparams);
3d2924e9 283 } else {
934329e5
DM
284 // $authorid is empty
285 return array();
6e309973 286 }
3dc78e5b 287 $sql .= ' ORDER BY u.lastname, u.firstname';
6e309973 288
3dc78e5b 289 return $DB->get_records_sql($sql, $params);
6e309973
DM
290 }
291
51508f25
DM
292 /**
293 * Returns a submission record with the author's data
294 *
295 * @param int $id submission id
296 * @return stdClass
297 */
298 public function get_submission_by_id($id) {
299 global $DB;
300
29dc43e7
DM
301 // we intentionally check the workshopid here, too, so the workshop can't touch submissions
302 // from other instances
51508f25
DM
303 $sql = 'SELECT s.*,
304 u.lastname AS authorlastname, u.firstname AS authorfirstname, u.id AS authorid,
305 u.picture AS authorpicture, u.imagealt AS authorimagealt
306 FROM {workshop_submissions} s
00aca3c1 307 INNER JOIN {user} u ON (s.authorid = u.id)
51508f25
DM
308 WHERE s.workshopid = :workshopid AND s.id = :id';
309 $params = array('workshopid' => $this->id, 'id' => $id);
310 return $DB->get_record_sql($sql, $params, MUST_EXIST);
311 }
312
53fad4b9 313 /**
3dc78e5b 314 * Returns a submission submitted by the given author
53fad4b9 315 *
3dc78e5b
DM
316 * @param int $id author id
317 * @return stdClass|false
53fad4b9 318 */
00aca3c1 319 public function get_submission_by_author($authorid) {
e9b0f0ab
DM
320 global $DB;
321
00aca3c1 322 if (empty($authorid)) {
53fad4b9
DM
323 return false;
324 }
3dc78e5b
DM
325 $sql = 'SELECT s.*,
326 u.lastname AS authorlastname, u.firstname AS authorfirstname, u.id AS authorid,
327 u.picture AS authorpicture, u.imagealt AS authorimagealt
328 FROM {workshop_submissions} s
00aca3c1
DM
329 INNER JOIN {user} u ON (s.authorid = u.id)
330 WHERE s.example = 0 AND s.workshopid = :workshopid AND s.authorid = :authorid';
331 $params = array('workshopid' => $this->id, 'authorid' => $authorid);
3dc78e5b 332 return $DB->get_record_sql($sql, $params);
53fad4b9 333 }
6e309973
DM
334
335 /**
3dc78e5b 336 * Returns the list of all assessments in the workshop with some data added
6e309973
DM
337 *
338 * Fetches data from {workshop_assessments} and adds some useful information from other
3dc78e5b
DM
339 * tables. The returned object does not contain textual fields (ie comments) to prevent memory
340 * lack issues.
341 *
342 * @return array [assessmentid] => assessment stdClass
6e309973 343 */
3dc78e5b 344 public function get_all_assessments() {
6e309973 345 global $DB;
53fad4b9 346
00aca3c1 347 $sql = 'SELECT a.id, a.submissionid, a.reviewerid, a.timecreated, a.timemodified, a.timeagreed,
3dc78e5b 348 a.grade, a.gradinggrade, a.gradinggradeover, a.gradinggradeoverby,
3d2924e9
DM
349 reviewer.id AS reviewerid,reviewer.firstname AS reviewerfirstname,reviewer.lastname as reviewerlastname,
350 s.title,
ddb59c77 351 author.id AS authorid, author.firstname AS authorfirstname,author.lastname AS authorlastname
3d2924e9 352 FROM {workshop_assessments} a
00aca3c1 353 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id)
3d2924e9 354 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
00aca3c1 355 INNER JOIN {user} author ON (s.authorid = author.id)
3dc78e5b
DM
356 WHERE s.workshopid = :workshopid AND s.example = 0
357 ORDER BY reviewer.lastname, reviewer.firstname';
3d2924e9
DM
358 $params = array('workshopid' => $this->id);
359
3dc78e5b 360 return $DB->get_records_sql($sql, $params);
53fad4b9
DM
361 }
362
363 /**
3dc78e5b 364 * Get the complete information about the given assessment
53fad4b9
DM
365 *
366 * @param int $id Assessment ID
65ba104c 367 * @return mixed false if not found, stdClass otherwise
53fad4b9
DM
368 */
369 public function get_assessment_by_id($id) {
3dc78e5b
DM
370 global $DB;
371
372 $sql = 'SELECT a.*,
373 reviewer.id AS reviewerid,reviewer.firstname AS reviewerfirstname,reviewer.lastname as reviewerlastname,
374 s.title,
375 author.id AS authorid, author.firstname AS authorfirstname,author.lastname as authorlastname
376 FROM {workshop_assessments} a
00aca3c1 377 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id)
3dc78e5b 378 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
00aca3c1 379 INNER JOIN {user} author ON (s.authorid = author.id)
3dc78e5b
DM
380 WHERE a.id = :id AND s.workshopid = :workshopid';
381 $params = array('id' => $id, 'workshopid' => $this->id);
382
383 return $DB->get_record_sql($sql, $params, MUST_EXIST);
53fad4b9
DM
384 }
385
386 /**
3dc78e5b 387 * Get the complete information about all assessments allocated to the given reviewer
53fad4b9 388 *
00aca3c1 389 * @param int $reviewerid
3dc78e5b 390 * @return array
53fad4b9 391 */
00aca3c1 392 public function get_assessments_by_reviewer($reviewerid) {
3dc78e5b
DM
393 global $DB;
394
395 $sql = 'SELECT a.*,
ddb59c77
DM
396 reviewer.id AS reviewerid,reviewer.firstname AS reviewerfirstname,reviewer.lastname AS reviewerlastname,
397 s.id AS submissionid, s.title AS submissiontitle, s.timecreated AS submissioncreated,
398 s.timemodified AS submissionmodified,
399 author.id AS authorid, author.firstname AS authorfirstname,author.lastname AS authorlastname,
400 author.picture AS authorpicture, author.imagealt AS authorimagealt
3dc78e5b 401 FROM {workshop_assessments} a
00aca3c1 402 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id)
3dc78e5b 403 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
00aca3c1
DM
404 INNER JOIN {user} author ON (s.authorid = author.id)
405 WHERE s.example = 0 AND reviewer.id = :reviewerid AND s.workshopid = :workshopid';
406 $params = array('reviewerid' => $reviewerid, 'workshopid' => $this->id);
3dc78e5b
DM
407
408 return $DB->get_records_sql($sql, $params);
53fad4b9 409 }
6e309973 410
6e309973
DM
411 /**
412 * Allocate a submission to a user for review
53fad4b9 413 *
65ba104c 414 * @param stdClass $submission Submission record
6e309973 415 * @param int $reviewerid User ID
53fad4b9 416 * @param bool $bulk repeated inserts into DB expected
6e309973
DM
417 * @return int ID of the new assessment or an error code
418 */
65ba104c 419 public function add_allocation(stdClass $submission, $reviewerid, $bulk=false) {
6e309973
DM
420 global $DB;
421
00aca3c1 422 if ($DB->record_exists('workshop_assessments', array('submissionid' => $submission->id, 'reviewerid' => $reviewerid))) {
b761e6d9 423 return self::ALLOCATION_EXISTS;
6e309973
DM
424 }
425
6e309973 426 $now = time();
65ba104c 427 $assessment = new stdClass();
53fad4b9 428 $assessment->submissionid = $submission->id;
00aca3c1 429 $assessment->reviewerid = $reviewerid;
6e309973
DM
430 $assessment->timecreated = $now;
431 $assessment->timemodified = $now;
a3610b08 432 $assessment->weight = 1; // todo better handling of the weight value/default
6e309973 433
235b31c8 434 return $DB->insert_record('workshop_assessments', $assessment, true, $bulk);
6e309973
DM
435 }
436
6e309973 437 /**
53fad4b9 438 * Delete assessment record or records
6e309973 439 *
53fad4b9
DM
440 * @param mixed $id int|array assessment id or array of assessments ids
441 * @return bool false if $id not a valid parameter, true otherwise
6e309973
DM
442 */
443 public function delete_assessment($id) {
444 global $DB;
445
446 // todo remove all given grades from workshop_grades;
6e309973 447
53fad4b9 448 if (is_array($id)) {
235b31c8 449 return $DB->delete_records_list('workshop_assessments', 'id', $id);
3d2924e9 450 } else {
235b31c8 451 return $DB->delete_records('workshop_assessments', array('id' => $id));
53fad4b9 452 }
53fad4b9 453 }
6e309973
DM
454
455 /**
456 * Returns instance of grading strategy class
53fad4b9 457 *
65ba104c 458 * @return stdClass Instance of a grading strategy
6e309973
DM
459 */
460 public function grading_strategy_instance() {
3d2924e9
DM
461 global $CFG; // because we require other libs here
462
3fd2b0e1 463 if (is_null($this->strategyinstance)) {
f05c168d 464 $strategylib = dirname(__FILE__) . '/form/' . $this->strategy . '/lib.php';
6e309973
DM
465 if (is_readable($strategylib)) {
466 require_once($strategylib);
467 } else {
f05c168d 468 throw new coding_exception('the grading forms subplugin must contain library ' . $strategylib);
6e309973 469 }
0dc47fb9 470 $classname = 'workshop_' . $this->strategy . '_strategy';
3fd2b0e1
DM
471 $this->strategyinstance = new $classname($this);
472 if (!in_array('workshop_strategy', class_implements($this->strategyinstance))) {
b761e6d9 473 throw new coding_exception($classname . ' does not implement workshop_strategy interface');
6e309973
DM
474 }
475 }
3fd2b0e1 476 return $this->strategyinstance;
6e309973
DM
477 }
478
45d24d39
DM
479 /**
480 * Returns instance of grading evaluation class
481 *
482 * @return stdClass Instance of a grading evaluation
483 */
484 public function grading_evaluation_instance() {
485 global $CFG; // because we require other libs here
486
487 if (is_null($this->evaluationinstance)) {
488 $evaluationlib = dirname(__FILE__) . '/eval/' . $this->evaluation . '/lib.php';
489 if (is_readable($evaluationlib)) {
490 require_once($evaluationlib);
491 } else {
492 throw new coding_exception('the grading evaluation subplugin must contain library ' . $evaluationlib);
493 }
494 $classname = 'workshop_' . $this->evaluation . '_evaluation';
495 $this->evaluationinstance = new $classname($this);
496 if (!in_array('workshop_evaluation', class_implements($this->evaluationinstance))) {
497 throw new coding_exception($classname . ' does not implement workshop_evaluation interface');
498 }
499 }
500 return $this->evaluationinstance;
501 }
502
66c9894d
DM
503 /**
504 * Returns instance of submissions allocator
53fad4b9 505 *
65ba104c
DM
506 * @param stdClass $method The name of the allocation method, must be PARAM_ALPHA
507 * @return stdClass Instance of submissions allocator
66c9894d
DM
508 */
509 public function allocator_instance($method) {
3d2924e9
DM
510 global $CFG; // because we require other libs here
511
f05c168d 512 $allocationlib = dirname(__FILE__) . '/allocation/' . $method . '/lib.php';
66c9894d
DM
513 if (is_readable($allocationlib)) {
514 require_once($allocationlib);
515 } else {
f05c168d 516 throw new coding_exception('Unable to find the allocation library ' . $allocationlib);
66c9894d
DM
517 }
518 $classname = 'workshop_' . $method . '_allocator';
519 return new $classname($this);
520 }
521
b8ead2e6 522 /**
454e8dd9 523 * @return moodle_url of this workshop's view page
b8ead2e6
DM
524 */
525 public function view_url() {
526 global $CFG;
527 return new moodle_url($CFG->wwwroot . '/mod/workshop/view.php', array('id' => $this->cm->id));
528 }
529
530 /**
454e8dd9 531 * @return moodle_url of the page for editing this workshop's grading form
b8ead2e6
DM
532 */
533 public function editform_url() {
534 global $CFG;
535 return new moodle_url($CFG->wwwroot . '/mod/workshop/editform.php', array('cmid' => $this->cm->id));
536 }
537
538 /**
454e8dd9 539 * @return moodle_url of the page for previewing this workshop's grading form
b8ead2e6
DM
540 */
541 public function previewform_url() {
542 global $CFG;
543 return new moodle_url($CFG->wwwroot . '/mod/workshop/assessment.php', array('preview' => $this->cm->id));
544 }
545
546 /**
547 * @param int $assessmentid The ID of assessment record
454e8dd9 548 * @return moodle_url of the assessment page
b8ead2e6 549 */
a39d7d87 550 public function assess_url($assessmentid) {
b8ead2e6 551 global $CFG;
454e8dd9 552 $assessmentid = clean_param($assessmentid, PARAM_INT);
a39d7d87 553 return new moodle_url($CFG->wwwroot . '/mod/workshop/assessment.php', array('asid' => $assessmentid));
b8ead2e6
DM
554 }
555
39861053 556 /**
454e8dd9 557 * @return moodle_url of the page to view own submission
39861053
DM
558 */
559 public function submission_url() {
560 global $CFG;
561 return new moodle_url($CFG->wwwroot . '/mod/workshop/submission.php', array('cmid' => $this->cm->id));
562 }
563
da0b1f70 564 /**
454e8dd9 565 * @return moodle_url of the mod_edit form
da0b1f70
DM
566 */
567 public function updatemod_url() {
568 global $CFG;
569 return new moodle_url($CFG->wwwroot . '/course/modedit.php', array('update' => $this->cm->id, 'return' => 1));
570 }
571
454e8dd9
DM
572 /**
573 * @return moodle_url to the allocation page
574 */
da0b1f70
DM
575 public function allocation_url() {
576 global $CFG;
577 return new moodle_url($CFG->wwwroot . '/mod/workshop/allocation.php', array('cmid' => $this->cm->id));
578 }
579
454e8dd9
DM
580 /**
581 * @param int $phasecode The internal phase code
582 * @return moodle_url of the script to change the current phase to $phasecode
583 */
584 public function switchphase_url($phasecode) {
585 global $CFG;
586 $phasecode = clean_param($phasecode, PARAM_INT);
587 return new moodle_url($CFG->wwwroot . '/mod/workshop/switchphase.php', array('cmid' => $this->cm->id, 'phase' => $phasecode));
588 }
589
b8ead2e6
DM
590 /**
591 * Returns an object containing all data to display the user's full name and picture
592 *
593 * @param int $id optional user id, defaults to the current user
65ba104c 594 * @return stdClass containing properties lastname, firstname, picture and imagealt
b8ead2e6
DM
595 */
596 public function user_info($id=null) {
597 global $USER, $DB;
598
599 if (is_null($id) || ($id == $USER->id)) {
600 return $USER;
601 } else {
602 return $DB->get_record('user', array('id' => $id), 'id,lastname,firstname,picture,imagealt', MUST_EXIST);
603 }
604 }
605
c1e883bb 606 /**
b13142da 607 * Are users allowed to create/edit their submissions?
c1e883bb
DM
608 *
609 * TODO: this depends on the workshop phase, phase deadlines, submitting after deadlines possibility
610 *
611 * @return bool
612 */
613 public function submitting_allowed() {
614 return true;
615 }
616
3dc78e5b
DM
617 /**
618 * Are the peer-reviews available to the authors?
619 *
620 * TODO: this depends on the workshop phase
621 *
622 * @return bool
623 */
624 public function assessments_available() {
625 return true;
626 }
627
628 /**
629 * Can the given grades be displayed to the authors?
630 *
631 * Grades are not displayed if {@link self::assessments_available()} return false. The returned
632 * value may be true (if yes, display grades), false (no, hide grades yet) or null (only
633 * display grades if the assessment has been agreed by the author).
634 *
635 * @return bool|null
636 */
637 public function grades_available() {
638 return true;
639 }
640
b13142da
DM
641 /**
642 * Returns the localized name of the grading strategy method to be displayed to the users
643 *
644 * @return string
645 */
646 public function strategy_name() {
f05c168d 647 return get_string('pluginname', 'workshopform_' . $this->strategy);
b13142da 648 }
b761e6d9
DM
649
650 /**
651 * Prepare an individual workshop plan for the given user.
652 *
f05c168d
DM
653 * @param int $userid whom the plan is prepared for
654 * @param stdClass context of the planned workshop
655 * @return stdClass data object to be passed to the renderer
b761e6d9 656 */
d895c6aa 657 public function prepare_user_plan($userid) {
b761e6d9
DM
658 global $DB;
659
660 $phases = array();
661
662 // Prepare tasks for the setup phase
663 $phase = new stdClass();
664 $phase->title = get_string('phasesetup', 'workshop');
665 $phase->tasks = array();
d895c6aa 666 if (has_capability('moodle/course:manageactivities', $this->context, $userid)) {
da0b1f70
DM
667 $task = new stdClass();
668 $task->title = get_string('taskintro', 'workshop');
669 $task->link = $this->updatemod_url();
670 $task->completed = !(trim(strip_tags($this->intro)) == '');
671 $phase->tasks['intro'] = $task;
672 }
d895c6aa 673 if (has_capability('moodle/course:manageactivities', $this->context, $userid)) {
454e8dd9
DM
674 $task = new stdClass();
675 $task->title = get_string('taskinstructauthors', 'workshop');
676 $task->link = $this->updatemod_url();
677 $task->completed = !(trim(strip_tags($this->instructauthors)) == '');
678 $phase->tasks['instructauthors'] = $task;
679 }
d895c6aa 680 if (has_capability('mod/workshop:editdimensions', $this->context, $userid)) {
b761e6d9 681 $task = new stdClass();
da0b1f70
DM
682 $task->title = get_string('editassessmentform', 'workshop');
683 $task->link = $this->editform_url();
684 if ($this->assessment_form_ready()) {
685 $task->completed = true;
686 } elseif ($this->phase > self::PHASE_SETUP) {
687 $task->completed = false;
688 }
b761e6d9
DM
689 $phase->tasks['editform'] = $task;
690 }
da0b1f70
DM
691 if (empty($phase->tasks) and $this->phase == self::PHASE_SETUP) {
692 // if we are in the setup phase and there is no task (typical for students), let us
693 // display some explanation what is going on
694 $task = new stdClass();
695 $task->title = get_string('undersetup', 'workshop');
696 $task->completed = 'info';
697 $phase->tasks['setupinfo'] = $task;
698 }
b761e6d9
DM
699 $phases[self::PHASE_SETUP] = $phase;
700
701 // Prepare tasks for the submission phase
702 $phase = new stdClass();
703 $phase->title = get_string('phasesubmission', 'workshop');
704 $phase->tasks = array();
d895c6aa 705 if (has_capability('moodle/course:manageactivities', $this->context, $userid)) {
b761e6d9 706 $task = new stdClass();
00aca3c1
DM
707 $task->title = get_string('taskinstructreviewers', 'workshop');
708 $task->link = $this->updatemod_url();
709 if (trim(strip_tags($this->instructreviewers))) {
da0b1f70
DM
710 $task->completed = true;
711 } elseif ($this->phase >= self::PHASE_ASSESSMENT) {
712 $task->completed = false;
da0b1f70 713 }
00aca3c1 714 $phase->tasks['instructreviewers'] = $task;
b761e6d9 715 }
d895c6aa 716 if (has_capability('mod/workshop:submit', $this->context, $userid, false)) {
da0b1f70 717 $task = new stdClass();
00aca3c1
DM
718 $task->title = get_string('tasksubmit', 'workshop');
719 $task->link = $this->submission_url();
720 if ($DB->record_exists('workshop_submissions', array('workshopid'=>$this->id, 'example'=>0, 'authorid'=>$userid))) {
da0b1f70
DM
721 $task->completed = true;
722 } elseif ($this->phase >= self::PHASE_ASSESSMENT) {
723 $task->completed = false;
00aca3c1
DM
724 } else {
725 $task->completed = null; // still has a chance to submit
da0b1f70 726 }
00aca3c1 727 $phase->tasks['submit'] = $task;
da0b1f70 728 }
b761e6d9 729 $phases[self::PHASE_SUBMISSION] = $phase;
d895c6aa 730 if (has_capability('mod/workshop:allocate', $this->context, $userid)) {
da0b1f70
DM
731 $task = new stdClass();
732 $task->title = get_string('allocate', 'workshop');
733 $task->link = $this->allocation_url();
d895c6aa 734 $numofauthors = count(get_users_by_capability($this->context, 'mod/workshop:submit', 'u.id', '', '', '',
a3610b08
DM
735 '', '', false, true));
736 $numofsubmissions = $DB->count_records('workshop_submissions', array('workshopid'=>$this->id, 'example'=>0));
737 $sql = 'SELECT COUNT(s.id) AS nonallocated
738 FROM {workshop_submissions} s
739 LEFT JOIN {workshop_assessments} a ON (a.submissionid=s.id)
740 WHERE s.workshopid = :workshopid AND s.example=0 AND a.submissionid IS NULL';
741 $params['workshopid'] = $this->id;
742 $numnonallocated = $DB->count_records_sql($sql, $params);
da0b1f70
DM
743 if ($numofsubmissions == 0) {
744 $task->completed = null;
a3610b08 745 } elseif ($numnonallocated == 0) {
da0b1f70
DM
746 $task->completed = true;
747 } elseif ($this->phase > self::PHASE_SUBMISSION) {
748 $task->completed = false;
749 } else {
750 $task->completed = null; // still has a chance to allocate
751 }
752 $a = new stdClass();
3189fb2d
DM
753 $a->expected = $numofauthors;
754 $a->submitted = $numofsubmissions;
a3610b08 755 $a->allocate = $numnonallocated;
3189fb2d 756 $task->details = get_string('allocatedetails', 'workshop', $a);
da0b1f70 757 unset($a);
3189fb2d 758 $phase->tasks['allocate'] = $task;
3dc78e5b
DM
759
760 if ($numofsubmissions < $numofauthors and $this->phase >= self::PHASE_SUBMISSION) {
761 $task = new stdClass();
762 $task->title = get_string('someuserswosubmission', 'workshop');
763 $task->completed = 'info';
764 $phase->tasks['allocateinfo'] = $task;
765 }
da0b1f70 766 }
b761e6d9
DM
767
768 // Prepare tasks for the peer-assessment phase (includes eventual self-assessments)
769 $phase = new stdClass();
770 $phase->title = get_string('phaseassessment', 'workshop');
771 $phase->tasks = array();
d895c6aa 772 $phase->isreviewer = has_capability('mod/workshop:peerassess', $this->context, $userid);
3dc78e5b 773 $phase->assessments = $this->get_assessments_by_reviewer($userid);
b761e6d9
DM
774 $numofpeers = 0; // number of allocated peer-assessments
775 $numofpeerstodo = 0; // number of peer-assessments to do
776 $numofself = 0; // number of allocated self-assessments - should be 0 or 1
777 $numofselftodo = 0; // number of self-assessments to do - should be 0 or 1
778 foreach ($phase->assessments as $a) {
779 if ($a->authorid == $userid) {
780 $numofself++;
781 if (is_null($a->grade)) {
782 $numofselftodo++;
783 }
784 } else {
785 $numofpeers++;
786 if (is_null($a->grade)) {
787 $numofpeerstodo++;
788 }
789 }
790 }
791 unset($a);
792 if ($numofpeers) {
793 $task = new stdClass();
3dc78e5b
DM
794 if ($numofpeerstodo == 0) {
795 $task->completed = true;
796 } elseif ($this->phase > self::PHASE_ASSESSMENT) {
797 $task->completed = false;
798 }
b761e6d9
DM
799 $a = new stdClass();
800 $a->total = $numofpeers;
801 $a->todo = $numofpeerstodo;
802 $task->title = get_string('taskassesspeers', 'workshop');
da0b1f70 803 $task->details = get_string('taskassesspeersdetails', 'workshop', $a);
b761e6d9
DM
804 unset($a);
805 $phase->tasks['assesspeers'] = $task;
806 }
807 if ($numofself) {
808 $task = new stdClass();
3dc78e5b
DM
809 if ($numofselftodo == 0) {
810 $task->completed = true;
811 } elseif ($this->phase > self::PHASE_ASSESSMENT) {
812 $task->completed = false;
813 }
b761e6d9
DM
814 $task->title = get_string('taskassessself', 'workshop');
815 $phase->tasks['assessself'] = $task;
816 }
817 $phases[self::PHASE_ASSESSMENT] = $phase;
818
819 // Prepare tasks for the grading evaluation phase - todo
820 $phase = new stdClass();
821 $phase->title = get_string('phaseevaluation', 'workshop');
822 $phase->tasks = array();
823 $phases[self::PHASE_EVALUATION] = $phase;
824
825 // Prepare tasks for the "workshop closed" phase - todo
826 $phase = new stdClass();
827 $phase->title = get_string('phaseclosed', 'workshop');
828 $phase->tasks = array();
829 $phases[self::PHASE_CLOSED] = $phase;
830
831 // Polish data, set default values if not done explicitly
832 foreach ($phases as $phasecode => $phase) {
833 $phase->title = isset($phase->title) ? $phase->title : '';
834 $phase->tasks = isset($phase->tasks) ? $phase->tasks : array();
835 if ($phasecode == $this->phase) {
836 $phase->active = true;
837 } else {
838 $phase->active = false;
839 }
454e8dd9
DM
840 if (!isset($phase->actions)) {
841 $phase->actions = array();
842 }
b761e6d9
DM
843
844 foreach ($phase->tasks as $taskcode => $task) {
845 $task->title = isset($task->title) ? $task->title : '';
da0b1f70
DM
846 $task->link = isset($task->link) ? $task->link : null;
847 $task->details = isset($task->details) ? $task->details : '';
b761e6d9
DM
848 $task->completed = isset($task->completed) ? $task->completed : null;
849 }
850 }
454e8dd9
DM
851
852 // Add phase swithing actions
d895c6aa 853 if (has_capability('mod/workshop:switchphase', $this->context, $userid)) {
454e8dd9
DM
854 foreach ($phases as $phasecode => $phase) {
855 if (! $phase->active) {
856 $action = new stdClass();
857 $action->type = 'switchphase';
858 $action->url = $this->switchphase_url($phasecode);
859 $phase->actions[] = $action;
860 }
861 }
862 }
863
b761e6d9
DM
864 return $phases;
865 }
866
867 /**
868 * Has the assessment form been defined?
869 *
870 * @return bool
871 */
872 public function assessment_form_ready() {
873 return $this->grading_strategy_instance()->form_ready();
874 }
875
454e8dd9
DM
876 /**
877 * Switch to a new workshop phase
878 *
879 * Modifies the underlying database record. You should terminate the script shortly after calling this.
880 *
881 * @param int $newphase new phase code
882 * @return bool true if success, false otherwise
883 */
884 public function switch_phase($newphase) {
885 global $DB;
886
887 $known = $this->available_phases();
888 if (!isset($known[$newphase])) {
889 return false;
890 }
891 $DB->set_field('workshop', 'phase', $newphase, array('id' => $this->id));
892 return true;
893 }
ddb59c77
DM
894
895 /**
896 * Saves a raw grade for submission as calculated from the assessment form fields
897 *
898 * @param array $assessmentid assessment record id, must exists
00aca3c1 899 * @param mixed $grade raw percentual grade from 0.00000 to 100.00000
ddb59c77
DM
900 * @return false|float the saved grade
901 */
902 public function set_peer_grade($assessmentid, $grade) {
903 global $DB;
904
905 if (is_null($grade)) {
906 return false;
907 }
908 $data = new stdClass();
909 $data->id = $assessmentid;
910 $data->grade = $grade;
911 $DB->update_record('workshop_assessments', $data);
912 return $grade;
913 }
6516b9e9 914
29dc43e7
DM
915 /**
916 * Prepares data object with all workshop grades to be rendered
917 *
5e71cefb
DM
918 * @param int $userid the user we are preparing the report for
919 * @param mixed $groups single group or array of groups - only show users who are in one of these group(s). Defaults to all
29dc43e7 920 * @param int $page the current page (for the pagination)
5e71cefb
DM
921 * @param int $perpage participants per page (for the pagination)
922 * @param string $sortby lastname|firstname|submissiontitle|submissiongrade|gradinggrade|totalgrade
923 * @param string $sorthow ASC|DESC
29dc43e7
DM
924 * @return stdClass data for the renderer
925 */
d895c6aa 926 public function prepare_grading_report($userid, $groups, $page, $perpage, $sortby, $sorthow) {
29dc43e7
DM
927 global $DB;
928
d895c6aa
DM
929 $canviewall = has_capability('mod/workshop:viewallassessments', $this->context, $userid);
930 $isparticipant = has_any_capability(array('mod/workshop:submit', 'mod/workshop:peerassess'), $this->context, $userid);
29dc43e7
DM
931
932 if (!$canviewall and !$isparticipant) {
933 // who the hell is this?
934 return array();
935 }
936
5e71cefb
DM
937 if (!in_array($sortby, array('lastname','firstname','submissiontitle','submissiongrade','gradinggrade','totalgrade'))) {
938 $sortby = 'lastname';
939 }
940
941 if (!($sorthow === 'ASC' or $sorthow === 'DESC')) {
942 $sorthow = 'ASC';
943 }
944
945 // get the list of user ids to be displayed
29dc43e7
DM
946 if ($canviewall) {
947 // fetch the list of ids of all workshop participants - this may get really long so fetch just id
d895c6aa 948 $participants = get_users_by_capability($this->context, array('mod/workshop:submit', 'mod/workshop:peerassess'),
5e71cefb 949 'u.id', '', '', '', $groups, '', false, false, true);
29dc43e7
DM
950 } else {
951 // this is an ordinary workshop participant (aka student) - display the report just for him/her
952 $participants = array($userid => (object)array('id' => $userid));
953 }
954
5e71cefb 955 // we will need to know the number of all records later for the pagination purposes
29dc43e7
DM
956 $numofparticipants = count($participants);
957
5e71cefb
DM
958 // load all fields which can be used sorting and paginate the records
959 list($participantids, $params) = $DB->get_in_or_equal(array_keys($participants), SQL_PARAMS_NAMED);
960 $params['workshopid'] = $this->id;
961 $sqlsort = $sortby . ' ' . $sorthow . ',u.lastname,u.firstname,u.id';
962 $sql = "SELECT u.id AS userid,u.firstname,u.lastname,u.picture,u.imagealt,
963 s.title AS submissiontitle, a.submissiongrade, a.gradinggrade, a.totalgrade
964 FROM {user} u
965 LEFT JOIN {workshop_submissions} s ON (s.authorid = u.id)
966 LEFT JOIN {workshop_aggregations} a ON (a.userid = u.id)
967 WHERE s.workshopid = :workshopid AND s.example = 0 AND u.id $participantids
968 ORDER BY $sqlsort";
969 $participants = $DB->get_records_sql($sql, $params, $page * $perpage, $perpage);
29dc43e7
DM
970
971 // this will hold the information needed to display user names and pictures
5e71cefb
DM
972 $userinfo = array();
973
974 // get the user details for all participants to display
975 foreach ($participants as $participant) {
976 if (!isset($userinfo[$participant->userid])) {
977 $userinfo[$participant->userid] = new stdClass();
978 $userinfo[$participant->userid]->id = $participant->userid;
979 $userinfo[$participant->userid]->firstname = $participant->firstname;
980 $userinfo[$participant->userid]->lastname = $participant->lastname;
981 $userinfo[$participant->userid]->picture = $participant->picture;
982 $userinfo[$participant->userid]->imagealt = $participant->imagealt;
983 }
984 }
29dc43e7 985
5e71cefb 986 // load the submissions details
29dc43e7 987 $submissions = $this->get_submissions(array_keys($participants));
5e71cefb
DM
988
989 // get the user details for all moderators (teachers) that have overridden a submission grade
29dc43e7 990 foreach ($submissions as $submission) {
29dc43e7
DM
991 if (!isset($userinfo[$submission->gradeoverby])) {
992 $userinfo[$submission->gradeoverby] = new stdClass();
993 $userinfo[$submission->gradeoverby]->id = $submission->gradeoverby;
994 $userinfo[$submission->gradeoverby]->firstname = $submission->overfirstname;
995 $userinfo[$submission->gradeoverby]->lastname = $submission->overlastname;
996 $userinfo[$submission->gradeoverby]->picture = $submission->overpicture;
997 $userinfo[$submission->gradeoverby]->imagealt = $submission->overimagealt;
998 }
999 }
1000
5e71cefb 1001 // get the user details for all reviewers of the displayed participants
29dc43e7
DM
1002 $reviewers = array();
1003 if ($submissions) {
1004 list($submissionids, $params) = $DB->get_in_or_equal(array_keys($submissions), SQL_PARAMS_NAMED);
1005 $sql = "SELECT a.id AS assessmentid, a.submissionid, a.grade, a.gradinggrade, a.gradinggradeover,
1006 r.id AS reviewerid, r.lastname, r.firstname, r.picture, r.imagealt,
1007 s.id AS submissionid, s.authorid
1008 FROM {workshop_assessments} a
1009 JOIN {user} r ON (a.reviewerid = r.id)
1010 JOIN {workshop_submissions} s ON (a.submissionid = s.id)
1011 WHERE a.submissionid $submissionids";
1012 $reviewers = $DB->get_records_sql($sql, $params);
1013 foreach ($reviewers as $reviewer) {
1014 if (!isset($userinfo[$reviewer->reviewerid])) {
1015 $userinfo[$reviewer->reviewerid] = new stdClass();
1016 $userinfo[$reviewer->reviewerid]->id = $reviewer->reviewerid;
1017 $userinfo[$reviewer->reviewerid]->firstname = $reviewer->firstname;
1018 $userinfo[$reviewer->reviewerid]->lastname = $reviewer->lastname;
1019 $userinfo[$reviewer->reviewerid]->picture = $reviewer->picture;
1020 $userinfo[$reviewer->reviewerid]->imagealt = $reviewer->imagealt;
1021 }
1022 }
1023 }
1024
5e71cefb 1025 // get the user details for all reviewees of the displayed participants
934329e5
DM
1026 $reviewees = array();
1027 if ($participants) {
1028 list($participantids, $params) = $DB->get_in_or_equal(array_keys($participants), SQL_PARAMS_NAMED);
1029 $params['workshopid'] = $this->id;
1030 $sql = "SELECT a.id AS assessmentid, a.submissionid, a.grade, a.gradinggrade, a.gradinggradeover,
1031 u.id AS reviewerid,
1032 s.id AS submissionid,
1033 e.id AS authorid, e.lastname, e.firstname, e.picture, e.imagealt
1034 FROM {user} u
1035 JOIN {workshop_assessments} a ON (a.reviewerid = u.id)
1036 JOIN {workshop_submissions} s ON (a.submissionid = s.id)
1037 JOIN {user} e ON (s.authorid = e.id)
1038 WHERE u.id $participantids AND s.workshopid = :workshopid";
1039 $reviewees = $DB->get_records_sql($sql, $params);
1040 foreach ($reviewees as $reviewee) {
1041 if (!isset($userinfo[$reviewee->authorid])) {
1042 $userinfo[$reviewee->authorid] = new stdClass();
1043 $userinfo[$reviewee->authorid]->id = $reviewee->authorid;
1044 $userinfo[$reviewee->authorid]->firstname = $reviewee->firstname;
1045 $userinfo[$reviewee->authorid]->lastname = $reviewee->lastname;
1046 $userinfo[$reviewee->authorid]->picture = $reviewee->picture;
1047 $userinfo[$reviewee->authorid]->imagealt = $reviewee->imagealt;
1048 }
29dc43e7
DM
1049 }
1050 }
1051
5e71cefb
DM
1052 // finally populate the object to be rendered
1053 $grades = $participants;
29dc43e7
DM
1054
1055 foreach ($participants as $participant) {
1056 // set up default (null) values
5e71cefb
DM
1057 $grades[$participant->userid]->reviewedby = array();
1058 $grades[$participant->userid]->reviewerof = array();
29dc43e7
DM
1059 }
1060 unset($participants);
1061 unset($participant);
1062
1063 foreach ($submissions as $submission) {
1064 $grades[$submission->authorid]->submissionid = $submission->id;
1065 $grades[$submission->authorid]->submissiontitle = $submission->title;
1066 $grades[$submission->authorid]->submissiongrade = $submission->grade;
1067 $grades[$submission->authorid]->submissiongradeover = $submission->gradeover;
1068 $grades[$submission->authorid]->submissiongradeoverby = $submission->gradeoverby;
1069 }
1070 unset($submissions);
1071 unset($submission);
1072
1073 foreach($reviewers as $reviewer) {
1074 $info = new stdClass();
1075 $info->userid = $reviewer->reviewerid;
1076 $info->assessmentid = $reviewer->assessmentid;
1077 $info->submissionid = $reviewer->submissionid;
1078 $info->grade = $reviewer->grade;
1079 $info->gradinggrade = $reviewer->gradinggrade;
1080 $info->gradinggradeover = $reviewer->gradinggradeover;
1081 $grades[$reviewer->authorid]->reviewedby[$reviewer->reviewerid] = $info;
1082 }
1083 unset($reviewers);
1084 unset($reviewer);
1085
1086 foreach($reviewees as $reviewee) {
1087 $info = new stdClass();
1088 $info->userid = $reviewee->authorid;
1089 $info->assessmentid = $reviewee->assessmentid;
1090 $info->submissionid = $reviewee->submissionid;
1091 $info->grade = $reviewee->grade;
1092 $info->gradinggrade = $reviewee->gradinggrade;
1093 $info->gradinggradeover = $reviewee->gradinggradeover;
1094 $grades[$reviewee->reviewerid]->reviewerof[$reviewee->authorid] = $info;
1095 }
1096 unset($reviewees);
1097 unset($reviewee);
1098
29dc43e7
DM
1099 $data = new stdClass();
1100 $data->grades = $grades;
1101 $data->userinfo = $userinfo;
1102 $data->totalcount = $numofparticipants;
1103 return $data;
1104 }
1105
29dc43e7
DM
1106 /**
1107 * Calculate the participant's total grade given the aggregated grades for submission and assessments
1108 *
1109 * todo there will be a setting how to deal with null values (for example no grade for submission) - if
1110 * they are considered as 0 or excluded
1111 *
1112 * @param float|null $grade for submission
1113 * @param float|null $gradinggrade for assessment
1114 * @return float|null
1115 */
1116 public function total_grade($grade=null, $gradinggrade=null) {
1117 if (is_null($grade) and is_null($gradinggrade)) {
1118 return null;
1119 }
1120 return grade_floatval((float)$grade + (float)$gradinggrade);
1121 }
1122
aa40adbf
DM
1123 ////////////////////////////////////////////////////////////////////////////////
1124 // Internal methods (implementation details) //
1125 ////////////////////////////////////////////////////////////////////////////////
6516b9e9
DM
1126
1127 /**
aa40adbf 1128 * Given a list of user ids, returns the filtered one containing just ids of users with own submission
6516b9e9 1129 *
aa40adbf
DM
1130 * Example submissions are ignored.
1131 *
1132 * @param array $userids
6516b9e9
DM
1133 * @return array
1134 */
aa40adbf
DM
1135 protected function users_with_submission(array $userids) {
1136 global $DB;
1137
1138 if (empty($userids)) {
1139 return array();
1140 }
1141 $userswithsubmission = array();
1142 list($usql, $uparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
00aca3c1 1143 $sql = "SELECT id,authorid
aa40adbf 1144 FROM {workshop_submissions}
00aca3c1 1145 WHERE example = 0 AND workshopid = :workshopid AND authorid $usql";
aa40adbf
DM
1146 $params = array('workshopid' => $this->id);
1147 $params = array_merge($params, $uparams);
1148 $submissions = $DB->get_records_sql($sql, $params);
1149 foreach ($submissions as $submission) {
00aca3c1 1150 $userswithsubmission[$submission->authorid] = true;
aa40adbf
DM
1151 }
1152
1153 return $userswithsubmission;
6516b9e9
DM
1154 }
1155
aa40adbf
DM
1156 /**
1157 * @return array of available workshop phases
1158 */
1159 protected function available_phases() {
1160 return array(
1161 self::PHASE_SETUP => true,
1162 self::PHASE_SUBMISSION => true,
1163 self::PHASE_ASSESSMENT => true,
1164 self::PHASE_EVALUATION => true,
1165 self::PHASE_CLOSED => true,
1166 );
1167 }
1168
1169
66c9894d 1170}