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