MDL-20652 workshop: initial work on example submissions
[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
89c1aa97
DM
33require_once(dirname(__FILE__).'/lib.php'); // we extend this library here
34require_once($CFG->libdir . '/gradelib.php'); // we use some rounding and comparing routines here
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 45 /** return statuses of {@link add_allocation} to be passed to a workshop renderer method */
f6e8b318
DM
46 const ALLOCATION_EXISTS = -1;
47 const ALLOCATION_ERROR = -2;
b761e6d9
DM
48
49 /** the internal code of the workshop phases as are stored in the database */
f6e8b318
DM
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
56 /** the internal code of the examples modes as are stored in the database */
57 const EXAMPLES_VOLUNTARY = 0;
58 const EXAMPLES_BEFORE_SUBMISSION = 1;
59 const EXAMPLES_BEFORE_ASSESSMENT = 2;
b761e6d9 60
65ba104c 61 /** @var stdClass course module record */
a39d7d87
DM
62 public $cm = null;
63
65ba104c 64 /** @var stdClass course record */
a39d7d87 65 public $course = null;
6e309973 66
f6e8b318
DM
67 /** @var stdClass context object */
68 public $context = null;
69
b761e6d9
DM
70 /**
71 * @var workshop_strategy grading strategy instance
72 * Do not use directly, get the instance using {@link workshop::grading_strategy_instance()}
73 */
b13142da
DM
74 protected $strategyinstance = null;
75
45d24d39
DM
76 /**
77 * @var workshop_evaluation grading evaluation instance
78 * Do not use directly, get the instance using {@link workshop::grading_evaluation_instance()}
79 */
80 protected $evaluationinstance = null;
81
6e309973 82 /**
65ba104c 83 * Initializes the workshop API instance using the data from DB
a39d7d87
DM
84 *
85 * Makes deep copy of all passed records properties. Replaces integer $course attribute
86 * with a full database record (course should not be stored in instances table anyway).
6e309973 87 *
b13142da 88 * @param stdClass $dbrecord Workshop instance data from {workshop} table
06d73dd5
DM
89 * @param stdClass $cm Course module record as returned by {@link get_coursemodule_from_id()}
90 * @param stdClass $course Course record from {course} table
4efd7b5d 91 * @param stdClass $context The context of the workshop instance
0dc47fb9 92 */
4efd7b5d 93 public function __construct(stdClass $dbrecord, stdClass $cm, stdClass $course, stdClass $context=null) {
f05c168d
DM
94 foreach ($dbrecord as $field => $value) {
95 $this->{$field} = $value;
a39d7d87 96 }
45d24d39
DM
97 $this->cm = $cm;
98 $this->course = $course; // beware - this replaces the standard course field in the instance table
99 // this is intentional - IMO there should be no such field as it violates
4efd7b5d
DM
100 // 3rd normal form with no real performance gain
101 if (is_null($context)) {
102 $this->context = get_context_instance(CONTEXT_MODULE, $this->cm->id);
103 } else {
104 $this->context = $context;
105 }
106 $this->evaluation = 'best'; // todo make this configurable although we have no alternatives yet
6e309973
DM
107 }
108
aa40adbf
DM
109 ////////////////////////////////////////////////////////////////////////////////
110 // Static methods //
111 ////////////////////////////////////////////////////////////////////////////////
112
da0b1f70 113 /**
aa40adbf 114 * Return list of available allocation methods
da0b1f70 115 *
aa40adbf 116 * @return array Array ['string' => 'string'] of localized allocation method names
da0b1f70 117 */
aa40adbf
DM
118 public static function installed_allocators() {
119 $installed = get_plugin_list('workshopallocation');
120 $forms = array();
121 foreach ($installed as $allocation => $allocationpath) {
122 if (file_exists($allocationpath . '/lib.php')) {
123 $forms[$allocation] = get_string('pluginname', 'workshopallocation_' . $allocation);
124 }
f05c168d 125 }
aa40adbf
DM
126 // usability - make sure that manual allocation appears the first
127 if (isset($forms['manual'])) {
128 $m = array('manual' => $forms['manual']);
129 unset($forms['manual']);
130 $forms = array_merge($m, $forms);
da0b1f70 131 }
aa40adbf
DM
132 return $forms;
133 }
da0b1f70 134
aa40adbf
DM
135 /**
136 * Returns an array of options for the editors that are used for submitting and assessing instructions
137 *
138 * @param stdClass $context
139 * @return array
140 */
141 public static function instruction_editors_options(stdClass $context) {
142 return array('subdirs' => 1, 'maxbytes' => 0, 'maxfiles' => EDITOR_UNLIMITED_FILES,
143 'changeformat' => 1, 'context' => $context, 'noclean' => 1, 'trusttext' => 0);
da0b1f70
DM
144 }
145
61b737a5
DM
146 /**
147 * Given the percent and the total, returns the number
148 *
149 * @param float $percent from 0 to 100
150 * @param float $total the 100% value
151 * @return float
152 */
153 public static function percent_to_value($percent, $total) {
154 if ($percent < 0 or $percent > 100) {
155 throw new coding_exception('The percent can not be less than 0 or higher than 100');
156 }
157
158 return $total * $percent / 100;
159 }
160
f6e8b318
DM
161 /**
162 * Returns an array of numeric values that can be used as maximum grades
163 *
164 * @return array Array of integers
165 */
166 public static function available_maxgrades_list() {
167 $grades = array();
168 for ($i=100; $i>=0; $i--) {
169 $grades[$i] = $i;
170 }
171 return $grades;
172 }
173
174 /**
175 * Returns the localized list of supported examples modes
176 *
177 * @return array
178 */
179 public static function available_example_modes_list() {
180 $options = array();
181 $options[self::EXAMPLES_VOLUNTARY] = get_string('examplesvoluntary', 'workshop');
182 $options[self::EXAMPLES_BEFORE_SUBMISSION] = get_string('examplesbeforesubmission', 'workshop');
183 $options[self::EXAMPLES_BEFORE_ASSESSMENT] = get_string('examplesbeforeassessment', 'workshop');
184 return $options;
185 }
186
187 /**
188 * Returns the list of available grading strategy methods
189 *
190 * @return array ['string' => 'string']
191 */
192 public static function available_strategies_list() {
193 $installed = get_plugin_list('workshopform');
194 $forms = array();
195 foreach ($installed as $strategy => $strategypath) {
196 if (file_exists($strategypath . '/lib.php')) {
197 $forms[$strategy] = get_string('pluginname', 'workshopform_' . $strategy);
198 }
199 }
200 return $forms;
201 }
202
203 /**
204 * Return an array of possible values of assessment dimension weight
205 *
206 * @return array of integers 0, 1, 2, ..., 16
207 */
208 public static function available_dimension_weights_list() {
209 $weights = array();
210 for ($i=16; $i>=0; $i--) {
211 $weights[$i] = $i;
212 }
213 return $weights;
214 }
215
216 /**
217 * Helper function returning the greatest common divisor
218 *
219 * @param int $a
220 * @param int $b
221 * @return int
222 */
223 public static function gcd($a, $b) {
224 return ($b == 0) ? ($a):(self::gcd($b, $a % $b));
225 }
226
227 /**
228 * Helper function returning the least common multiple
229 *
230 * @param int $a
231 * @param int $b
232 * @return int
233 */
234 public static function lcm($a, $b) {
235 return ($a / self::gcd($a,$b)) * $b;
236 }
237
aa40adbf
DM
238 ////////////////////////////////////////////////////////////////////////////////
239 // Workshop API //
240 ////////////////////////////////////////////////////////////////////////////////
241
6e309973
DM
242 /**
243 * Fetches all users with the capability mod/workshop:submit in the current context
244 *
3d2924e9 245 * The returned objects contain id, lastname and firstname properties and are ordered by lastname,firstname
53fad4b9 246 *
aa40adbf 247 * @todo handle with limits and groups
53fad4b9 248 * @param bool $musthavesubmission If true, return only users who have already submitted. All possible authors otherwise.
65ba104c 249 * @return array array[userid] => stdClass{->id ->lastname ->firstname}
6e309973 250 */
d895c6aa
DM
251 public function get_potential_authors($musthavesubmission=true) {
252 $users = get_users_by_capability($this->context, 'mod/workshop:submit',
1fed6ce3 253 'u.id,u.lastname,u.firstname', 'u.lastname,u.firstname,u.id', '', '', '', '', false, false, true);
3d2924e9 254 if ($musthavesubmission) {
da0b1f70 255 $users = array_intersect_key($users, $this->users_with_submission(array_keys($users)));
66c9894d 256 }
da0b1f70 257 return $users;
6e309973
DM
258 }
259
6e309973
DM
260 /**
261 * Fetches all users with the capability mod/workshop:peerassess in the current context
262 *
b13142da 263 * The returned objects contain id, lastname and firstname properties and are ordered by lastname,firstname
53fad4b9 264 *
aa40adbf 265 * @todo handle with limits and groups
53fad4b9 266 * @param bool $musthavesubmission If true, return only users who have already submitted. All possible users otherwise.
65ba104c 267 * @return array array[userid] => stdClass{->id ->lastname ->firstname}
6e309973 268 */
d895c6aa
DM
269 public function get_potential_reviewers($musthavesubmission=false) {
270 $users = get_users_by_capability($this->context, 'mod/workshop:peerassess',
1fed6ce3 271 'u.id, u.lastname, u.firstname', 'u.lastname,u.firstname,u.id', '', '', '', '', false, false, true);
3d2924e9
DM
272 if ($musthavesubmission) {
273 // users without their own submission can not be reviewers
da0b1f70 274 $users = array_intersect_key($users, $this->users_with_submission(array_keys($users)));
0968b1a3 275 }
da0b1f70 276 return $users;
0968b1a3
DM
277 }
278
b8ead2e6
DM
279 /**
280 * Groups the given users by the group membership
281 *
282 * This takes the module grouping settings into account. If "Available for group members only"
283 * is set, returns only groups withing the course module grouping. Always returns group [0] with
284 * all the given users.
285 *
65ba104c
DM
286 * @param array $users array[userid] => stdClass{->id ->lastname ->firstname}
287 * @return array array[groupid][userid] => stdClass{->id ->lastname ->firstname}
53fad4b9 288 */
3d2924e9 289 public function get_grouped($users) {
53fad4b9 290 global $DB;
3d2924e9 291 global $CFG;
53fad4b9 292
b8ead2e6
DM
293 $grouped = array(); // grouped users to be returned
294 if (empty($users)) {
295 return $grouped;
a7c5b918 296 }
3d2924e9 297 if (!empty($CFG->enablegroupings) and $this->cm->groupmembersonly) {
53fad4b9
DM
298 // Available for group members only - the workshop is available only
299 // to users assigned to groups within the selected grouping, or to
300 // any group if no grouping is selected.
301 $groupingid = $this->cm->groupingid;
b8ead2e6 302 // All users that are members of at least one group will be
53fad4b9 303 // added into a virtual group id 0
b8ead2e6 304 $grouped[0] = array();
53fad4b9
DM
305 } else {
306 $groupingid = 0;
b8ead2e6
DM
307 // there is no need to be member of a group so $grouped[0] will contain
308 // all users
309 $grouped[0] = $users;
53fad4b9 310 }
b8ead2e6 311 $gmemberships = groups_get_all_groups($this->cm->course, array_keys($users), $groupingid,
53fad4b9
DM
312 'gm.id,gm.groupid,gm.userid');
313 foreach ($gmemberships as $gmembership) {
b8ead2e6
DM
314 if (!isset($grouped[$gmembership->groupid])) {
315 $grouped[$gmembership->groupid] = array();
53fad4b9 316 }
b8ead2e6
DM
317 $grouped[$gmembership->groupid][$gmembership->userid] = $users[$gmembership->userid];
318 $grouped[0][$gmembership->userid] = $users[$gmembership->userid];
53fad4b9 319 }
b8ead2e6 320 return $grouped;
53fad4b9 321 }
6e309973 322
aa40adbf
DM
323 /**
324 * Returns the list of all allocations (it est assigned assessments) in the workshop
325 *
326 * Assessments of example submissions are ignored
327 *
328 * @return array
329 */
330 public function get_allocations() {
331 global $DB;
332
00aca3c1 333 $sql = 'SELECT a.id, a.submissionid, a.reviewerid, s.authorid
aa40adbf
DM
334 FROM {workshop_assessments} a
335 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
336 WHERE s.example = 0 AND s.workshopid = :workshopid';
337 $params = array('workshopid' => $this->id);
338
339 return $DB->get_records_sql($sql, $params);
340 }
341
6e309973
DM
342 /**
343 * Returns submissions from this workshop
344 *
3dc78e5b
DM
345 * Fetches data from {workshop_submissions} and adds some useful information from other
346 * tables. Does not return textual fields to prevent possible memory lack issues.
53fad4b9 347 *
00aca3c1 348 * @param mixed $authorid int|array|'all' If set to [array of] integer, return submission[s] of the given user[s] only
934329e5 349 * @return array of records or an empty array
6e309973 350 */
29dc43e7 351 public function get_submissions($authorid='all') {
6e309973
DM
352 global $DB;
353
00aca3c1
DM
354 $sql = 'SELECT s.id, s.workshopid, s.example, s.authorid, s.timecreated, s.timemodified,
355 s.title, s.grade, s.gradeover, s.gradeoverby,
356 u.lastname AS authorlastname, u.firstname AS authorfirstname,
357 u.picture AS authorpicture, u.imagealt AS authorimagealt,
358 t.lastname AS overlastname, t.firstname AS overfirstname,
359 t.picture AS overpicture, t.imagealt AS overimagealt
3d2924e9 360 FROM {workshop_submissions} s
00aca3c1 361 INNER JOIN {user} u ON (s.authorid = u.id)
29dc43e7
DM
362 LEFT JOIN {user} t ON (s.gradeoverby = t.id)
363 WHERE s.example = 0 AND s.workshopid = :workshopid';
3d2924e9 364 $params = array('workshopid' => $this->id);
6e309973 365
00aca3c1 366 if ('all' === $authorid) {
3d2924e9 367 // no additional conditions
934329e5 368 } elseif (!empty($authorid)) {
00aca3c1
DM
369 list($usql, $uparams) = $DB->get_in_or_equal($authorid, SQL_PARAMS_NAMED);
370 $sql .= " AND authorid $usql";
6e309973 371 $params = array_merge($params, $uparams);
3d2924e9 372 } else {
934329e5
DM
373 // $authorid is empty
374 return array();
6e309973 375 }
3dc78e5b 376 $sql .= ' ORDER BY u.lastname, u.firstname';
6e309973 377
3dc78e5b 378 return $DB->get_records_sql($sql, $params);
6e309973
DM
379 }
380
51508f25
DM
381 /**
382 * Returns a submission record with the author's data
383 *
384 * @param int $id submission id
385 * @return stdClass
386 */
387 public function get_submission_by_id($id) {
388 global $DB;
389
29dc43e7
DM
390 // we intentionally check the workshopid here, too, so the workshop can't touch submissions
391 // from other instances
51508f25
DM
392 $sql = 'SELECT s.*,
393 u.lastname AS authorlastname, u.firstname AS authorfirstname, u.id AS authorid,
394 u.picture AS authorpicture, u.imagealt AS authorimagealt
395 FROM {workshop_submissions} s
00aca3c1 396 INNER JOIN {user} u ON (s.authorid = u.id)
81eccf0a 397 WHERE s.example = 0 AND s.workshopid = :workshopid AND s.id = :id';
51508f25
DM
398 $params = array('workshopid' => $this->id, 'id' => $id);
399 return $DB->get_record_sql($sql, $params, MUST_EXIST);
400 }
401
53fad4b9 402 /**
3dc78e5b 403 * Returns a submission submitted by the given author
53fad4b9 404 *
3dc78e5b
DM
405 * @param int $id author id
406 * @return stdClass|false
53fad4b9 407 */
00aca3c1 408 public function get_submission_by_author($authorid) {
e9b0f0ab
DM
409 global $DB;
410
00aca3c1 411 if (empty($authorid)) {
53fad4b9
DM
412 return false;
413 }
3dc78e5b
DM
414 $sql = 'SELECT s.*,
415 u.lastname AS authorlastname, u.firstname AS authorfirstname, u.id AS authorid,
416 u.picture AS authorpicture, u.imagealt AS authorimagealt
417 FROM {workshop_submissions} s
00aca3c1
DM
418 INNER JOIN {user} u ON (s.authorid = u.id)
419 WHERE s.example = 0 AND s.workshopid = :workshopid AND s.authorid = :authorid';
420 $params = array('workshopid' => $this->id, 'authorid' => $authorid);
3dc78e5b 421 return $DB->get_record_sql($sql, $params);
53fad4b9 422 }
6e309973 423
81eccf0a
DM
424 /**
425 * Returns example submissions for this workshop
426 *
427 * @return array of records or an empty array
428 */
429 public function get_examples() {
430 global $DB;
431 return $DB->get_records('workshop_submissions', array('workshopid' => $this->id, 'example' => 1), 'title', 'id,title');
432 }
433
434 /**
435 * Returns full record of the given example submission
436 *
437 * @param int $id example submission od
438 * @return object
439 */
440 public function get_example_by_id($id) {
441 global $DB;
442 return $DB->get_record('workshop_submissions',
443 array('id' => $id, 'workshopid' => $this->id, 'example' => 1), '*', MUST_EXIST);
444 }
445
446 /**
447 * Removes the submission and all relevant data
448 *
449 * @param stdClass $submission record to delete
450 * @return void
451 */
452 public function delete_submission(stdClass $submission) {
453 global $DB;
454 $assessments = $DB->get_records('workshop_assessments', array('submissionid' => $submission->id), '', 'id');
455 $this->delete_assessment(array_keys($assessments));
456 $DB->delete_records('workshop_submissions', array('id' => $submission->id));
457 }
458
6e309973 459 /**
3dc78e5b 460 * Returns the list of all assessments in the workshop with some data added
6e309973
DM
461 *
462 * Fetches data from {workshop_assessments} and adds some useful information from other
3dc78e5b
DM
463 * tables. The returned object does not contain textual fields (ie comments) to prevent memory
464 * lack issues.
465 *
466 * @return array [assessmentid] => assessment stdClass
6e309973 467 */
3dc78e5b 468 public function get_all_assessments() {
6e309973 469 global $DB;
53fad4b9 470
f6e8b318 471 $sql = 'SELECT a.id, a.submissionid, a.reviewerid, a.timecreated, a.timemodified,
3dc78e5b 472 a.grade, a.gradinggrade, a.gradinggradeover, a.gradinggradeoverby,
3d2924e9
DM
473 reviewer.id AS reviewerid,reviewer.firstname AS reviewerfirstname,reviewer.lastname as reviewerlastname,
474 s.title,
ddb59c77 475 author.id AS authorid, author.firstname AS authorfirstname,author.lastname AS authorlastname
3d2924e9 476 FROM {workshop_assessments} a
00aca3c1 477 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id)
3d2924e9 478 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
00aca3c1 479 INNER JOIN {user} author ON (s.authorid = author.id)
3dc78e5b
DM
480 WHERE s.workshopid = :workshopid AND s.example = 0
481 ORDER BY reviewer.lastname, reviewer.firstname';
3d2924e9
DM
482 $params = array('workshopid' => $this->id);
483
3dc78e5b 484 return $DB->get_records_sql($sql, $params);
53fad4b9
DM
485 }
486
487 /**
3dc78e5b 488 * Get the complete information about the given assessment
53fad4b9
DM
489 *
490 * @param int $id Assessment ID
65ba104c 491 * @return mixed false if not found, stdClass otherwise
53fad4b9
DM
492 */
493 public function get_assessment_by_id($id) {
3dc78e5b
DM
494 global $DB;
495
496 $sql = 'SELECT a.*,
497 reviewer.id AS reviewerid,reviewer.firstname AS reviewerfirstname,reviewer.lastname as reviewerlastname,
498 s.title,
499 author.id AS authorid, author.firstname AS authorfirstname,author.lastname as authorlastname
500 FROM {workshop_assessments} a
00aca3c1 501 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id)
3dc78e5b 502 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
00aca3c1 503 INNER JOIN {user} author ON (s.authorid = author.id)
3dc78e5b
DM
504 WHERE a.id = :id AND s.workshopid = :workshopid';
505 $params = array('id' => $id, 'workshopid' => $this->id);
506
507 return $DB->get_record_sql($sql, $params, MUST_EXIST);
53fad4b9
DM
508 }
509
510 /**
3dc78e5b 511 * Get the complete information about all assessments allocated to the given reviewer
53fad4b9 512 *
00aca3c1 513 * @param int $reviewerid
3dc78e5b 514 * @return array
53fad4b9 515 */
00aca3c1 516 public function get_assessments_by_reviewer($reviewerid) {
3dc78e5b
DM
517 global $DB;
518
519 $sql = 'SELECT a.*,
ddb59c77
DM
520 reviewer.id AS reviewerid,reviewer.firstname AS reviewerfirstname,reviewer.lastname AS reviewerlastname,
521 s.id AS submissionid, s.title AS submissiontitle, s.timecreated AS submissioncreated,
522 s.timemodified AS submissionmodified,
523 author.id AS authorid, author.firstname AS authorfirstname,author.lastname AS authorlastname,
524 author.picture AS authorpicture, author.imagealt AS authorimagealt
3dc78e5b 525 FROM {workshop_assessments} a
00aca3c1 526 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id)
3dc78e5b 527 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
00aca3c1
DM
528 INNER JOIN {user} author ON (s.authorid = author.id)
529 WHERE s.example = 0 AND reviewer.id = :reviewerid AND s.workshopid = :workshopid';
530 $params = array('reviewerid' => $reviewerid, 'workshopid' => $this->id);
3dc78e5b
DM
531
532 return $DB->get_records_sql($sql, $params);
53fad4b9 533 }
6e309973 534
6e309973
DM
535 /**
536 * Allocate a submission to a user for review
53fad4b9 537 *
65ba104c 538 * @param stdClass $submission Submission record
6e309973 539 * @param int $reviewerid User ID
53fad4b9 540 * @param bool $bulk repeated inserts into DB expected
6e309973
DM
541 * @return int ID of the new assessment or an error code
542 */
65ba104c 543 public function add_allocation(stdClass $submission, $reviewerid, $bulk=false) {
6e309973
DM
544 global $DB;
545
00aca3c1 546 if ($DB->record_exists('workshop_assessments', array('submissionid' => $submission->id, 'reviewerid' => $reviewerid))) {
b761e6d9 547 return self::ALLOCATION_EXISTS;
6e309973
DM
548 }
549
6e309973 550 $now = time();
65ba104c 551 $assessment = new stdClass();
e554671d
DM
552 $assessment->submissionid = $submission->id;
553 $assessment->reviewerid = $reviewerid;
554 $assessment->timecreated = $now;
555 $assessment->timemodified = $now;
556 $assessment->weight = 1;
557 $assessment->generalcommentformat = FORMAT_HTML; // todo better default handling
558 $assessment->feedbackreviewerformat = FORMAT_HTML; // todo better default handling
6e309973 559
235b31c8 560 return $DB->insert_record('workshop_assessments', $assessment, true, $bulk);
6e309973
DM
561 }
562
6e309973 563 /**
53fad4b9 564 * Delete assessment record or records
6e309973 565 *
53fad4b9
DM
566 * @param mixed $id int|array assessment id or array of assessments ids
567 * @return bool false if $id not a valid parameter, true otherwise
6e309973
DM
568 */
569 public function delete_assessment($id) {
570 global $DB;
571
572 // todo remove all given grades from workshop_grades;
6e309973 573
53fad4b9 574 if (is_array($id)) {
235b31c8 575 return $DB->delete_records_list('workshop_assessments', 'id', $id);
3d2924e9 576 } else {
235b31c8 577 return $DB->delete_records('workshop_assessments', array('id' => $id));
53fad4b9 578 }
53fad4b9 579 }
6e309973
DM
580
581 /**
582 * Returns instance of grading strategy class
53fad4b9 583 *
65ba104c 584 * @return stdClass Instance of a grading strategy
6e309973
DM
585 */
586 public function grading_strategy_instance() {
3d2924e9
DM
587 global $CFG; // because we require other libs here
588
3fd2b0e1 589 if (is_null($this->strategyinstance)) {
f05c168d 590 $strategylib = dirname(__FILE__) . '/form/' . $this->strategy . '/lib.php';
6e309973
DM
591 if (is_readable($strategylib)) {
592 require_once($strategylib);
593 } else {
f05c168d 594 throw new coding_exception('the grading forms subplugin must contain library ' . $strategylib);
6e309973 595 }
0dc47fb9 596 $classname = 'workshop_' . $this->strategy . '_strategy';
3fd2b0e1
DM
597 $this->strategyinstance = new $classname($this);
598 if (!in_array('workshop_strategy', class_implements($this->strategyinstance))) {
b761e6d9 599 throw new coding_exception($classname . ' does not implement workshop_strategy interface');
6e309973
DM
600 }
601 }
3fd2b0e1 602 return $this->strategyinstance;
6e309973
DM
603 }
604
45d24d39
DM
605 /**
606 * Returns instance of grading evaluation class
607 *
608 * @return stdClass Instance of a grading evaluation
609 */
610 public function grading_evaluation_instance() {
611 global $CFG; // because we require other libs here
612
613 if (is_null($this->evaluationinstance)) {
614 $evaluationlib = dirname(__FILE__) . '/eval/' . $this->evaluation . '/lib.php';
615 if (is_readable($evaluationlib)) {
616 require_once($evaluationlib);
617 } else {
618 throw new coding_exception('the grading evaluation subplugin must contain library ' . $evaluationlib);
619 }
620 $classname = 'workshop_' . $this->evaluation . '_evaluation';
621 $this->evaluationinstance = new $classname($this);
622 if (!in_array('workshop_evaluation', class_implements($this->evaluationinstance))) {
623 throw new coding_exception($classname . ' does not implement workshop_evaluation interface');
624 }
625 }
626 return $this->evaluationinstance;
627 }
628
66c9894d
DM
629 /**
630 * Returns instance of submissions allocator
53fad4b9 631 *
130ae619 632 * @param string $method The name of the allocation method, must be PARAM_ALPHA
65ba104c 633 * @return stdClass Instance of submissions allocator
66c9894d
DM
634 */
635 public function allocator_instance($method) {
3d2924e9
DM
636 global $CFG; // because we require other libs here
637
f05c168d 638 $allocationlib = dirname(__FILE__) . '/allocation/' . $method . '/lib.php';
66c9894d
DM
639 if (is_readable($allocationlib)) {
640 require_once($allocationlib);
641 } else {
f05c168d 642 throw new coding_exception('Unable to find the allocation library ' . $allocationlib);
66c9894d
DM
643 }
644 $classname = 'workshop_' . $method . '_allocator';
645 return new $classname($this);
646 }
647
b8ead2e6 648 /**
454e8dd9 649 * @return moodle_url of this workshop's view page
b8ead2e6
DM
650 */
651 public function view_url() {
652 global $CFG;
653 return new moodle_url($CFG->wwwroot . '/mod/workshop/view.php', array('id' => $this->cm->id));
654 }
655
656 /**
454e8dd9 657 * @return moodle_url of the page for editing this workshop's grading form
b8ead2e6
DM
658 */
659 public function editform_url() {
660 global $CFG;
661 return new moodle_url($CFG->wwwroot . '/mod/workshop/editform.php', array('cmid' => $this->cm->id));
662 }
663
664 /**
454e8dd9 665 * @return moodle_url of the page for previewing this workshop's grading form
b8ead2e6
DM
666 */
667 public function previewform_url() {
668 global $CFG;
43b34576 669 return new moodle_url($CFG->wwwroot . '/mod/workshop/editformpreview.php', array('cmid' => $this->cm->id));
b8ead2e6
DM
670 }
671
672 /**
673 * @param int $assessmentid The ID of assessment record
454e8dd9 674 * @return moodle_url of the assessment page
b8ead2e6 675 */
a39d7d87 676 public function assess_url($assessmentid) {
b8ead2e6 677 global $CFG;
454e8dd9 678 $assessmentid = clean_param($assessmentid, PARAM_INT);
a39d7d87 679 return new moodle_url($CFG->wwwroot . '/mod/workshop/assessment.php', array('asid' => $assessmentid));
b8ead2e6
DM
680 }
681
39861053 682 /**
67cd00ba 683 * @return moodle_url of the page to view a submission, defaults to the own one
39861053 684 */
67cd00ba 685 public function submission_url($id=null) {
39861053 686 global $CFG;
67cd00ba 687 return new moodle_url($CFG->wwwroot . '/mod/workshop/submission.php', array('cmid' => $this->cm->id, 'id' => $id));
39861053
DM
688 }
689
81eccf0a
DM
690 /**
691 * @param int $id example submission id
692 * @return moodle_url of the page to view an example submission
693 */
694 public function example_url($id) {
695 global $CFG;
696 return new moodle_url($CFG->wwwroot . '/mod/workshop/example.php', array('cmid' => $this->cm->id, 'id' => $id));
697 }
698
da0b1f70 699 /**
454e8dd9 700 * @return moodle_url of the mod_edit form
da0b1f70
DM
701 */
702 public function updatemod_url() {
703 global $CFG;
704 return new moodle_url($CFG->wwwroot . '/course/modedit.php', array('update' => $this->cm->id, 'return' => 1));
705 }
706
454e8dd9
DM
707 /**
708 * @return moodle_url to the allocation page
709 */
da0b1f70
DM
710 public function allocation_url() {
711 global $CFG;
712 return new moodle_url($CFG->wwwroot . '/mod/workshop/allocation.php', array('cmid' => $this->cm->id));
713 }
714
454e8dd9
DM
715 /**
716 * @param int $phasecode The internal phase code
717 * @return moodle_url of the script to change the current phase to $phasecode
718 */
719 public function switchphase_url($phasecode) {
720 global $CFG;
721 $phasecode = clean_param($phasecode, PARAM_INT);
722 return new moodle_url($CFG->wwwroot . '/mod/workshop/switchphase.php', array('cmid' => $this->cm->id, 'phase' => $phasecode));
723 }
724
89c1aa97
DM
725 /**
726 * @return moodle_url to the aggregation page
727 */
728 public function aggregate_url() {
729 global $CFG;
730 return new moodle_url($CFG->wwwroot . '/mod/workshop/aggregate.php', array('cmid' => $this->cm->id));
731 }
732
b8ead2e6 733 /**
407b1e91
DM
734 * Are users allowed to create/edit their submissions?
735 *
736 * TODO: this depends on the workshop phase, phase deadlines, submitting after deadlines possibility
b8ead2e6 737 *
407b1e91 738 * @return bool
b8ead2e6 739 */
407b1e91
DM
740 public function submitting_allowed() {
741 return true;
b8ead2e6
DM
742 }
743
c1e883bb 744 /**
407b1e91 745 * Are reviewers allowed to create/edit their assessments?
c1e883bb 746 *
407b1e91 747 * TODO: this depends on the workshop phase, phase deadlines
c1e883bb
DM
748 *
749 * @return bool
750 */
407b1e91 751 public function assessing_allowed() {
c1e883bb
DM
752 return true;
753 }
754
407b1e91 755
3dc78e5b
DM
756 /**
757 * Are the peer-reviews available to the authors?
758 *
759 * TODO: this depends on the workshop phase
760 *
761 * @return bool
762 */
763 public function assessments_available() {
764 return true;
765 }
766
767 /**
768 * Can the given grades be displayed to the authors?
769 *
770 * Grades are not displayed if {@link self::assessments_available()} return false. The returned
f6e8b318 771 * value may be true (if yes, display grades) or false (no, hide grades yet)
3dc78e5b 772 *
f6e8b318 773 * @return bool
3dc78e5b
DM
774 */
775 public function grades_available() {
776 return true;
777 }
778
b761e6d9
DM
779 /**
780 * Prepare an individual workshop plan for the given user.
781 *
f05c168d
DM
782 * @param int $userid whom the plan is prepared for
783 * @param stdClass context of the planned workshop
784 * @return stdClass data object to be passed to the renderer
b761e6d9 785 */
d895c6aa 786 public function prepare_user_plan($userid) {
b761e6d9
DM
787 global $DB;
788
789 $phases = array();
790
791 // Prepare tasks for the setup phase
792 $phase = new stdClass();
793 $phase->title = get_string('phasesetup', 'workshop');
794 $phase->tasks = array();
d895c6aa 795 if (has_capability('moodle/course:manageactivities', $this->context, $userid)) {
da0b1f70
DM
796 $task = new stdClass();
797 $task->title = get_string('taskintro', 'workshop');
798 $task->link = $this->updatemod_url();
799 $task->completed = !(trim(strip_tags($this->intro)) == '');
800 $phase->tasks['intro'] = $task;
801 }
d895c6aa 802 if (has_capability('moodle/course:manageactivities', $this->context, $userid)) {
454e8dd9
DM
803 $task = new stdClass();
804 $task->title = get_string('taskinstructauthors', 'workshop');
805 $task->link = $this->updatemod_url();
806 $task->completed = !(trim(strip_tags($this->instructauthors)) == '');
807 $phase->tasks['instructauthors'] = $task;
808 }
d895c6aa 809 if (has_capability('mod/workshop:editdimensions', $this->context, $userid)) {
b761e6d9 810 $task = new stdClass();
da0b1f70
DM
811 $task->title = get_string('editassessmentform', 'workshop');
812 $task->link = $this->editform_url();
f6e8b318 813 if ($this->grading_strategy_instance()->form_ready()) {
da0b1f70
DM
814 $task->completed = true;
815 } elseif ($this->phase > self::PHASE_SETUP) {
816 $task->completed = false;
817 }
b761e6d9
DM
818 $phase->tasks['editform'] = $task;
819 }
81eccf0a
DM
820 if ($this->useexamples and has_capability('mod/workshop:manageexamples', $this->context, $userid)) {
821 $task = new stdClass();
822 $task->title = get_string('prepareexamples', 'workshop');
823 if ($DB->count_records('workshop_submissions', array('example' => 1, 'workshopid' => $this->id)) > 0) {
824 $task->completed = true;
825 } elseif ($this->phase > self::PHASE_SETUP) {
826 $task->completed = false;
827 }
828 $phase->tasks['prepareexamples'] = $task;
829 }
da0b1f70
DM
830 if (empty($phase->tasks) and $this->phase == self::PHASE_SETUP) {
831 // if we are in the setup phase and there is no task (typical for students), let us
832 // display some explanation what is going on
833 $task = new stdClass();
834 $task->title = get_string('undersetup', 'workshop');
835 $task->completed = 'info';
836 $phase->tasks['setupinfo'] = $task;
837 }
b761e6d9
DM
838 $phases[self::PHASE_SETUP] = $phase;
839
840 // Prepare tasks for the submission phase
841 $phase = new stdClass();
842 $phase->title = get_string('phasesubmission', 'workshop');
843 $phase->tasks = array();
81eccf0a
DM
844 if (($this->usepeerassessment or $this->useselfassessment)
845 and has_capability('moodle/course:manageactivities', $this->context, $userid)) {
b761e6d9 846 $task = new stdClass();
00aca3c1
DM
847 $task->title = get_string('taskinstructreviewers', 'workshop');
848 $task->link = $this->updatemod_url();
849 if (trim(strip_tags($this->instructreviewers))) {
da0b1f70
DM
850 $task->completed = true;
851 } elseif ($this->phase >= self::PHASE_ASSESSMENT) {
852 $task->completed = false;
da0b1f70 853 }
00aca3c1 854 $phase->tasks['instructreviewers'] = $task;
b761e6d9 855 }
d895c6aa 856 if (has_capability('mod/workshop:submit', $this->context, $userid, false)) {
da0b1f70 857 $task = new stdClass();
00aca3c1
DM
858 $task->title = get_string('tasksubmit', 'workshop');
859 $task->link = $this->submission_url();
860 if ($DB->record_exists('workshop_submissions', array('workshopid'=>$this->id, 'example'=>0, 'authorid'=>$userid))) {
da0b1f70
DM
861 $task->completed = true;
862 } elseif ($this->phase >= self::PHASE_ASSESSMENT) {
863 $task->completed = false;
00aca3c1
DM
864 } else {
865 $task->completed = null; // still has a chance to submit
da0b1f70 866 }
00aca3c1 867 $phase->tasks['submit'] = $task;
da0b1f70 868 }
d895c6aa 869 if (has_capability('mod/workshop:allocate', $this->context, $userid)) {
da0b1f70
DM
870 $task = new stdClass();
871 $task->title = get_string('allocate', 'workshop');
872 $task->link = $this->allocation_url();
d895c6aa 873 $numofauthors = count(get_users_by_capability($this->context, 'mod/workshop:submit', 'u.id', '', '', '',
a3610b08
DM
874 '', '', false, true));
875 $numofsubmissions = $DB->count_records('workshop_submissions', array('workshopid'=>$this->id, 'example'=>0));
876 $sql = 'SELECT COUNT(s.id) AS nonallocated
877 FROM {workshop_submissions} s
878 LEFT JOIN {workshop_assessments} a ON (a.submissionid=s.id)
879 WHERE s.workshopid = :workshopid AND s.example=0 AND a.submissionid IS NULL';
880 $params['workshopid'] = $this->id;
881 $numnonallocated = $DB->count_records_sql($sql, $params);
da0b1f70
DM
882 if ($numofsubmissions == 0) {
883 $task->completed = null;
a3610b08 884 } elseif ($numnonallocated == 0) {
da0b1f70
DM
885 $task->completed = true;
886 } elseif ($this->phase > self::PHASE_SUBMISSION) {
887 $task->completed = false;
888 } else {
889 $task->completed = null; // still has a chance to allocate
890 }
891 $a = new stdClass();
3189fb2d
DM
892 $a->expected = $numofauthors;
893 $a->submitted = $numofsubmissions;
a3610b08 894 $a->allocate = $numnonallocated;
3189fb2d 895 $task->details = get_string('allocatedetails', 'workshop', $a);
da0b1f70 896 unset($a);
3189fb2d 897 $phase->tasks['allocate'] = $task;
3dc78e5b
DM
898
899 if ($numofsubmissions < $numofauthors and $this->phase >= self::PHASE_SUBMISSION) {
900 $task = new stdClass();
901 $task->title = get_string('someuserswosubmission', 'workshop');
902 $task->completed = 'info';
903 $phase->tasks['allocateinfo'] = $task;
904 }
da0b1f70 905 }
81eccf0a 906 $phases[self::PHASE_SUBMISSION] = $phase;
b761e6d9
DM
907
908 // Prepare tasks for the peer-assessment phase (includes eventual self-assessments)
909 $phase = new stdClass();
910 $phase->title = get_string('phaseassessment', 'workshop');
911 $phase->tasks = array();
d895c6aa 912 $phase->isreviewer = has_capability('mod/workshop:peerassess', $this->context, $userid);
3dc78e5b 913 $phase->assessments = $this->get_assessments_by_reviewer($userid);
b761e6d9
DM
914 $numofpeers = 0; // number of allocated peer-assessments
915 $numofpeerstodo = 0; // number of peer-assessments to do
916 $numofself = 0; // number of allocated self-assessments - should be 0 or 1
917 $numofselftodo = 0; // number of self-assessments to do - should be 0 or 1
918 foreach ($phase->assessments as $a) {
919 if ($a->authorid == $userid) {
920 $numofself++;
921 if (is_null($a->grade)) {
922 $numofselftodo++;
923 }
924 } else {
925 $numofpeers++;
926 if (is_null($a->grade)) {
927 $numofpeerstodo++;
928 }
929 }
930 }
931 unset($a);
81eccf0a 932 if ($this->usepeerassessment and $numofpeers) {
b761e6d9 933 $task = new stdClass();
3dc78e5b
DM
934 if ($numofpeerstodo == 0) {
935 $task->completed = true;
936 } elseif ($this->phase > self::PHASE_ASSESSMENT) {
937 $task->completed = false;
938 }
b761e6d9
DM
939 $a = new stdClass();
940 $a->total = $numofpeers;
941 $a->todo = $numofpeerstodo;
942 $task->title = get_string('taskassesspeers', 'workshop');
da0b1f70 943 $task->details = get_string('taskassesspeersdetails', 'workshop', $a);
b761e6d9
DM
944 unset($a);
945 $phase->tasks['assesspeers'] = $task;
946 }
81eccf0a 947 if ($this->useselfassessment and $numofself) {
b761e6d9 948 $task = new stdClass();
3dc78e5b
DM
949 if ($numofselftodo == 0) {
950 $task->completed = true;
951 } elseif ($this->phase > self::PHASE_ASSESSMENT) {
952 $task->completed = false;
953 }
b761e6d9
DM
954 $task->title = get_string('taskassessself', 'workshop');
955 $phase->tasks['assessself'] = $task;
956 }
957 $phases[self::PHASE_ASSESSMENT] = $phase;
958
1fed6ce3 959 // Prepare tasks for the grading evaluation phase
b761e6d9
DM
960 $phase = new stdClass();
961 $phase->title = get_string('phaseevaluation', 'workshop');
962 $phase->tasks = array();
1fed6ce3 963 if (has_capability('mod/workshop:overridegrades', $this->context)) {
f27b70fb
DM
964 $expected = count($this->get_potential_authors(false));
965 $calculated = $DB->count_records_select('workshop_submissions',
966 'workshopid = ? AND (grade IS NOT NULL OR gradeover IS NOT NULL)', array($this->id));
1fed6ce3 967 $task = new stdClass();
10bc4bce 968 $task->title = get_string('calculatesubmissiongrades', 'workshop');
1fed6ce3
DM
969 $a = new stdClass();
970 $a->expected = $expected;
f27b70fb 971 $a->calculated = $calculated;
10bc4bce 972 $task->details = get_string('calculatesubmissiongradesdetails', 'workshop', $a);
f27b70fb 973 if ($calculated >= $expected) {
1fed6ce3
DM
974 $task->completed = true;
975 } elseif ($this->phase > self::PHASE_EVALUATION) {
976 $task->completed = false;
977 }
10bc4bce 978 $phase->tasks['calculatesubmissiongrade'] = $task;
f27b70fb
DM
979
980 $expected = count($this->get_potential_reviewers(false));
981 $calculated = $DB->count_records_select('workshop_aggregations',
982 'workshopid = ? AND gradinggrade IS NOT NULL', array($this->id));
983 $task = new stdClass();
984 $task->title = get_string('calculategradinggrades', 'workshop');
985 $a = new stdClass();
986 $a->expected = $expected;
987 $a->calculated = $calculated;
988 $task->details = get_string('calculategradinggradesdetails', 'workshop', $a);
989 if ($calculated >= $expected) {
990 $task->completed = true;
991 } elseif ($this->phase > self::PHASE_EVALUATION) {
992 $task->completed = false;
f55650e6 993 }
f27b70fb
DM
994 $phase->tasks['calculategradinggrade'] = $task;
995
d183140d 996 } elseif ($this->phase == self::PHASE_EVALUATION) {
1fed6ce3
DM
997 $task = new stdClass();
998 $task->title = get_string('evaluategradeswait', 'workshop');
999 $task->completed = 'info';
1000 $phase->tasks['evaluateinfo'] = $task;
1001 }
b761e6d9
DM
1002 $phases[self::PHASE_EVALUATION] = $phase;
1003
1004 // Prepare tasks for the "workshop closed" phase - todo
1005 $phase = new stdClass();
1006 $phase->title = get_string('phaseclosed', 'workshop');
1007 $phase->tasks = array();
1008 $phases[self::PHASE_CLOSED] = $phase;
1009
1010 // Polish data, set default values if not done explicitly
1011 foreach ($phases as $phasecode => $phase) {
1012 $phase->title = isset($phase->title) ? $phase->title : '';
1013 $phase->tasks = isset($phase->tasks) ? $phase->tasks : array();
1014 if ($phasecode == $this->phase) {
1015 $phase->active = true;
1016 } else {
1017 $phase->active = false;
1018 }
454e8dd9
DM
1019 if (!isset($phase->actions)) {
1020 $phase->actions = array();
1021 }
b761e6d9
DM
1022
1023 foreach ($phase->tasks as $taskcode => $task) {
1024 $task->title = isset($task->title) ? $task->title : '';
da0b1f70
DM
1025 $task->link = isset($task->link) ? $task->link : null;
1026 $task->details = isset($task->details) ? $task->details : '';
b761e6d9
DM
1027 $task->completed = isset($task->completed) ? $task->completed : null;
1028 }
1029 }
454e8dd9
DM
1030
1031 // Add phase swithing actions
d895c6aa 1032 if (has_capability('mod/workshop:switchphase', $this->context, $userid)) {
454e8dd9
DM
1033 foreach ($phases as $phasecode => $phase) {
1034 if (! $phase->active) {
1035 $action = new stdClass();
1036 $action->type = 'switchphase';
1037 $action->url = $this->switchphase_url($phasecode);
1038 $phase->actions[] = $action;
1039 }
1040 }
1041 }
1042
b761e6d9
DM
1043 return $phases;
1044 }
1045
454e8dd9
DM
1046 /**
1047 * Switch to a new workshop phase
1048 *
1049 * Modifies the underlying database record. You should terminate the script shortly after calling this.
1050 *
1051 * @param int $newphase new phase code
1052 * @return bool true if success, false otherwise
1053 */
1054 public function switch_phase($newphase) {
1055 global $DB;
1056
365c2cc2 1057 $known = $this->available_phases_list();
454e8dd9
DM
1058 if (!isset($known[$newphase])) {
1059 return false;
1060 }
f6e8b318
DM
1061
1062 if (self::PHASE_CLOSED == $newphase) {
f27b70fb 1063 // push the grades into the gradebook
10bc4bce
DM
1064 $workshop = new stdClass();
1065 foreach ($this as $property => $value) {
1066 $workshop->{$property} = $value;
1067 }
1068 $workshop->course = $this->course->id;
1069 $workshop->cmidnumber = $this->cm->id;
1070 $workshop->modname = 'workshop';
1071 workshop_update_grades($workshop);
f6e8b318
DM
1072 }
1073
454e8dd9
DM
1074 $DB->set_field('workshop', 'phase', $newphase, array('id' => $this->id));
1075 return true;
1076 }
ddb59c77
DM
1077
1078 /**
1079 * Saves a raw grade for submission as calculated from the assessment form fields
1080 *
1081 * @param array $assessmentid assessment record id, must exists
00aca3c1 1082 * @param mixed $grade raw percentual grade from 0.00000 to 100.00000
ddb59c77
DM
1083 * @return false|float the saved grade
1084 */
1085 public function set_peer_grade($assessmentid, $grade) {
1086 global $DB;
1087
1088 if (is_null($grade)) {
1089 return false;
1090 }
1091 $data = new stdClass();
1092 $data->id = $assessmentid;
1093 $data->grade = $grade;
1094 $DB->update_record('workshop_assessments', $data);
1095 return $grade;
1096 }
6516b9e9 1097
29dc43e7
DM
1098 /**
1099 * Prepares data object with all workshop grades to be rendered
1100 *
5e71cefb
DM
1101 * @param int $userid the user we are preparing the report for
1102 * @param mixed $groups single group or array of groups - only show users who are in one of these group(s). Defaults to all
29dc43e7 1103 * @param int $page the current page (for the pagination)
5e71cefb 1104 * @param int $perpage participants per page (for the pagination)
f27b70fb 1105 * @param string $sortby lastname|firstname|submissiontitle|submissiongrade|gradinggrade
5e71cefb 1106 * @param string $sorthow ASC|DESC
29dc43e7
DM
1107 * @return stdClass data for the renderer
1108 */
d895c6aa 1109 public function prepare_grading_report($userid, $groups, $page, $perpage, $sortby, $sorthow) {
29dc43e7
DM
1110 global $DB;
1111
d895c6aa
DM
1112 $canviewall = has_capability('mod/workshop:viewallassessments', $this->context, $userid);
1113 $isparticipant = has_any_capability(array('mod/workshop:submit', 'mod/workshop:peerassess'), $this->context, $userid);
29dc43e7
DM
1114
1115 if (!$canviewall and !$isparticipant) {
1116 // who the hell is this?
1117 return array();
1118 }
1119
f27b70fb 1120 if (!in_array($sortby, array('lastname','firstname','submissiontitle','submissiongrade','gradinggrade'))) {
5e71cefb
DM
1121 $sortby = 'lastname';
1122 }
1123
1124 if (!($sorthow === 'ASC' or $sorthow === 'DESC')) {
1125 $sorthow = 'ASC';
1126 }
1127
1128 // get the list of user ids to be displayed
29dc43e7
DM
1129 if ($canviewall) {
1130 // fetch the list of ids of all workshop participants - this may get really long so fetch just id
d895c6aa 1131 $participants = get_users_by_capability($this->context, array('mod/workshop:submit', 'mod/workshop:peerassess'),
5e71cefb 1132 'u.id', '', '', '', $groups, '', false, false, true);
29dc43e7
DM
1133 } else {
1134 // this is an ordinary workshop participant (aka student) - display the report just for him/her
1135 $participants = array($userid => (object)array('id' => $userid));
1136 }
1137
5e71cefb 1138 // we will need to know the number of all records later for the pagination purposes
29dc43e7
DM
1139 $numofparticipants = count($participants);
1140
d183140d 1141 // load all fields which can be used for sorting and paginate the records
5e71cefb 1142 list($participantids, $params) = $DB->get_in_or_equal(array_keys($participants), SQL_PARAMS_NAMED);
d183140d
DM
1143 $params['workshopid1'] = $this->id;
1144 $params['workshopid2'] = $this->id;
5e71cefb
DM
1145 $sqlsort = $sortby . ' ' . $sorthow . ',u.lastname,u.firstname,u.id';
1146 $sql = "SELECT u.id AS userid,u.firstname,u.lastname,u.picture,u.imagealt,
f27b70fb 1147 s.title AS submissiontitle, s.grade AS submissiongrade, ag.gradinggrade
5e71cefb 1148 FROM {user} u
d183140d
DM
1149 LEFT JOIN {workshop_submissions} s ON (s.authorid = u.id AND s.workshopid = :workshopid1 AND s.example = 0)
1150 LEFT JOIN {workshop_aggregations} ag ON (ag.userid = u.id AND ag.workshopid = :workshopid2)
1151 WHERE u.id $participantids
5e71cefb
DM
1152 ORDER BY $sqlsort";
1153 $participants = $DB->get_records_sql($sql, $params, $page * $perpage, $perpage);
29dc43e7
DM
1154
1155 // this will hold the information needed to display user names and pictures
5e71cefb
DM
1156 $userinfo = array();
1157
1158 // get the user details for all participants to display
1159 foreach ($participants as $participant) {
1160 if (!isset($userinfo[$participant->userid])) {
1161 $userinfo[$participant->userid] = new stdClass();
1162 $userinfo[$participant->userid]->id = $participant->userid;
1163 $userinfo[$participant->userid]->firstname = $participant->firstname;
1164 $userinfo[$participant->userid]->lastname = $participant->lastname;
1165 $userinfo[$participant->userid]->picture = $participant->picture;
1166 $userinfo[$participant->userid]->imagealt = $participant->imagealt;
1167 }
1168 }
29dc43e7 1169
5e71cefb 1170 // load the submissions details
29dc43e7 1171 $submissions = $this->get_submissions(array_keys($participants));
5e71cefb
DM
1172
1173 // get the user details for all moderators (teachers) that have overridden a submission grade
29dc43e7 1174 foreach ($submissions as $submission) {
29dc43e7
DM
1175 if (!isset($userinfo[$submission->gradeoverby])) {
1176 $userinfo[$submission->gradeoverby] = new stdClass();
1177 $userinfo[$submission->gradeoverby]->id = $submission->gradeoverby;
1178 $userinfo[$submission->gradeoverby]->firstname = $submission->overfirstname;
1179 $userinfo[$submission->gradeoverby]->lastname = $submission->overlastname;
1180 $userinfo[$submission->gradeoverby]->picture = $submission->overpicture;
1181 $userinfo[$submission->gradeoverby]->imagealt = $submission->overimagealt;
1182 }
1183 }
1184
5e71cefb 1185 // get the user details for all reviewers of the displayed participants
29dc43e7
DM
1186 $reviewers = array();
1187 if ($submissions) {
1188 list($submissionids, $params) = $DB->get_in_or_equal(array_keys($submissions), SQL_PARAMS_NAMED);
1189 $sql = "SELECT a.id AS assessmentid, a.submissionid, a.grade, a.gradinggrade, a.gradinggradeover,
1190 r.id AS reviewerid, r.lastname, r.firstname, r.picture, r.imagealt,
1191 s.id AS submissionid, s.authorid
1192 FROM {workshop_assessments} a
1193 JOIN {user} r ON (a.reviewerid = r.id)
1194 JOIN {workshop_submissions} s ON (a.submissionid = s.id)
1195 WHERE a.submissionid $submissionids";
1196 $reviewers = $DB->get_records_sql($sql, $params);
1197 foreach ($reviewers as $reviewer) {
1198 if (!isset($userinfo[$reviewer->reviewerid])) {
1199 $userinfo[$reviewer->reviewerid] = new stdClass();
1200 $userinfo[$reviewer->reviewerid]->id = $reviewer->reviewerid;
1201 $userinfo[$reviewer->reviewerid]->firstname = $reviewer->firstname;
1202 $userinfo[$reviewer->reviewerid]->lastname = $reviewer->lastname;
1203 $userinfo[$reviewer->reviewerid]->picture = $reviewer->picture;
1204 $userinfo[$reviewer->reviewerid]->imagealt = $reviewer->imagealt;
1205 }
1206 }
1207 }
1208
5e71cefb 1209 // get the user details for all reviewees of the displayed participants
934329e5
DM
1210 $reviewees = array();
1211 if ($participants) {
1212 list($participantids, $params) = $DB->get_in_or_equal(array_keys($participants), SQL_PARAMS_NAMED);
1213 $params['workshopid'] = $this->id;
d183140d 1214 $sql = "SELECT a.id AS assessmentid, a.submissionid, a.grade, a.gradinggrade, a.gradinggradeover, a.reviewerid,
934329e5
DM
1215 s.id AS submissionid,
1216 e.id AS authorid, e.lastname, e.firstname, e.picture, e.imagealt
1217 FROM {user} u
1218 JOIN {workshop_assessments} a ON (a.reviewerid = u.id)
1219 JOIN {workshop_submissions} s ON (a.submissionid = s.id)
1220 JOIN {user} e ON (s.authorid = e.id)
1221 WHERE u.id $participantids AND s.workshopid = :workshopid";
1222 $reviewees = $DB->get_records_sql($sql, $params);
1223 foreach ($reviewees as $reviewee) {
1224 if (!isset($userinfo[$reviewee->authorid])) {
1225 $userinfo[$reviewee->authorid] = new stdClass();
1226 $userinfo[$reviewee->authorid]->id = $reviewee->authorid;
1227 $userinfo[$reviewee->authorid]->firstname = $reviewee->firstname;
1228 $userinfo[$reviewee->authorid]->lastname = $reviewee->lastname;
1229 $userinfo[$reviewee->authorid]->picture = $reviewee->picture;
1230 $userinfo[$reviewee->authorid]->imagealt = $reviewee->imagealt;
1231 }
29dc43e7
DM
1232 }
1233 }
1234
5e71cefb
DM
1235 // finally populate the object to be rendered
1236 $grades = $participants;
29dc43e7
DM
1237
1238 foreach ($participants as $participant) {
1239 // set up default (null) values
d183140d
DM
1240 $grades[$participant->userid]->submissionid = null;
1241 $grades[$participant->userid]->submissiontitle = null;
1242 $grades[$participant->userid]->submissiongrade = null;
1243 $grades[$participant->userid]->submissiongradeover = null;
1244 $grades[$participant->userid]->submissiongradeoverby = null;
5e71cefb
DM
1245 $grades[$participant->userid]->reviewedby = array();
1246 $grades[$participant->userid]->reviewerof = array();
29dc43e7
DM
1247 }
1248 unset($participants);
1249 unset($participant);
1250
1251 foreach ($submissions as $submission) {
1252 $grades[$submission->authorid]->submissionid = $submission->id;
1253 $grades[$submission->authorid]->submissiontitle = $submission->title;
b4857acb
DM
1254 $grades[$submission->authorid]->submissiongrade = $this->real_grade($submission->grade);
1255 $grades[$submission->authorid]->submissiongradeover = $this->real_grade($submission->gradeover);
29dc43e7
DM
1256 $grades[$submission->authorid]->submissiongradeoverby = $submission->gradeoverby;
1257 }
1258 unset($submissions);
1259 unset($submission);
1260
1261 foreach($reviewers as $reviewer) {
1262 $info = new stdClass();
1263 $info->userid = $reviewer->reviewerid;
1264 $info->assessmentid = $reviewer->assessmentid;
1265 $info->submissionid = $reviewer->submissionid;
b4857acb
DM
1266 $info->grade = $this->real_grade($reviewer->grade);
1267 $info->gradinggrade = $this->real_grading_grade($reviewer->gradinggrade);
1268 $info->gradinggradeover = $this->real_grading_grade($reviewer->gradinggradeover);
29dc43e7
DM
1269 $grades[$reviewer->authorid]->reviewedby[$reviewer->reviewerid] = $info;
1270 }
1271 unset($reviewers);
1272 unset($reviewer);
1273
1274 foreach($reviewees as $reviewee) {
1275 $info = new stdClass();
1276 $info->userid = $reviewee->authorid;
1277 $info->assessmentid = $reviewee->assessmentid;
1278 $info->submissionid = $reviewee->submissionid;
b4857acb
DM
1279 $info->grade = $this->real_grade($reviewee->grade);
1280 $info->gradinggrade = $this->real_grading_grade($reviewee->gradinggrade);
1281 $info->gradinggradeover = $this->real_grading_grade($reviewee->gradinggradeover);
29dc43e7
DM
1282 $grades[$reviewee->reviewerid]->reviewerof[$reviewee->authorid] = $info;
1283 }
1284 unset($reviewees);
1285 unset($reviewee);
1286
b4857acb
DM
1287 foreach ($grades as $grade) {
1288 $grade->gradinggrade = $this->real_grading_grade($grade->gradinggrade);
b4857acb
DM
1289 }
1290
29dc43e7
DM
1291 $data = new stdClass();
1292 $data->grades = $grades;
1293 $data->userinfo = $userinfo;
1294 $data->totalcount = $numofparticipants;
b4857acb
DM
1295 $data->maxgrade = $this->real_grade(100);
1296 $data->maxgradinggrade = $this->real_grading_grade(100);
29dc43e7
DM
1297 return $data;
1298 }
1299
29dc43e7 1300 /**
b4857acb 1301 * Calculates the real value of a grade
29dc43e7 1302 *
b4857acb
DM
1303 * @param float $value percentual value from 0 to 100
1304 * @param float $max the maximal grade
1305 * @return string
1306 */
1307 public function real_grade_value($value, $max) {
1308 $localized = true;
557a1100 1309 if (is_null($value) or $value === '') {
b4857acb
DM
1310 return null;
1311 } elseif ($max == 0) {
1312 return 0;
1313 } else {
1314 return format_float($max * $value / 100, $this->gradedecimals, $localized);
1315 }
1316 }
1317
e554671d
DM
1318 /**
1319 * Calculates the raw (percentual) value from a real grade
1320 *
1321 * This is used in cases when a user wants to give a grade such as 12 of 20 and we need to save
1322 * this value in a raw percentual form into DB
1323 * @param float $value given grade
1324 * @param float $max the maximal grade
1325 * @return float suitable to be stored as numeric(10,5)
1326 */
1327 public function raw_grade_value($value, $max) {
557a1100 1328 if (is_null($value) or $value === '') {
e554671d
DM
1329 return null;
1330 }
1331 if ($max == 0 or $value < 0) {
1332 return 0;
1333 }
1334 $p = $value / $max * 100;
1335 if ($p > 100) {
1336 return $max;
1337 }
1338 return grade_floatval($p);
1339 }
1340
b4857acb
DM
1341 /**
1342 * Calculates the real value of grade for submission
1343 *
1344 * @param float $value percentual value from 0 to 100
1345 * @return string
1346 */
1347 public function real_grade($value) {
1348 return $this->real_grade_value($value, $this->grade);
1349 }
1350
1351 /**
1352 * Calculates the real value of grade for assessment
1353 *
1354 * @param float $value percentual value from 0 to 100
1355 * @return string
1356 */
1357 public function real_grading_grade($value) {
1358 return $this->real_grade_value($value, $this->gradinggrade);
29dc43e7
DM
1359 }
1360
89c1aa97 1361 /**
e9a90e69 1362 * Calculates grades for submission for the given participant(s) and updates it in the database
89c1aa97
DM
1363 *
1364 * @param null|int|array $restrict If null, update all authors, otherwise update just grades for the given author(s)
1365 * @return void
1366 */
8a1ba8ac 1367 public function aggregate_submission_grades($restrict=null) {
89c1aa97
DM
1368 global $DB;
1369
1370 // fetch a recordset with all assessments to process
1696f36c 1371 $sql = 'SELECT s.id AS submissionid, s.grade AS submissiongrade,
89c1aa97
DM
1372 a.weight, a.grade
1373 FROM {workshop_submissions} s
1374 LEFT JOIN {workshop_assessments} a ON (a.submissionid = s.id)
1375 WHERE s.example=0 AND s.workshopid=:workshopid'; // to be cont.
1376 $params = array('workshopid' => $this->id);
1377
1378 if (is_null($restrict)) {
1379 // update all users - no more conditions
1380 } elseif (!empty($restrict)) {
1381 list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED);
1382 $sql .= " AND s.authorid $usql";
1383 $params = array_merge($params, $uparams);
1384 } else {
1385 throw new coding_exception('Empty value is not a valid parameter here');
1386 }
1387
1388 $sql .= ' ORDER BY s.id'; // this is important for bulk processing
89c1aa97 1389
e9a90e69
DM
1390 $rs = $DB->get_recordset_sql($sql, $params);
1391 $batch = array(); // will contain a set of all assessments of a single submission
1392 $previous = null; // a previous record in the recordset
1393
89c1aa97
DM
1394 foreach ($rs as $current) {
1395 if (is_null($previous)) {
1396 // we are processing the very first record in the recordset
1397 $previous = $current;
89c1aa97 1398 }
e9a90e69 1399 if ($current->submissionid == $previous->submissionid) {
89c1aa97 1400 // we are still processing the current submission
e9a90e69
DM
1401 $batch[] = $current;
1402 } else {
1403 // process all the assessments of a sigle submission
1404 $this->aggregate_submission_grades_process($batch);
1405 // and then start to process another submission
1406 $batch = array($current);
1407 $previous = $current;
89c1aa97
DM
1408 }
1409 }
e9a90e69
DM
1410 // do not forget to process the last batch!
1411 $this->aggregate_submission_grades_process($batch);
89c1aa97
DM
1412 $rs->close();
1413 }
1414
1415 /**
1416 * Calculates grades for assessment for the given participant(s)
1417 *
39411930
DM
1418 * Grade for assessment is calculated as a simple mean of all grading grades calculated by the grading evaluator.
1419 * The assessment weight is not taken into account here.
89c1aa97
DM
1420 *
1421 * @param null|int|array $restrict If null, update all reviewers, otherwise update just grades for the given reviewer(s)
1422 * @return void
1423 */
8a1ba8ac 1424 public function aggregate_grading_grades($restrict=null) {
89c1aa97
DM
1425 global $DB;
1426
39411930
DM
1427 // fetch a recordset with all assessments to process
1428 $sql = 'SELECT a.reviewerid, a.gradinggrade, a.gradinggradeover,
1429 ag.id AS aggregationid, ag.gradinggrade AS aggregatedgrade
1430 FROM {workshop_assessments} a
1431 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
1432 LEFT JOIN {workshop_aggregations} ag ON (ag.userid = a.reviewerid AND ag.workshopid = s.workshopid)
1433 WHERE s.example=0 AND s.workshopid=:workshopid'; // to be cont.
1434 $params = array('workshopid' => $this->id);
1435
1436 if (is_null($restrict)) {
1437 // update all users - no more conditions
1438 } elseif (!empty($restrict)) {
1439 list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED);
1440 $sql .= " AND a.reviewerid $usql";
1441 $params = array_merge($params, $uparams);
1442 } else {
1443 throw new coding_exception('Empty value is not a valid parameter here');
1444 }
1445
1446 $sql .= ' ORDER BY a.reviewerid'; // this is important for bulk processing
1447
1448 $rs = $DB->get_recordset_sql($sql, $params);
1449 $batch = array(); // will contain a set of all assessments of a single submission
1450 $previous = null; // a previous record in the recordset
1451
1452 foreach ($rs as $current) {
1453 if (is_null($previous)) {
1454 // we are processing the very first record in the recordset
1455 $previous = $current;
1456 }
1457 if ($current->reviewerid == $previous->reviewerid) {
1458 // we are still processing the current reviewer
1459 $batch[] = $current;
1460 } else {
1461 // process all the assessments of a sigle submission
1462 $this->aggregate_grading_grades_process($batch);
1463 // and then start to process another reviewer
1464 $batch = array($current);
1465 $previous = $current;
1466 }
1467 }
1468 // do not forget to process the last batch!
1469 $this->aggregate_grading_grades_process($batch);
1470 $rs->close();
89c1aa97
DM
1471 }
1472
77f43e7d 1473 /**
f6e8b318 1474 * Returns the mform the teachers use to put a feedback for the reviewer
77f43e7d 1475 *
f6e8b318 1476 * @return workshop_feedbackreviewer_form
77f43e7d 1477 */
e554671d 1478 public function get_feedbackreviewer_form(moodle_url $actionurl, stdClass $assessment, $editable=true) {
77f43e7d
DM
1479 global $CFG;
1480 require_once(dirname(__FILE__) . '/feedbackreviewer_form.php');
1481
e554671d
DM
1482 $current = new stdClass();
1483 $current->asid = $assessment->id;
1484 $current->gradinggrade = $this->real_grading_grade($assessment->gradinggrade);
1485 $current->gradinggradeover = $this->real_grading_grade($assessment->gradinggradeover);
1486 $current->feedbackreviewer = $assessment->feedbackreviewer;
1487 $current->feedbackreviewerformat = $assessment->feedbackreviewerformat;
1488 if (is_null($current->gradinggrade)) {
1489 $current->gradinggrade = get_string('nullgrade', 'workshop');
1490 }
1491
1492 // prepare wysiwyg editor
1493 $current = file_prepare_standard_editor($current, 'feedbackreviewer', array());
1494
77f43e7d 1495 return new workshop_feedbackreviewer_form($actionurl,
e554671d 1496 array('workshop' => $this, 'current' => $current, 'feedbackopts' => array()),
77f43e7d
DM
1497 'post', '', null, $editable);
1498 }
1499
67cd00ba
DM
1500 /**
1501 * Returns the mform the teachers use to put a feedback for the author on their submission
1502 *
1503 * @return workshop_feedbackauthor_form
1504 */
1505 public function get_feedbackauthor_form(moodle_url $actionurl, stdClass $submission, $editable=true) {
1506 global $CFG;
1507 require_once(dirname(__FILE__) . '/feedbackauthor_form.php');
1508
1509 $current = new stdClass();
1510 $current->submissionid = $submission->id;
557a1100
DM
1511 $current->grade = $this->real_grade($submission->grade);
1512 $current->gradeover = $this->real_grade($submission->gradeover);
1513 $current->feedbackauthor = $submission->feedbackauthor;
1514 $current->feedbackauthorformat = $submission->feedbackauthorformat;
67cd00ba
DM
1515 if (is_null($current->grade)) {
1516 $current->grade = get_string('nullgrade', 'workshop');
1517 }
1518
1519 // prepare wysiwyg editor
1520 $current = file_prepare_standard_editor($current, 'feedbackauthor', array());
1521
1522 return new workshop_feedbackauthor_form($actionurl,
1523 array('workshop' => $this, 'current' => $current, 'feedbackopts' => array()),
1524 'post', '', null, $editable);
1525 }
1526
aa40adbf
DM
1527 ////////////////////////////////////////////////////////////////////////////////
1528 // Internal methods (implementation details) //
1529 ////////////////////////////////////////////////////////////////////////////////
6516b9e9 1530
e9a90e69
DM
1531 /**
1532 * Given an array of all assessments of a single submission, calculates the final grade for this submission
1533 *
1534 * This calculates the weighted mean of the passed assessment grades. If, however, the submission grade
1535 * was overridden by a teacher, the gradeover value is returned and the rest of grades are ignored.
1536 *
1537 * @param array $assessments of stdClass(->submissionid ->submissiongrade ->gradeover ->weight ->grade)
1fed6ce3 1538 * @return void
e9a90e69
DM
1539 */
1540 protected function aggregate_submission_grades_process(array $assessments) {
1541 global $DB;
1542
1543 $submissionid = null; // the id of the submission being processed
1544 $current = null; // the grade currently saved in database
1545 $finalgrade = null; // the new grade to be calculated
1546 $sumgrades = 0;
1547 $sumweights = 0;
1548
1549 foreach ($assessments as $assessment) {
1550 if (is_null($submissionid)) {
1551 // the id is the same in all records, fetch it during the first loop cycle
1552 $submissionid = $assessment->submissionid;
1553 }
1554 if (is_null($current)) {
1555 // the currently saved grade is the same in all records, fetch it during the first loop cycle
1556 $current = $assessment->submissiongrade;
1557 }
e9a90e69
DM
1558 if (is_null($assessment->grade)) {
1559 // this was not assessed yet
1560 continue;
1561 }
1562 if ($assessment->weight == 0) {
1563 // this does not influence the calculation
1564 continue;
1565 }
1566 $sumgrades += $assessment->grade * $assessment->weight;
1567 $sumweights += $assessment->weight;
1568 }
1569 if ($sumweights > 0 and is_null($finalgrade)) {
1570 $finalgrade = grade_floatval($sumgrades / $sumweights);
1571 }
1572 // check if the new final grade differs from the one stored in the database
1573 if (grade_floats_different($finalgrade, $current)) {
1574 // we need to save new calculation into the database
10bc4bce
DM
1575 $record = new stdClass();
1576 $record->id = $submissionid;
1577 $record->grade = $finalgrade;
1578 $record->timegraded = time();
1579 $DB->update_record('workshop_submissions', $record);
e9a90e69
DM
1580 }
1581 }
1582
39411930
DM
1583 /**
1584 * Given an array of all assessments done by a single reviewer, calculates the final grading grade
1585 *
1586 * This calculates the simple mean of the passed grading grades. If, however, the grading grade
1587 * was overridden by a teacher, the gradinggradeover value is returned and the rest of grades are ignored.
1588 *
1589 * @param array $assessments of stdClass(->reviewerid ->gradinggrade ->gradinggradeover ->aggregationid ->aggregatedgrade)
1fed6ce3 1590 * @return void
39411930
DM
1591 */
1592 protected function aggregate_grading_grades_process(array $assessments) {
1593 global $DB;
1594
1595 $reviewerid = null; // the id of the reviewer being processed
1596 $current = null; // the gradinggrade currently saved in database
1597 $finalgrade = null; // the new grade to be calculated
1598 $agid = null; // aggregation id
1599 $sumgrades = 0;
1600 $count = 0;
1601
1602 foreach ($assessments as $assessment) {
1603 if (is_null($reviewerid)) {
1604 // the id is the same in all records, fetch it during the first loop cycle
1605 $reviewerid = $assessment->reviewerid;
1606 }
1607 if (is_null($agid)) {
1608 // the id is the same in all records, fetch it during the first loop cycle
1609 $agid = $assessment->aggregationid;
1610 }
1611 if (is_null($current)) {
1612 // the currently saved grade is the same in all records, fetch it during the first loop cycle
1613 $current = $assessment->aggregatedgrade;
1614 }
1615 if (!is_null($assessment->gradinggradeover)) {
1616 // the grading grade for this assessment is overriden by a teacher
1617 $sumgrades += $assessment->gradinggradeover;
1618 $count++;
1619 } else {
1620 if (!is_null($assessment->gradinggrade)) {
1621 $sumgrades += $assessment->gradinggrade;
1622 $count++;
1623 }
1624 }
1625 }
1626 if ($count > 0) {
1627 $finalgrade = grade_floatval($sumgrades / $count);
1628 }
1629 // check if the new final grade differs from the one stored in the database
1630 if (grade_floats_different($finalgrade, $current)) {
1631 // we need to save new calculation into the database
1632 if (is_null($agid)) {
1633 // no aggregation record yet
1634 $record = new stdClass();
1635 $record->workshopid = $this->id;
1636 $record->userid = $reviewerid;
1637 $record->gradinggrade = $finalgrade;
10bc4bce 1638 $record->timegraded = time();
39411930
DM
1639 $DB->insert_record('workshop_aggregations', $record);
1640 } else {
10bc4bce
DM
1641 $record = new stdClass();
1642 $record->id = $agid;
1643 $record->gradinggrade = $finalgrade;
1644 $record->timegraded = time();
1645 $DB->update_record('workshop_aggregations', $record);
39411930
DM
1646 }
1647 }
1648 }
1649
6516b9e9 1650 /**
aa40adbf 1651 * Given a list of user ids, returns the filtered one containing just ids of users with own submission
6516b9e9 1652 *
aa40adbf
DM
1653 * Example submissions are ignored.
1654 *
1655 * @param array $userids
6516b9e9
DM
1656 * @return array
1657 */
aa40adbf
DM
1658 protected function users_with_submission(array $userids) {
1659 global $DB;
1660
1661 if (empty($userids)) {
1662 return array();
1663 }
1664 $userswithsubmission = array();
1665 list($usql, $uparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
00aca3c1 1666 $sql = "SELECT id,authorid
aa40adbf 1667 FROM {workshop_submissions}
00aca3c1 1668 WHERE example = 0 AND workshopid = :workshopid AND authorid $usql";
aa40adbf
DM
1669 $params = array('workshopid' => $this->id);
1670 $params = array_merge($params, $uparams);
1671 $submissions = $DB->get_records_sql($sql, $params);
1672 foreach ($submissions as $submission) {
00aca3c1 1673 $userswithsubmission[$submission->authorid] = true;
aa40adbf
DM
1674 }
1675
1676 return $userswithsubmission;
6516b9e9
DM
1677 }
1678
aa40adbf
DM
1679 /**
1680 * @return array of available workshop phases
1681 */
365c2cc2 1682 protected function available_phases_list() {
aa40adbf
DM
1683 return array(
1684 self::PHASE_SETUP => true,
1685 self::PHASE_SUBMISSION => true,
1686 self::PHASE_ASSESSMENT => true,
1687 self::PHASE_EVALUATION => true,
1688 self::PHASE_CLOSED => true,
1689 );
1690 }
1691
66c9894d 1692}