workshop: english strings for workshop capabilities
[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 */
cbf87967
DM
46 const ALLOCATION_EXISTS = -9999;
47 const ALLOCATION_ERROR = -9998;
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 full record of the given example submission
426 *
427 * @param int $id example submission od
428 * @return object
429 */
430 public function get_example_by_id($id) {
431 global $DB;
432 return $DB->get_record('workshop_submissions',
433 array('id' => $id, 'workshopid' => $this->id, 'example' => 1), '*', MUST_EXIST);
434 }
435
cbf87967
DM
436 /**
437 * Returns the list of example submissions in this workshop with reference assessments attached
438 *
439 * @return array of objects or an empty array
440 * @see workshop::prepare_example_summary()
441 */
442 public function get_examples_for_manager() {
443 global $DB;
444
445 $sql = 'SELECT s.id, s.title,
446 a.id AS assessmentid, a.weight, a.grade, a.gradinggrade
447 FROM {workshop_submissions} s
448 LEFT JOIN {workshop_assessments} a ON (a.submissionid = s.id AND a.weight = 1)
449 WHERE s.example = 1 AND s.workshopid = :workshopid
450 ORDER BY s.title';
451 return $DB->get_records_sql($sql, array('workshopid' => $this->id));
452 }
453
454 /**
455 * Returns the list of all example submissions in this workshop with the information of assessments done by the given user
456 *
457 * @param int $reviewerid user id
458 * @return array of objects, indexed by example submission id
459 * @see workshop::prepare_example_summary()
460 */
461 public function get_examples_for_reviewer($reviewerid) {
462 global $DB;
463
464 if (empty($reviewerid)) {
465 return false;
466 }
467 $sql = 'SELECT s.id, s.title,
468 a.id AS assessmentid, a.weight, a.grade, a.gradinggrade
469 FROM {workshop_submissions} s
470 LEFT JOIN {workshop_assessments} a ON (a.submissionid = s.id AND a.reviewerid = :reviewerid AND a.weight = 0)
471 WHERE s.example = 1 AND s.workshopid = :workshopid
472 ORDER BY s.title';
473 return $DB->get_records_sql($sql, array('workshopid' => $this->id, 'reviewerid' => $reviewerid));
474 }
475
476 /**
477 * Prepares component containing summary of given example to be rendered
478 *
479 * @param stdClass $example as returned by {@link workshop::get_examples_for_manager()} or {@link workshop::get_examples_for_reviewer()}
480 * @return stdClass component to be rendered
481 */
482 public function prepare_example_summary(stdClass $example) {
483
484 $summary = new stdClass();
485 $summary->example = $example;
486 if (is_null($example->grade)) {
487 $summary->status = 'notgraded';
488 $buttontext = get_string('assess', 'workshop');
489 } else {
490 $summary->status = 'graded';
491 $buttontext = get_string('reassess', 'workshop');
492 }
493
494 $summary->gradeinfo = new stdClass();
495 $summary->gradeinfo->received = $this->real_grade($example->grade);
496 $summary->gradeinfo->max = $this->real_grade(100);
497
498 $summary->btnform = new html_form();
499 $summary->btnform->method = 'get';
500 $summary->btnform->button->text = $buttontext;
501 $summary->btnform->url = new moodle_url($this->exsubmission_url($example->id), array('assess' => 'on', 'sesskey' => sesskey()));
502
503 return $summary;
504 }
505
81eccf0a
DM
506 /**
507 * Removes the submission and all relevant data
508 *
509 * @param stdClass $submission record to delete
510 * @return void
511 */
512 public function delete_submission(stdClass $submission) {
513 global $DB;
514 $assessments = $DB->get_records('workshop_assessments', array('submissionid' => $submission->id), '', 'id');
515 $this->delete_assessment(array_keys($assessments));
516 $DB->delete_records('workshop_submissions', array('id' => $submission->id));
517 }
518
6e309973 519 /**
3dc78e5b 520 * Returns the list of all assessments in the workshop with some data added
6e309973
DM
521 *
522 * Fetches data from {workshop_assessments} and adds some useful information from other
3dc78e5b
DM
523 * tables. The returned object does not contain textual fields (ie comments) to prevent memory
524 * lack issues.
525 *
526 * @return array [assessmentid] => assessment stdClass
6e309973 527 */
3dc78e5b 528 public function get_all_assessments() {
6e309973 529 global $DB;
53fad4b9 530
f6e8b318 531 $sql = 'SELECT a.id, a.submissionid, a.reviewerid, a.timecreated, a.timemodified,
3dc78e5b 532 a.grade, a.gradinggrade, a.gradinggradeover, a.gradinggradeoverby,
3d2924e9
DM
533 reviewer.id AS reviewerid,reviewer.firstname AS reviewerfirstname,reviewer.lastname as reviewerlastname,
534 s.title,
ddb59c77 535 author.id AS authorid, author.firstname AS authorfirstname,author.lastname AS authorlastname
3d2924e9 536 FROM {workshop_assessments} a
00aca3c1 537 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id)
3d2924e9 538 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
00aca3c1 539 INNER JOIN {user} author ON (s.authorid = author.id)
3dc78e5b
DM
540 WHERE s.workshopid = :workshopid AND s.example = 0
541 ORDER BY reviewer.lastname, reviewer.firstname';
3d2924e9
DM
542 $params = array('workshopid' => $this->id);
543
3dc78e5b 544 return $DB->get_records_sql($sql, $params);
53fad4b9
DM
545 }
546
547 /**
3dc78e5b 548 * Get the complete information about the given assessment
53fad4b9
DM
549 *
550 * @param int $id Assessment ID
65ba104c 551 * @return mixed false if not found, stdClass otherwise
53fad4b9
DM
552 */
553 public function get_assessment_by_id($id) {
3dc78e5b
DM
554 global $DB;
555
556 $sql = 'SELECT a.*,
557 reviewer.id AS reviewerid,reviewer.firstname AS reviewerfirstname,reviewer.lastname as reviewerlastname,
558 s.title,
559 author.id AS authorid, author.firstname AS authorfirstname,author.lastname as authorlastname
560 FROM {workshop_assessments} a
00aca3c1 561 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id)
3dc78e5b 562 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
00aca3c1 563 INNER JOIN {user} author ON (s.authorid = author.id)
3dc78e5b
DM
564 WHERE a.id = :id AND s.workshopid = :workshopid';
565 $params = array('id' => $id, 'workshopid' => $this->id);
566
567 return $DB->get_record_sql($sql, $params, MUST_EXIST);
53fad4b9
DM
568 }
569
570 /**
3dc78e5b 571 * Get the complete information about all assessments allocated to the given reviewer
53fad4b9 572 *
00aca3c1 573 * @param int $reviewerid
3dc78e5b 574 * @return array
53fad4b9 575 */
00aca3c1 576 public function get_assessments_by_reviewer($reviewerid) {
3dc78e5b
DM
577 global $DB;
578
579 $sql = 'SELECT a.*,
ddb59c77
DM
580 reviewer.id AS reviewerid,reviewer.firstname AS reviewerfirstname,reviewer.lastname AS reviewerlastname,
581 s.id AS submissionid, s.title AS submissiontitle, s.timecreated AS submissioncreated,
582 s.timemodified AS submissionmodified,
583 author.id AS authorid, author.firstname AS authorfirstname,author.lastname AS authorlastname,
584 author.picture AS authorpicture, author.imagealt AS authorimagealt
3dc78e5b 585 FROM {workshop_assessments} a
00aca3c1 586 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id)
3dc78e5b 587 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
00aca3c1
DM
588 INNER JOIN {user} author ON (s.authorid = author.id)
589 WHERE s.example = 0 AND reviewer.id = :reviewerid AND s.workshopid = :workshopid';
590 $params = array('reviewerid' => $reviewerid, 'workshopid' => $this->id);
3dc78e5b
DM
591
592 return $DB->get_records_sql($sql, $params);
53fad4b9 593 }
6e309973 594
6e309973
DM
595 /**
596 * Allocate a submission to a user for review
53fad4b9 597 *
cbf87967 598 * @param stdClass $submission Submission object with at least id property
6e309973 599 * @param int $reviewerid User ID
53fad4b9 600 * @param bool $bulk repeated inserts into DB expected
becec954 601 * @param int $weight of the new assessment, from 0 to 16
6e309973
DM
602 * @return int ID of the new assessment or an error code
603 */
becec954 604 public function add_allocation(stdClass $submission, $reviewerid, $bulk=false, $weight=1) {
6e309973
DM
605 global $DB;
606
00aca3c1 607 if ($DB->record_exists('workshop_assessments', array('submissionid' => $submission->id, 'reviewerid' => $reviewerid))) {
b761e6d9 608 return self::ALLOCATION_EXISTS;
6e309973
DM
609 }
610
6e309973 611 $now = time();
65ba104c 612 $assessment = new stdClass();
e554671d
DM
613 $assessment->submissionid = $submission->id;
614 $assessment->reviewerid = $reviewerid;
615 $assessment->timecreated = $now;
616 $assessment->timemodified = $now;
becec954 617 $assessment->weight = $weight;
e554671d
DM
618 $assessment->generalcommentformat = FORMAT_HTML; // todo better default handling
619 $assessment->feedbackreviewerformat = FORMAT_HTML; // todo better default handling
6e309973 620
235b31c8 621 return $DB->insert_record('workshop_assessments', $assessment, true, $bulk);
6e309973
DM
622 }
623
6e309973 624 /**
53fad4b9 625 * Delete assessment record or records
6e309973 626 *
53fad4b9
DM
627 * @param mixed $id int|array assessment id or array of assessments ids
628 * @return bool false if $id not a valid parameter, true otherwise
6e309973
DM
629 */
630 public function delete_assessment($id) {
631 global $DB;
632
633 // todo remove all given grades from workshop_grades;
6e309973 634
53fad4b9 635 if (is_array($id)) {
235b31c8 636 return $DB->delete_records_list('workshop_assessments', 'id', $id);
3d2924e9 637 } else {
235b31c8 638 return $DB->delete_records('workshop_assessments', array('id' => $id));
53fad4b9 639 }
53fad4b9 640 }
6e309973
DM
641
642 /**
643 * Returns instance of grading strategy class
53fad4b9 644 *
65ba104c 645 * @return stdClass Instance of a grading strategy
6e309973
DM
646 */
647 public function grading_strategy_instance() {
3d2924e9
DM
648 global $CFG; // because we require other libs here
649
3fd2b0e1 650 if (is_null($this->strategyinstance)) {
f05c168d 651 $strategylib = dirname(__FILE__) . '/form/' . $this->strategy . '/lib.php';
6e309973
DM
652 if (is_readable($strategylib)) {
653 require_once($strategylib);
654 } else {
f05c168d 655 throw new coding_exception('the grading forms subplugin must contain library ' . $strategylib);
6e309973 656 }
0dc47fb9 657 $classname = 'workshop_' . $this->strategy . '_strategy';
3fd2b0e1
DM
658 $this->strategyinstance = new $classname($this);
659 if (!in_array('workshop_strategy', class_implements($this->strategyinstance))) {
b761e6d9 660 throw new coding_exception($classname . ' does not implement workshop_strategy interface');
6e309973
DM
661 }
662 }
3fd2b0e1 663 return $this->strategyinstance;
6e309973
DM
664 }
665
45d24d39
DM
666 /**
667 * Returns instance of grading evaluation class
668 *
669 * @return stdClass Instance of a grading evaluation
670 */
671 public function grading_evaluation_instance() {
672 global $CFG; // because we require other libs here
673
674 if (is_null($this->evaluationinstance)) {
675 $evaluationlib = dirname(__FILE__) . '/eval/' . $this->evaluation . '/lib.php';
676 if (is_readable($evaluationlib)) {
677 require_once($evaluationlib);
678 } else {
679 throw new coding_exception('the grading evaluation subplugin must contain library ' . $evaluationlib);
680 }
681 $classname = 'workshop_' . $this->evaluation . '_evaluation';
682 $this->evaluationinstance = new $classname($this);
683 if (!in_array('workshop_evaluation', class_implements($this->evaluationinstance))) {
684 throw new coding_exception($classname . ' does not implement workshop_evaluation interface');
685 }
686 }
687 return $this->evaluationinstance;
688 }
689
66c9894d
DM
690 /**
691 * Returns instance of submissions allocator
53fad4b9 692 *
130ae619 693 * @param string $method The name of the allocation method, must be PARAM_ALPHA
65ba104c 694 * @return stdClass Instance of submissions allocator
66c9894d
DM
695 */
696 public function allocator_instance($method) {
3d2924e9
DM
697 global $CFG; // because we require other libs here
698
f05c168d 699 $allocationlib = dirname(__FILE__) . '/allocation/' . $method . '/lib.php';
66c9894d
DM
700 if (is_readable($allocationlib)) {
701 require_once($allocationlib);
702 } else {
f05c168d 703 throw new coding_exception('Unable to find the allocation library ' . $allocationlib);
66c9894d
DM
704 }
705 $classname = 'workshop_' . $method . '_allocator';
706 return new $classname($this);
707 }
708
b8ead2e6 709 /**
454e8dd9 710 * @return moodle_url of this workshop's view page
b8ead2e6
DM
711 */
712 public function view_url() {
713 global $CFG;
714 return new moodle_url($CFG->wwwroot . '/mod/workshop/view.php', array('id' => $this->cm->id));
715 }
716
717 /**
454e8dd9 718 * @return moodle_url of the page for editing this workshop's grading form
b8ead2e6
DM
719 */
720 public function editform_url() {
721 global $CFG;
722 return new moodle_url($CFG->wwwroot . '/mod/workshop/editform.php', array('cmid' => $this->cm->id));
723 }
724
725 /**
454e8dd9 726 * @return moodle_url of the page for previewing this workshop's grading form
b8ead2e6
DM
727 */
728 public function previewform_url() {
729 global $CFG;
43b34576 730 return new moodle_url($CFG->wwwroot . '/mod/workshop/editformpreview.php', array('cmid' => $this->cm->id));
b8ead2e6
DM
731 }
732
733 /**
734 * @param int $assessmentid The ID of assessment record
454e8dd9 735 * @return moodle_url of the assessment page
b8ead2e6 736 */
a39d7d87 737 public function assess_url($assessmentid) {
b8ead2e6 738 global $CFG;
454e8dd9 739 $assessmentid = clean_param($assessmentid, PARAM_INT);
a39d7d87 740 return new moodle_url($CFG->wwwroot . '/mod/workshop/assessment.php', array('asid' => $assessmentid));
b8ead2e6
DM
741 }
742
becec954
DM
743 /**
744 * @param int $assessmentid The ID of assessment record
745 * @return moodle_url of the example assessment page
746 */
747 public function exassess_url($assessmentid) {
748 global $CFG;
749 $assessmentid = clean_param($assessmentid, PARAM_INT);
750 return new moodle_url($CFG->wwwroot . '/mod/workshop/exassessment.php', array('asid' => $assessmentid));
751 }
752
39861053 753 /**
67cd00ba 754 * @return moodle_url of the page to view a submission, defaults to the own one
39861053 755 */
67cd00ba 756 public function submission_url($id=null) {
39861053 757 global $CFG;
67cd00ba 758 return new moodle_url($CFG->wwwroot . '/mod/workshop/submission.php', array('cmid' => $this->cm->id, 'id' => $id));
39861053
DM
759 }
760
81eccf0a
DM
761 /**
762 * @param int $id example submission id
763 * @return moodle_url of the page to view an example submission
764 */
becec954 765 public function exsubmission_url($id) {
81eccf0a 766 global $CFG;
becec954 767 return new moodle_url($CFG->wwwroot . '/mod/workshop/exsubmission.php', array('cmid' => $this->cm->id, 'id' => $id));
81eccf0a
DM
768 }
769
cbf87967
DM
770 /**
771 * @param int $sid submission id
772 * @param array $aid of int assessment ids
773 * @return moodle_url of the page to compare assessments of the given submission
774 */
775 public function compare_url($sid, array $aids) {
776 global $CFG;
777
778 $url = new moodle_url($CFG->wwwroot . '/mod/workshop/compare.php', array('cmid' => $this->cm->id, 'sid' => $sid));
779 $i = 0;
780 foreach ($aids as $aid) {
781 $url->param("aid{$i}", $aid);
782 $i++;
783 }
784 return $url;
785 }
786
787 /**
788 * @param int $sid submission id
789 * @param int $aid assessment id
790 * @return moodle_url of the page to compare the reference assessments of the given example submission
791 */
792 public function excompare_url($sid, $aid) {
793 global $CFG;
794 return new moodle_url($CFG->wwwroot . '/mod/workshop/excompare.php', array('cmid' => $this->cm->id, 'sid' => $sid, 'aid' => $aid));
795 }
796
da0b1f70 797 /**
454e8dd9 798 * @return moodle_url of the mod_edit form
da0b1f70
DM
799 */
800 public function updatemod_url() {
801 global $CFG;
802 return new moodle_url($CFG->wwwroot . '/course/modedit.php', array('update' => $this->cm->id, 'return' => 1));
803 }
804
454e8dd9
DM
805 /**
806 * @return moodle_url to the allocation page
807 */
da0b1f70
DM
808 public function allocation_url() {
809 global $CFG;
810 return new moodle_url($CFG->wwwroot . '/mod/workshop/allocation.php', array('cmid' => $this->cm->id));
811 }
812
454e8dd9
DM
813 /**
814 * @param int $phasecode The internal phase code
815 * @return moodle_url of the script to change the current phase to $phasecode
816 */
817 public function switchphase_url($phasecode) {
818 global $CFG;
819 $phasecode = clean_param($phasecode, PARAM_INT);
820 return new moodle_url($CFG->wwwroot . '/mod/workshop/switchphase.php', array('cmid' => $this->cm->id, 'phase' => $phasecode));
821 }
822
89c1aa97
DM
823 /**
824 * @return moodle_url to the aggregation page
825 */
826 public function aggregate_url() {
827 global $CFG;
828 return new moodle_url($CFG->wwwroot . '/mod/workshop/aggregate.php', array('cmid' => $this->cm->id));
829 }
830
b8ead2e6 831 /**
407b1e91
DM
832 * Are users allowed to create/edit their submissions?
833 *
407b1e91 834 * @return bool
b8ead2e6 835 */
407b1e91 836 public function submitting_allowed() {
74bf8a94
DM
837 if ($this->phase != self::PHASE_SUBMISSION) {
838 // submitting is not allowed but in the submission phase
839 return false;
840 }
841 $now = time();
842 if (!empty($this->submissionstart) and $this->submissionstart > $now) {
843 // if enabled, submitting is not allowed before the date/time defined in the mod_form
844 return false;
845 }
846 if (!empty($this->submissionend) and empty($this->latesubmissions) and $now > $this->submissionend ) {
847 // if enabled, submitting is not allowed after the date/time defined in the mod_form unless late submission is allowed
848 return false;
849 }
850 // here we go, submission is allowed
407b1e91 851 return true;
b8ead2e6
DM
852 }
853
c1e883bb 854 /**
407b1e91 855 * Are reviewers allowed to create/edit their assessments?
c1e883bb 856 *
c1e883bb
DM
857 * @return bool
858 */
407b1e91 859 public function assessing_allowed() {
74bf8a94
DM
860 if ($this->phase != self::PHASE_ASSESSMENT) {
861 // assessing is not allowed but in the assessment phase
862 return false;
863 }
864 $now = time();
865 if (!empty($this->assessmentstart) and $this->assessmentstart > $now) {
866 // if enabled, assessing is not allowed before the date/time defined in the mod_form
867 return false;
868 }
869 if (!empty($this->assessmentend) and $now > $this->assessmentend ) {
870 // if enabled, assessing is not allowed after the date/time defined in the mod_form
871 return false;
872 }
873 // here we go, assessing is allowed
c1e883bb
DM
874 return true;
875 }
876
becec954
DM
877 /**
878 * Are reviewers allowed to create/edit their assessments of the example submissions?
879 *
74bf8a94 880 * Note this does not check other conditions like the number of already submitted examples etc.
becec954 881 *
74bf8a94 882 * @return null|bool
becec954
DM
883 */
884 public function assessing_examples_allowed() {
74bf8a94
DM
885 if (empty($this->useexamples)) {
886 return null;
887 }
888 if (self::EXAMPLES_VOLUNTARY == $this->examplesmode) {
889 return true;
890 }
891 if (self::EXAMPLES_BEFORE_SUBMISSION == $this->examplesmode and self::PHASE_SUBMISSION == $this->phase) {
892 return true;
893 }
894 if (self::EXAMPLES_BEFORE_ASSESSMENT == $this->examplesmode and self::PHASE_ASSESSMENT == $this->phase) {
895 return true;
896 }
897 return false;
becec954 898 }
407b1e91 899
3dc78e5b
DM
900 /**
901 * Are the peer-reviews available to the authors?
902 *
903 * TODO: this depends on the workshop phase
904 *
905 * @return bool
906 */
907 public function assessments_available() {
908 return true;
909 }
910
911 /**
912 * Can the given grades be displayed to the authors?
913 *
914 * Grades are not displayed if {@link self::assessments_available()} return false. The returned
f6e8b318 915 * value may be true (if yes, display grades) or false (no, hide grades yet)
3dc78e5b 916 *
f6e8b318 917 * @return bool
3dc78e5b
DM
918 */
919 public function grades_available() {
920 return true;
921 }
922
b761e6d9
DM
923 /**
924 * Prepare an individual workshop plan for the given user.
925 *
f05c168d
DM
926 * @param int $userid whom the plan is prepared for
927 * @param stdClass context of the planned workshop
928 * @return stdClass data object to be passed to the renderer
b761e6d9 929 */
d895c6aa 930 public function prepare_user_plan($userid) {
b761e6d9
DM
931 global $DB;
932
933 $phases = array();
934
935 // Prepare tasks for the setup phase
936 $phase = new stdClass();
937 $phase->title = get_string('phasesetup', 'workshop');
938 $phase->tasks = array();
d895c6aa 939 if (has_capability('moodle/course:manageactivities', $this->context, $userid)) {
da0b1f70
DM
940 $task = new stdClass();
941 $task->title = get_string('taskintro', 'workshop');
942 $task->link = $this->updatemod_url();
943 $task->completed = !(trim(strip_tags($this->intro)) == '');
944 $phase->tasks['intro'] = $task;
945 }
d895c6aa 946 if (has_capability('moodle/course:manageactivities', $this->context, $userid)) {
454e8dd9
DM
947 $task = new stdClass();
948 $task->title = get_string('taskinstructauthors', 'workshop');
949 $task->link = $this->updatemod_url();
950 $task->completed = !(trim(strip_tags($this->instructauthors)) == '');
951 $phase->tasks['instructauthors'] = $task;
952 }
d895c6aa 953 if (has_capability('mod/workshop:editdimensions', $this->context, $userid)) {
b761e6d9 954 $task = new stdClass();
da0b1f70
DM
955 $task->title = get_string('editassessmentform', 'workshop');
956 $task->link = $this->editform_url();
f6e8b318 957 if ($this->grading_strategy_instance()->form_ready()) {
da0b1f70
DM
958 $task->completed = true;
959 } elseif ($this->phase > self::PHASE_SETUP) {
960 $task->completed = false;
961 }
b761e6d9
DM
962 $phase->tasks['editform'] = $task;
963 }
81eccf0a
DM
964 if ($this->useexamples and has_capability('mod/workshop:manageexamples', $this->context, $userid)) {
965 $task = new stdClass();
966 $task->title = get_string('prepareexamples', 'workshop');
967 if ($DB->count_records('workshop_submissions', array('example' => 1, 'workshopid' => $this->id)) > 0) {
968 $task->completed = true;
969 } elseif ($this->phase > self::PHASE_SETUP) {
970 $task->completed = false;
971 }
972 $phase->tasks['prepareexamples'] = $task;
973 }
da0b1f70
DM
974 if (empty($phase->tasks) and $this->phase == self::PHASE_SETUP) {
975 // if we are in the setup phase and there is no task (typical for students), let us
976 // display some explanation what is going on
977 $task = new stdClass();
978 $task->title = get_string('undersetup', 'workshop');
979 $task->completed = 'info';
980 $phase->tasks['setupinfo'] = $task;
981 }
b761e6d9
DM
982 $phases[self::PHASE_SETUP] = $phase;
983
984 // Prepare tasks for the submission phase
985 $phase = new stdClass();
986 $phase->title = get_string('phasesubmission', 'workshop');
987 $phase->tasks = array();
81eccf0a
DM
988 if (($this->usepeerassessment or $this->useselfassessment)
989 and has_capability('moodle/course:manageactivities', $this->context, $userid)) {
b761e6d9 990 $task = new stdClass();
00aca3c1
DM
991 $task->title = get_string('taskinstructreviewers', 'workshop');
992 $task->link = $this->updatemod_url();
993 if (trim(strip_tags($this->instructreviewers))) {
da0b1f70
DM
994 $task->completed = true;
995 } elseif ($this->phase >= self::PHASE_ASSESSMENT) {
996 $task->completed = false;
da0b1f70 997 }
00aca3c1 998 $phase->tasks['instructreviewers'] = $task;
b761e6d9 999 }
d895c6aa 1000 if (has_capability('mod/workshop:submit', $this->context, $userid, false)) {
da0b1f70 1001 $task = new stdClass();
00aca3c1
DM
1002 $task->title = get_string('tasksubmit', 'workshop');
1003 $task->link = $this->submission_url();
1004 if ($DB->record_exists('workshop_submissions', array('workshopid'=>$this->id, 'example'=>0, 'authorid'=>$userid))) {
da0b1f70
DM
1005 $task->completed = true;
1006 } elseif ($this->phase >= self::PHASE_ASSESSMENT) {
1007 $task->completed = false;
00aca3c1
DM
1008 } else {
1009 $task->completed = null; // still has a chance to submit
da0b1f70 1010 }
00aca3c1 1011 $phase->tasks['submit'] = $task;
da0b1f70 1012 }
d895c6aa 1013 if (has_capability('mod/workshop:allocate', $this->context, $userid)) {
da0b1f70
DM
1014 $task = new stdClass();
1015 $task->title = get_string('allocate', 'workshop');
1016 $task->link = $this->allocation_url();
d895c6aa 1017 $numofauthors = count(get_users_by_capability($this->context, 'mod/workshop:submit', 'u.id', '', '', '',
a3610b08
DM
1018 '', '', false, true));
1019 $numofsubmissions = $DB->count_records('workshop_submissions', array('workshopid'=>$this->id, 'example'=>0));
1020 $sql = 'SELECT COUNT(s.id) AS nonallocated
1021 FROM {workshop_submissions} s
1022 LEFT JOIN {workshop_assessments} a ON (a.submissionid=s.id)
1023 WHERE s.workshopid = :workshopid AND s.example=0 AND a.submissionid IS NULL';
1024 $params['workshopid'] = $this->id;
1025 $numnonallocated = $DB->count_records_sql($sql, $params);
da0b1f70
DM
1026 if ($numofsubmissions == 0) {
1027 $task->completed = null;
a3610b08 1028 } elseif ($numnonallocated == 0) {
da0b1f70
DM
1029 $task->completed = true;
1030 } elseif ($this->phase > self::PHASE_SUBMISSION) {
1031 $task->completed = false;
1032 } else {
1033 $task->completed = null; // still has a chance to allocate
1034 }
1035 $a = new stdClass();
3189fb2d
DM
1036 $a->expected = $numofauthors;
1037 $a->submitted = $numofsubmissions;
a3610b08 1038 $a->allocate = $numnonallocated;
3189fb2d 1039 $task->details = get_string('allocatedetails', 'workshop', $a);
da0b1f70 1040 unset($a);
3189fb2d 1041 $phase->tasks['allocate'] = $task;
3dc78e5b
DM
1042
1043 if ($numofsubmissions < $numofauthors and $this->phase >= self::PHASE_SUBMISSION) {
1044 $task = new stdClass();
1045 $task->title = get_string('someuserswosubmission', 'workshop');
1046 $task->completed = 'info';
1047 $phase->tasks['allocateinfo'] = $task;
1048 }
da0b1f70 1049 }
81eccf0a 1050 $phases[self::PHASE_SUBMISSION] = $phase;
b761e6d9
DM
1051
1052 // Prepare tasks for the peer-assessment phase (includes eventual self-assessments)
1053 $phase = new stdClass();
1054 $phase->title = get_string('phaseassessment', 'workshop');
1055 $phase->tasks = array();
d895c6aa 1056 $phase->isreviewer = has_capability('mod/workshop:peerassess', $this->context, $userid);
3dc78e5b 1057 $phase->assessments = $this->get_assessments_by_reviewer($userid);
b761e6d9
DM
1058 $numofpeers = 0; // number of allocated peer-assessments
1059 $numofpeerstodo = 0; // number of peer-assessments to do
1060 $numofself = 0; // number of allocated self-assessments - should be 0 or 1
1061 $numofselftodo = 0; // number of self-assessments to do - should be 0 or 1
1062 foreach ($phase->assessments as $a) {
1063 if ($a->authorid == $userid) {
1064 $numofself++;
1065 if (is_null($a->grade)) {
1066 $numofselftodo++;
1067 }
1068 } else {
1069 $numofpeers++;
1070 if (is_null($a->grade)) {
1071 $numofpeerstodo++;
1072 }
1073 }
1074 }
1075 unset($a);
81eccf0a 1076 if ($this->usepeerassessment and $numofpeers) {
b761e6d9 1077 $task = new stdClass();
3dc78e5b
DM
1078 if ($numofpeerstodo == 0) {
1079 $task->completed = true;
1080 } elseif ($this->phase > self::PHASE_ASSESSMENT) {
1081 $task->completed = false;
1082 }
b761e6d9
DM
1083 $a = new stdClass();
1084 $a->total = $numofpeers;
1085 $a->todo = $numofpeerstodo;
1086 $task->title = get_string('taskassesspeers', 'workshop');
da0b1f70 1087 $task->details = get_string('taskassesspeersdetails', 'workshop', $a);
b761e6d9
DM
1088 unset($a);
1089 $phase->tasks['assesspeers'] = $task;
1090 }
81eccf0a 1091 if ($this->useselfassessment and $numofself) {
b761e6d9 1092 $task = new stdClass();
3dc78e5b
DM
1093 if ($numofselftodo == 0) {
1094 $task->completed = true;
1095 } elseif ($this->phase > self::PHASE_ASSESSMENT) {
1096 $task->completed = false;
1097 }
b761e6d9
DM
1098 $task->title = get_string('taskassessself', 'workshop');
1099 $phase->tasks['assessself'] = $task;
1100 }
1101 $phases[self::PHASE_ASSESSMENT] = $phase;
1102
1fed6ce3 1103 // Prepare tasks for the grading evaluation phase
b761e6d9
DM
1104 $phase = new stdClass();
1105 $phase->title = get_string('phaseevaluation', 'workshop');
1106 $phase->tasks = array();
1fed6ce3 1107 if (has_capability('mod/workshop:overridegrades', $this->context)) {
f27b70fb
DM
1108 $expected = count($this->get_potential_authors(false));
1109 $calculated = $DB->count_records_select('workshop_submissions',
1110 'workshopid = ? AND (grade IS NOT NULL OR gradeover IS NOT NULL)', array($this->id));
1fed6ce3 1111 $task = new stdClass();
10bc4bce 1112 $task->title = get_string('calculatesubmissiongrades', 'workshop');
1fed6ce3
DM
1113 $a = new stdClass();
1114 $a->expected = $expected;
f27b70fb 1115 $a->calculated = $calculated;
10bc4bce 1116 $task->details = get_string('calculatesubmissiongradesdetails', 'workshop', $a);
f27b70fb 1117 if ($calculated >= $expected) {
1fed6ce3
DM
1118 $task->completed = true;
1119 } elseif ($this->phase > self::PHASE_EVALUATION) {
1120 $task->completed = false;
1121 }
10bc4bce 1122 $phase->tasks['calculatesubmissiongrade'] = $task;
f27b70fb
DM
1123
1124 $expected = count($this->get_potential_reviewers(false));
1125 $calculated = $DB->count_records_select('workshop_aggregations',
1126 'workshopid = ? AND gradinggrade IS NOT NULL', array($this->id));
1127 $task = new stdClass();
1128 $task->title = get_string('calculategradinggrades', 'workshop');
1129 $a = new stdClass();
1130 $a->expected = $expected;
1131 $a->calculated = $calculated;
1132 $task->details = get_string('calculategradinggradesdetails', 'workshop', $a);
1133 if ($calculated >= $expected) {
1134 $task->completed = true;
1135 } elseif ($this->phase > self::PHASE_EVALUATION) {
1136 $task->completed = false;
f55650e6 1137 }
f27b70fb
DM
1138 $phase->tasks['calculategradinggrade'] = $task;
1139
d183140d 1140 } elseif ($this->phase == self::PHASE_EVALUATION) {
1fed6ce3
DM
1141 $task = new stdClass();
1142 $task->title = get_string('evaluategradeswait', 'workshop');
1143 $task->completed = 'info';
1144 $phase->tasks['evaluateinfo'] = $task;
1145 }
b761e6d9
DM
1146 $phases[self::PHASE_EVALUATION] = $phase;
1147
1148 // Prepare tasks for the "workshop closed" phase - todo
1149 $phase = new stdClass();
1150 $phase->title = get_string('phaseclosed', 'workshop');
1151 $phase->tasks = array();
1152 $phases[self::PHASE_CLOSED] = $phase;
1153
1154 // Polish data, set default values if not done explicitly
1155 foreach ($phases as $phasecode => $phase) {
1156 $phase->title = isset($phase->title) ? $phase->title : '';
1157 $phase->tasks = isset($phase->tasks) ? $phase->tasks : array();
1158 if ($phasecode == $this->phase) {
1159 $phase->active = true;
1160 } else {
1161 $phase->active = false;
1162 }
454e8dd9
DM
1163 if (!isset($phase->actions)) {
1164 $phase->actions = array();
1165 }
b761e6d9
DM
1166
1167 foreach ($phase->tasks as $taskcode => $task) {
1168 $task->title = isset($task->title) ? $task->title : '';
da0b1f70
DM
1169 $task->link = isset($task->link) ? $task->link : null;
1170 $task->details = isset($task->details) ? $task->details : '';
b761e6d9
DM
1171 $task->completed = isset($task->completed) ? $task->completed : null;
1172 }
1173 }
454e8dd9
DM
1174
1175 // Add phase swithing actions
d895c6aa 1176 if (has_capability('mod/workshop:switchphase', $this->context, $userid)) {
454e8dd9
DM
1177 foreach ($phases as $phasecode => $phase) {
1178 if (! $phase->active) {
1179 $action = new stdClass();
1180 $action->type = 'switchphase';
1181 $action->url = $this->switchphase_url($phasecode);
1182 $phase->actions[] = $action;
1183 }
1184 }
1185 }
1186
b761e6d9
DM
1187 return $phases;
1188 }
1189
454e8dd9
DM
1190 /**
1191 * Switch to a new workshop phase
1192 *
1193 * Modifies the underlying database record. You should terminate the script shortly after calling this.
1194 *
1195 * @param int $newphase new phase code
1196 * @return bool true if success, false otherwise
1197 */
1198 public function switch_phase($newphase) {
1199 global $DB;
1200
365c2cc2 1201 $known = $this->available_phases_list();
454e8dd9
DM
1202 if (!isset($known[$newphase])) {
1203 return false;
1204 }
f6e8b318
DM
1205
1206 if (self::PHASE_CLOSED == $newphase) {
f27b70fb 1207 // push the grades into the gradebook
10bc4bce
DM
1208 $workshop = new stdClass();
1209 foreach ($this as $property => $value) {
1210 $workshop->{$property} = $value;
1211 }
1212 $workshop->course = $this->course->id;
1213 $workshop->cmidnumber = $this->cm->id;
1214 $workshop->modname = 'workshop';
1215 workshop_update_grades($workshop);
f6e8b318
DM
1216 }
1217
454e8dd9
DM
1218 $DB->set_field('workshop', 'phase', $newphase, array('id' => $this->id));
1219 return true;
1220 }
ddb59c77
DM
1221
1222 /**
1223 * Saves a raw grade for submission as calculated from the assessment form fields
1224 *
1225 * @param array $assessmentid assessment record id, must exists
00aca3c1 1226 * @param mixed $grade raw percentual grade from 0.00000 to 100.00000
ddb59c77
DM
1227 * @return false|float the saved grade
1228 */
1229 public function set_peer_grade($assessmentid, $grade) {
1230 global $DB;
1231
1232 if (is_null($grade)) {
1233 return false;
1234 }
1235 $data = new stdClass();
1236 $data->id = $assessmentid;
1237 $data->grade = $grade;
1238 $DB->update_record('workshop_assessments', $data);
1239 return $grade;
1240 }
6516b9e9 1241
29dc43e7
DM
1242 /**
1243 * Prepares data object with all workshop grades to be rendered
1244 *
5e71cefb
DM
1245 * @param int $userid the user we are preparing the report for
1246 * @param mixed $groups single group or array of groups - only show users who are in one of these group(s). Defaults to all
29dc43e7 1247 * @param int $page the current page (for the pagination)
5e71cefb 1248 * @param int $perpage participants per page (for the pagination)
f27b70fb 1249 * @param string $sortby lastname|firstname|submissiontitle|submissiongrade|gradinggrade
5e71cefb 1250 * @param string $sorthow ASC|DESC
29dc43e7
DM
1251 * @return stdClass data for the renderer
1252 */
d895c6aa 1253 public function prepare_grading_report($userid, $groups, $page, $perpage, $sortby, $sorthow) {
29dc43e7
DM
1254 global $DB;
1255
d895c6aa
DM
1256 $canviewall = has_capability('mod/workshop:viewallassessments', $this->context, $userid);
1257 $isparticipant = has_any_capability(array('mod/workshop:submit', 'mod/workshop:peerassess'), $this->context, $userid);
29dc43e7
DM
1258
1259 if (!$canviewall and !$isparticipant) {
1260 // who the hell is this?
1261 return array();
1262 }
1263
f27b70fb 1264 if (!in_array($sortby, array('lastname','firstname','submissiontitle','submissiongrade','gradinggrade'))) {
5e71cefb
DM
1265 $sortby = 'lastname';
1266 }
1267
1268 if (!($sorthow === 'ASC' or $sorthow === 'DESC')) {
1269 $sorthow = 'ASC';
1270 }
1271
1272 // get the list of user ids to be displayed
29dc43e7
DM
1273 if ($canviewall) {
1274 // fetch the list of ids of all workshop participants - this may get really long so fetch just id
d895c6aa 1275 $participants = get_users_by_capability($this->context, array('mod/workshop:submit', 'mod/workshop:peerassess'),
5e71cefb 1276 'u.id', '', '', '', $groups, '', false, false, true);
29dc43e7
DM
1277 } else {
1278 // this is an ordinary workshop participant (aka student) - display the report just for him/her
1279 $participants = array($userid => (object)array('id' => $userid));
1280 }
1281
5e71cefb 1282 // we will need to know the number of all records later for the pagination purposes
29dc43e7
DM
1283 $numofparticipants = count($participants);
1284
d183140d 1285 // load all fields which can be used for sorting and paginate the records
5e71cefb 1286 list($participantids, $params) = $DB->get_in_or_equal(array_keys($participants), SQL_PARAMS_NAMED);
d183140d
DM
1287 $params['workshopid1'] = $this->id;
1288 $params['workshopid2'] = $this->id;
5e71cefb
DM
1289 $sqlsort = $sortby . ' ' . $sorthow . ',u.lastname,u.firstname,u.id';
1290 $sql = "SELECT u.id AS userid,u.firstname,u.lastname,u.picture,u.imagealt,
f27b70fb 1291 s.title AS submissiontitle, s.grade AS submissiongrade, ag.gradinggrade
5e71cefb 1292 FROM {user} u
d183140d
DM
1293 LEFT JOIN {workshop_submissions} s ON (s.authorid = u.id AND s.workshopid = :workshopid1 AND s.example = 0)
1294 LEFT JOIN {workshop_aggregations} ag ON (ag.userid = u.id AND ag.workshopid = :workshopid2)
1295 WHERE u.id $participantids
5e71cefb
DM
1296 ORDER BY $sqlsort";
1297 $participants = $DB->get_records_sql($sql, $params, $page * $perpage, $perpage);
29dc43e7
DM
1298
1299 // this will hold the information needed to display user names and pictures
5e71cefb
DM
1300 $userinfo = array();
1301
1302 // get the user details for all participants to display
1303 foreach ($participants as $participant) {
1304 if (!isset($userinfo[$participant->userid])) {
1305 $userinfo[$participant->userid] = new stdClass();
1306 $userinfo[$participant->userid]->id = $participant->userid;
1307 $userinfo[$participant->userid]->firstname = $participant->firstname;
1308 $userinfo[$participant->userid]->lastname = $participant->lastname;
1309 $userinfo[$participant->userid]->picture = $participant->picture;
1310 $userinfo[$participant->userid]->imagealt = $participant->imagealt;
1311 }
1312 }
29dc43e7 1313
5e71cefb 1314 // load the submissions details
29dc43e7 1315 $submissions = $this->get_submissions(array_keys($participants));
5e71cefb
DM
1316
1317 // get the user details for all moderators (teachers) that have overridden a submission grade
29dc43e7 1318 foreach ($submissions as $submission) {
29dc43e7
DM
1319 if (!isset($userinfo[$submission->gradeoverby])) {
1320 $userinfo[$submission->gradeoverby] = new stdClass();
1321 $userinfo[$submission->gradeoverby]->id = $submission->gradeoverby;
1322 $userinfo[$submission->gradeoverby]->firstname = $submission->overfirstname;
1323 $userinfo[$submission->gradeoverby]->lastname = $submission->overlastname;
1324 $userinfo[$submission->gradeoverby]->picture = $submission->overpicture;
1325 $userinfo[$submission->gradeoverby]->imagealt = $submission->overimagealt;
1326 }
1327 }
1328
5e71cefb 1329 // get the user details for all reviewers of the displayed participants
29dc43e7
DM
1330 $reviewers = array();
1331 if ($submissions) {
1332 list($submissionids, $params) = $DB->get_in_or_equal(array_keys($submissions), SQL_PARAMS_NAMED);
581878b8 1333 $sql = "SELECT a.id AS assessmentid, a.submissionid, a.grade, a.gradinggrade, a.gradinggradeover, a.weight,
29dc43e7
DM
1334 r.id AS reviewerid, r.lastname, r.firstname, r.picture, r.imagealt,
1335 s.id AS submissionid, s.authorid
1336 FROM {workshop_assessments} a
1337 JOIN {user} r ON (a.reviewerid = r.id)
1338 JOIN {workshop_submissions} s ON (a.submissionid = s.id)
1339 WHERE a.submissionid $submissionids";
1340 $reviewers = $DB->get_records_sql($sql, $params);
1341 foreach ($reviewers as $reviewer) {
1342 if (!isset($userinfo[$reviewer->reviewerid])) {
1343 $userinfo[$reviewer->reviewerid] = new stdClass();
1344 $userinfo[$reviewer->reviewerid]->id = $reviewer->reviewerid;
1345 $userinfo[$reviewer->reviewerid]->firstname = $reviewer->firstname;
1346 $userinfo[$reviewer->reviewerid]->lastname = $reviewer->lastname;
1347 $userinfo[$reviewer->reviewerid]->picture = $reviewer->picture;
1348 $userinfo[$reviewer->reviewerid]->imagealt = $reviewer->imagealt;
1349 }
1350 }
1351 }
1352
5e71cefb 1353 // get the user details for all reviewees of the displayed participants
934329e5
DM
1354 $reviewees = array();
1355 if ($participants) {
1356 list($participantids, $params) = $DB->get_in_or_equal(array_keys($participants), SQL_PARAMS_NAMED);
1357 $params['workshopid'] = $this->id;
581878b8 1358 $sql = "SELECT a.id AS assessmentid, a.submissionid, a.grade, a.gradinggrade, a.gradinggradeover, a.reviewerid, a.weight,
934329e5
DM
1359 s.id AS submissionid,
1360 e.id AS authorid, e.lastname, e.firstname, e.picture, e.imagealt
1361 FROM {user} u
1362 JOIN {workshop_assessments} a ON (a.reviewerid = u.id)
1363 JOIN {workshop_submissions} s ON (a.submissionid = s.id)
1364 JOIN {user} e ON (s.authorid = e.id)
1365 WHERE u.id $participantids AND s.workshopid = :workshopid";
1366 $reviewees = $DB->get_records_sql($sql, $params);
1367 foreach ($reviewees as $reviewee) {
1368 if (!isset($userinfo[$reviewee->authorid])) {
1369 $userinfo[$reviewee->authorid] = new stdClass();
1370 $userinfo[$reviewee->authorid]->id = $reviewee->authorid;
1371 $userinfo[$reviewee->authorid]->firstname = $reviewee->firstname;
1372 $userinfo[$reviewee->authorid]->lastname = $reviewee->lastname;
1373 $userinfo[$reviewee->authorid]->picture = $reviewee->picture;
1374 $userinfo[$reviewee->authorid]->imagealt = $reviewee->imagealt;
1375 }
29dc43e7
DM
1376 }
1377 }
1378
5e71cefb
DM
1379 // finally populate the object to be rendered
1380 $grades = $participants;
29dc43e7
DM
1381
1382 foreach ($participants as $participant) {
1383 // set up default (null) values
d183140d
DM
1384 $grades[$participant->userid]->submissionid = null;
1385 $grades[$participant->userid]->submissiontitle = null;
1386 $grades[$participant->userid]->submissiongrade = null;
1387 $grades[$participant->userid]->submissiongradeover = null;
1388 $grades[$participant->userid]->submissiongradeoverby = null;
5e71cefb
DM
1389 $grades[$participant->userid]->reviewedby = array();
1390 $grades[$participant->userid]->reviewerof = array();
29dc43e7
DM
1391 }
1392 unset($participants);
1393 unset($participant);
1394
1395 foreach ($submissions as $submission) {
1396 $grades[$submission->authorid]->submissionid = $submission->id;
1397 $grades[$submission->authorid]->submissiontitle = $submission->title;
b4857acb
DM
1398 $grades[$submission->authorid]->submissiongrade = $this->real_grade($submission->grade);
1399 $grades[$submission->authorid]->submissiongradeover = $this->real_grade($submission->gradeover);
29dc43e7
DM
1400 $grades[$submission->authorid]->submissiongradeoverby = $submission->gradeoverby;
1401 }
1402 unset($submissions);
1403 unset($submission);
1404
1405 foreach($reviewers as $reviewer) {
1406 $info = new stdClass();
1407 $info->userid = $reviewer->reviewerid;
1408 $info->assessmentid = $reviewer->assessmentid;
1409 $info->submissionid = $reviewer->submissionid;
b4857acb
DM
1410 $info->grade = $this->real_grade($reviewer->grade);
1411 $info->gradinggrade = $this->real_grading_grade($reviewer->gradinggrade);
1412 $info->gradinggradeover = $this->real_grading_grade($reviewer->gradinggradeover);
581878b8 1413 $info->weight = $reviewer->weight;
29dc43e7
DM
1414 $grades[$reviewer->authorid]->reviewedby[$reviewer->reviewerid] = $info;
1415 }
1416 unset($reviewers);
1417 unset($reviewer);
1418
1419 foreach($reviewees as $reviewee) {
1420 $info = new stdClass();
1421 $info->userid = $reviewee->authorid;
1422 $info->assessmentid = $reviewee->assessmentid;
1423 $info->submissionid = $reviewee->submissionid;
b4857acb
DM
1424 $info->grade = $this->real_grade($reviewee->grade);
1425 $info->gradinggrade = $this->real_grading_grade($reviewee->gradinggrade);
1426 $info->gradinggradeover = $this->real_grading_grade($reviewee->gradinggradeover);
581878b8 1427 $info->weight = $reviewee->weight;
29dc43e7
DM
1428 $grades[$reviewee->reviewerid]->reviewerof[$reviewee->authorid] = $info;
1429 }
1430 unset($reviewees);
1431 unset($reviewee);
1432
b4857acb
DM
1433 foreach ($grades as $grade) {
1434 $grade->gradinggrade = $this->real_grading_grade($grade->gradinggrade);
b4857acb
DM
1435 }
1436
29dc43e7
DM
1437 $data = new stdClass();
1438 $data->grades = $grades;
1439 $data->userinfo = $userinfo;
1440 $data->totalcount = $numofparticipants;
b4857acb
DM
1441 $data->maxgrade = $this->real_grade(100);
1442 $data->maxgradinggrade = $this->real_grading_grade(100);
29dc43e7
DM
1443 return $data;
1444 }
1445
29dc43e7 1446 /**
b4857acb 1447 * Calculates the real value of a grade
29dc43e7 1448 *
b4857acb
DM
1449 * @param float $value percentual value from 0 to 100
1450 * @param float $max the maximal grade
1451 * @return string
1452 */
1453 public function real_grade_value($value, $max) {
1454 $localized = true;
557a1100 1455 if (is_null($value) or $value === '') {
b4857acb
DM
1456 return null;
1457 } elseif ($max == 0) {
1458 return 0;
1459 } else {
1460 return format_float($max * $value / 100, $this->gradedecimals, $localized);
1461 }
1462 }
1463
e554671d
DM
1464 /**
1465 * Calculates the raw (percentual) value from a real grade
1466 *
1467 * This is used in cases when a user wants to give a grade such as 12 of 20 and we need to save
1468 * this value in a raw percentual form into DB
1469 * @param float $value given grade
1470 * @param float $max the maximal grade
1471 * @return float suitable to be stored as numeric(10,5)
1472 */
1473 public function raw_grade_value($value, $max) {
557a1100 1474 if (is_null($value) or $value === '') {
e554671d
DM
1475 return null;
1476 }
1477 if ($max == 0 or $value < 0) {
1478 return 0;
1479 }
1480 $p = $value / $max * 100;
1481 if ($p > 100) {
1482 return $max;
1483 }
1484 return grade_floatval($p);
1485 }
1486
b4857acb
DM
1487 /**
1488 * Calculates the real value of grade for submission
1489 *
1490 * @param float $value percentual value from 0 to 100
1491 * @return string
1492 */
1493 public function real_grade($value) {
1494 return $this->real_grade_value($value, $this->grade);
1495 }
1496
1497 /**
1498 * Calculates the real value of grade for assessment
1499 *
1500 * @param float $value percentual value from 0 to 100
1501 * @return string
1502 */
1503 public function real_grading_grade($value) {
1504 return $this->real_grade_value($value, $this->gradinggrade);
29dc43e7
DM
1505 }
1506
89c1aa97 1507 /**
e9a90e69 1508 * Calculates grades for submission for the given participant(s) and updates it in the database
89c1aa97
DM
1509 *
1510 * @param null|int|array $restrict If null, update all authors, otherwise update just grades for the given author(s)
1511 * @return void
1512 */
8a1ba8ac 1513 public function aggregate_submission_grades($restrict=null) {
89c1aa97
DM
1514 global $DB;
1515
1516 // fetch a recordset with all assessments to process
1696f36c 1517 $sql = 'SELECT s.id AS submissionid, s.grade AS submissiongrade,
89c1aa97
DM
1518 a.weight, a.grade
1519 FROM {workshop_submissions} s
1520 LEFT JOIN {workshop_assessments} a ON (a.submissionid = s.id)
1521 WHERE s.example=0 AND s.workshopid=:workshopid'; // to be cont.
1522 $params = array('workshopid' => $this->id);
1523
1524 if (is_null($restrict)) {
1525 // update all users - no more conditions
1526 } elseif (!empty($restrict)) {
1527 list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED);
1528 $sql .= " AND s.authorid $usql";
1529 $params = array_merge($params, $uparams);
1530 } else {
1531 throw new coding_exception('Empty value is not a valid parameter here');
1532 }
1533
1534 $sql .= ' ORDER BY s.id'; // this is important for bulk processing
89c1aa97 1535
e9a90e69
DM
1536 $rs = $DB->get_recordset_sql($sql, $params);
1537 $batch = array(); // will contain a set of all assessments of a single submission
1538 $previous = null; // a previous record in the recordset
1539
89c1aa97
DM
1540 foreach ($rs as $current) {
1541 if (is_null($previous)) {
1542 // we are processing the very first record in the recordset
1543 $previous = $current;
89c1aa97 1544 }
e9a90e69 1545 if ($current->submissionid == $previous->submissionid) {
89c1aa97 1546 // we are still processing the current submission
e9a90e69
DM
1547 $batch[] = $current;
1548 } else {
1549 // process all the assessments of a sigle submission
1550 $this->aggregate_submission_grades_process($batch);
1551 // and then start to process another submission
1552 $batch = array($current);
1553 $previous = $current;
89c1aa97
DM
1554 }
1555 }
e9a90e69
DM
1556 // do not forget to process the last batch!
1557 $this->aggregate_submission_grades_process($batch);
89c1aa97
DM
1558 $rs->close();
1559 }
1560
1561 /**
1562 * Calculates grades for assessment for the given participant(s)
1563 *
39411930
DM
1564 * Grade for assessment is calculated as a simple mean of all grading grades calculated by the grading evaluator.
1565 * The assessment weight is not taken into account here.
89c1aa97
DM
1566 *
1567 * @param null|int|array $restrict If null, update all reviewers, otherwise update just grades for the given reviewer(s)
1568 * @return void
1569 */
8a1ba8ac 1570 public function aggregate_grading_grades($restrict=null) {
89c1aa97
DM
1571 global $DB;
1572
39411930
DM
1573 // fetch a recordset with all assessments to process
1574 $sql = 'SELECT a.reviewerid, a.gradinggrade, a.gradinggradeover,
1575 ag.id AS aggregationid, ag.gradinggrade AS aggregatedgrade
1576 FROM {workshop_assessments} a
1577 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
1578 LEFT JOIN {workshop_aggregations} ag ON (ag.userid = a.reviewerid AND ag.workshopid = s.workshopid)
1579 WHERE s.example=0 AND s.workshopid=:workshopid'; // to be cont.
1580 $params = array('workshopid' => $this->id);
1581
1582 if (is_null($restrict)) {
1583 // update all users - no more conditions
1584 } elseif (!empty($restrict)) {
1585 list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED);
1586 $sql .= " AND a.reviewerid $usql";
1587 $params = array_merge($params, $uparams);
1588 } else {
1589 throw new coding_exception('Empty value is not a valid parameter here');
1590 }
1591
1592 $sql .= ' ORDER BY a.reviewerid'; // this is important for bulk processing
1593
1594 $rs = $DB->get_recordset_sql($sql, $params);
1595 $batch = array(); // will contain a set of all assessments of a single submission
1596 $previous = null; // a previous record in the recordset
1597
1598 foreach ($rs as $current) {
1599 if (is_null($previous)) {
1600 // we are processing the very first record in the recordset
1601 $previous = $current;
1602 }
1603 if ($current->reviewerid == $previous->reviewerid) {
1604 // we are still processing the current reviewer
1605 $batch[] = $current;
1606 } else {
1607 // process all the assessments of a sigle submission
1608 $this->aggregate_grading_grades_process($batch);
1609 // and then start to process another reviewer
1610 $batch = array($current);
1611 $previous = $current;
1612 }
1613 }
1614 // do not forget to process the last batch!
1615 $this->aggregate_grading_grades_process($batch);
1616 $rs->close();
89c1aa97
DM
1617 }
1618
77f43e7d 1619 /**
f6e8b318 1620 * Returns the mform the teachers use to put a feedback for the reviewer
77f43e7d 1621 *
f6e8b318 1622 * @return workshop_feedbackreviewer_form
77f43e7d 1623 */
e554671d 1624 public function get_feedbackreviewer_form(moodle_url $actionurl, stdClass $assessment, $editable=true) {
77f43e7d
DM
1625 global $CFG;
1626 require_once(dirname(__FILE__) . '/feedbackreviewer_form.php');
1627
e554671d
DM
1628 $current = new stdClass();
1629 $current->asid = $assessment->id;
1630 $current->gradinggrade = $this->real_grading_grade($assessment->gradinggrade);
1631 $current->gradinggradeover = $this->real_grading_grade($assessment->gradinggradeover);
1632 $current->feedbackreviewer = $assessment->feedbackreviewer;
1633 $current->feedbackreviewerformat = $assessment->feedbackreviewerformat;
1634 if (is_null($current->gradinggrade)) {
1635 $current->gradinggrade = get_string('nullgrade', 'workshop');
1636 }
1637
1638 // prepare wysiwyg editor
1639 $current = file_prepare_standard_editor($current, 'feedbackreviewer', array());
1640
77f43e7d 1641 return new workshop_feedbackreviewer_form($actionurl,
e554671d 1642 array('workshop' => $this, 'current' => $current, 'feedbackopts' => array()),
77f43e7d
DM
1643 'post', '', null, $editable);
1644 }
1645
67cd00ba
DM
1646 /**
1647 * Returns the mform the teachers use to put a feedback for the author on their submission
1648 *
1649 * @return workshop_feedbackauthor_form
1650 */
1651 public function get_feedbackauthor_form(moodle_url $actionurl, stdClass $submission, $editable=true) {
1652 global $CFG;
1653 require_once(dirname(__FILE__) . '/feedbackauthor_form.php');
1654
1655 $current = new stdClass();
1656 $current->submissionid = $submission->id;
557a1100
DM
1657 $current->grade = $this->real_grade($submission->grade);
1658 $current->gradeover = $this->real_grade($submission->gradeover);
1659 $current->feedbackauthor = $submission->feedbackauthor;
1660 $current->feedbackauthorformat = $submission->feedbackauthorformat;
67cd00ba
DM
1661 if (is_null($current->grade)) {
1662 $current->grade = get_string('nullgrade', 'workshop');
1663 }
1664
1665 // prepare wysiwyg editor
1666 $current = file_prepare_standard_editor($current, 'feedbackauthor', array());
1667
1668 return new workshop_feedbackauthor_form($actionurl,
1669 array('workshop' => $this, 'current' => $current, 'feedbackopts' => array()),
1670 'post', '', null, $editable);
1671 }
1672
aa40adbf
DM
1673 ////////////////////////////////////////////////////////////////////////////////
1674 // Internal methods (implementation details) //
1675 ////////////////////////////////////////////////////////////////////////////////
6516b9e9 1676
e9a90e69
DM
1677 /**
1678 * Given an array of all assessments of a single submission, calculates the final grade for this submission
1679 *
1680 * This calculates the weighted mean of the passed assessment grades. If, however, the submission grade
1681 * was overridden by a teacher, the gradeover value is returned and the rest of grades are ignored.
1682 *
1683 * @param array $assessments of stdClass(->submissionid ->submissiongrade ->gradeover ->weight ->grade)
1fed6ce3 1684 * @return void
e9a90e69
DM
1685 */
1686 protected function aggregate_submission_grades_process(array $assessments) {
1687 global $DB;
1688
1689 $submissionid = null; // the id of the submission being processed
1690 $current = null; // the grade currently saved in database
1691 $finalgrade = null; // the new grade to be calculated
1692 $sumgrades = 0;
1693 $sumweights = 0;
1694
1695 foreach ($assessments as $assessment) {
1696 if (is_null($submissionid)) {
1697 // the id is the same in all records, fetch it during the first loop cycle
1698 $submissionid = $assessment->submissionid;
1699 }
1700 if (is_null($current)) {
1701 // the currently saved grade is the same in all records, fetch it during the first loop cycle
1702 $current = $assessment->submissiongrade;
1703 }
e9a90e69
DM
1704 if (is_null($assessment->grade)) {
1705 // this was not assessed yet
1706 continue;
1707 }
1708 if ($assessment->weight == 0) {
1709 // this does not influence the calculation
1710 continue;
1711 }
1712 $sumgrades += $assessment->grade * $assessment->weight;
1713 $sumweights += $assessment->weight;
1714 }
1715 if ($sumweights > 0 and is_null($finalgrade)) {
1716 $finalgrade = grade_floatval($sumgrades / $sumweights);
1717 }
1718 // check if the new final grade differs from the one stored in the database
1719 if (grade_floats_different($finalgrade, $current)) {
1720 // we need to save new calculation into the database
10bc4bce
DM
1721 $record = new stdClass();
1722 $record->id = $submissionid;
1723 $record->grade = $finalgrade;
1724 $record->timegraded = time();
1725 $DB->update_record('workshop_submissions', $record);
e9a90e69
DM
1726 }
1727 }
1728
39411930
DM
1729 /**
1730 * Given an array of all assessments done by a single reviewer, calculates the final grading grade
1731 *
1732 * This calculates the simple mean of the passed grading grades. If, however, the grading grade
1733 * was overridden by a teacher, the gradinggradeover value is returned and the rest of grades are ignored.
1734 *
1735 * @param array $assessments of stdClass(->reviewerid ->gradinggrade ->gradinggradeover ->aggregationid ->aggregatedgrade)
1fed6ce3 1736 * @return void
39411930
DM
1737 */
1738 protected function aggregate_grading_grades_process(array $assessments) {
1739 global $DB;
1740
1741 $reviewerid = null; // the id of the reviewer being processed
1742 $current = null; // the gradinggrade currently saved in database
1743 $finalgrade = null; // the new grade to be calculated
1744 $agid = null; // aggregation id
1745 $sumgrades = 0;
1746 $count = 0;
1747
1748 foreach ($assessments as $assessment) {
1749 if (is_null($reviewerid)) {
1750 // the id is the same in all records, fetch it during the first loop cycle
1751 $reviewerid = $assessment->reviewerid;
1752 }
1753 if (is_null($agid)) {
1754 // the id is the same in all records, fetch it during the first loop cycle
1755 $agid = $assessment->aggregationid;
1756 }
1757 if (is_null($current)) {
1758 // the currently saved grade is the same in all records, fetch it during the first loop cycle
1759 $current = $assessment->aggregatedgrade;
1760 }
1761 if (!is_null($assessment->gradinggradeover)) {
1762 // the grading grade for this assessment is overriden by a teacher
1763 $sumgrades += $assessment->gradinggradeover;
1764 $count++;
1765 } else {
1766 if (!is_null($assessment->gradinggrade)) {
1767 $sumgrades += $assessment->gradinggrade;
1768 $count++;
1769 }
1770 }
1771 }
1772 if ($count > 0) {
1773 $finalgrade = grade_floatval($sumgrades / $count);
1774 }
1775 // check if the new final grade differs from the one stored in the database
1776 if (grade_floats_different($finalgrade, $current)) {
1777 // we need to save new calculation into the database
1778 if (is_null($agid)) {
1779 // no aggregation record yet
1780 $record = new stdClass();
1781 $record->workshopid = $this->id;
1782 $record->userid = $reviewerid;
1783 $record->gradinggrade = $finalgrade;
10bc4bce 1784 $record->timegraded = time();
39411930
DM
1785 $DB->insert_record('workshop_aggregations', $record);
1786 } else {
10bc4bce
DM
1787 $record = new stdClass();
1788 $record->id = $agid;
1789 $record->gradinggrade = $finalgrade;
1790 $record->timegraded = time();
1791 $DB->update_record('workshop_aggregations', $record);
39411930
DM
1792 }
1793 }
1794 }
1795
6516b9e9 1796 /**
aa40adbf 1797 * Given a list of user ids, returns the filtered one containing just ids of users with own submission
6516b9e9 1798 *
aa40adbf
DM
1799 * Example submissions are ignored.
1800 *
1801 * @param array $userids
6516b9e9
DM
1802 * @return array
1803 */
aa40adbf
DM
1804 protected function users_with_submission(array $userids) {
1805 global $DB;
1806
1807 if (empty($userids)) {
1808 return array();
1809 }
1810 $userswithsubmission = array();
1811 list($usql, $uparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
00aca3c1 1812 $sql = "SELECT id,authorid
aa40adbf 1813 FROM {workshop_submissions}
00aca3c1 1814 WHERE example = 0 AND workshopid = :workshopid AND authorid $usql";
aa40adbf
DM
1815 $params = array('workshopid' => $this->id);
1816 $params = array_merge($params, $uparams);
1817 $submissions = $DB->get_records_sql($sql, $params);
1818 foreach ($submissions as $submission) {
00aca3c1 1819 $userswithsubmission[$submission->authorid] = true;
aa40adbf
DM
1820 }
1821
1822 return $userswithsubmission;
6516b9e9
DM
1823 }
1824
aa40adbf
DM
1825 /**
1826 * @return array of available workshop phases
1827 */
365c2cc2 1828 protected function available_phases_list() {
aa40adbf
DM
1829 return array(
1830 self::PHASE_SETUP => true,
1831 self::PHASE_SUBMISSION => true,
1832 self::PHASE_ASSESSMENT => true,
1833 self::PHASE_EVALUATION => true,
1834 self::PHASE_CLOSED => true,
1835 );
1836 }
1837
66c9894d 1838}