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