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