Updated the HEAD build version to 20100116
[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 }
3d2924e9 375 if (!empty($CFG->enablegroupings) 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;
790 return new moodle_url($CFG->wwwroot . '/mod/workshop/view.php', array('id' => $this->cm->id));
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;
798 return new moodle_url($CFG->wwwroot . '/mod/workshop/editform.php', array('cmid' => $this->cm->id));
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;
43b34576 806 return new moodle_url($CFG->wwwroot . '/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);
a39d7d87 816 return new moodle_url($CFG->wwwroot . '/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);
826 return new moodle_url($CFG->wwwroot . '/mod/workshop/exassessment.php', array('asid' => $assessmentid));
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;
67cd00ba 834 return new moodle_url($CFG->wwwroot . '/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;
becec954 843 return new moodle_url($CFG->wwwroot . '/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
854 $url = new moodle_url($CFG->wwwroot . '/mod/workshop/compare.php', array('cmid' => $this->cm->id, 'sid' => $sid));
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;
870 return new moodle_url($CFG->wwwroot . '/mod/workshop/excompare.php', array('cmid' => $this->cm->id, 'sid' => $sid, 'aid' => $aid));
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;
878 return new moodle_url($CFG->wwwroot . '/course/modedit.php', array('update' => $this->cm->id, 'return' => 1));
879 }
880
454e8dd9
DM
881 /**
882 * @return moodle_url to the allocation page
883 */
da0b1f70
DM
884 public function allocation_url() {
885 global $CFG;
886 return new moodle_url($CFG->wwwroot . '/mod/workshop/allocation.php', array('cmid' => $this->cm->id));
887 }
888
454e8dd9
DM
889 /**
890 * @param int $phasecode The internal phase code
891 * @return moodle_url of the script to change the current phase to $phasecode
892 */
893 public function switchphase_url($phasecode) {
894 global $CFG;
895 $phasecode = clean_param($phasecode, PARAM_INT);
896 return new moodle_url($CFG->wwwroot . '/mod/workshop/switchphase.php', array('cmid' => $this->cm->id, 'phase' => $phasecode));
897 }
898
89c1aa97
DM
899 /**
900 * @return moodle_url to the aggregation page
901 */
902 public function aggregate_url() {
903 global $CFG;
904 return new moodle_url($CFG->wwwroot . '/mod/workshop/aggregate.php', array('cmid' => $this->cm->id));
905 }
906
b8ead2e6 907 /**
407b1e91
DM
908 * Are users allowed to create/edit their submissions?
909 *
407b1e91 910 * @return bool
b8ead2e6 911 */
407b1e91 912 public function submitting_allowed() {
74bf8a94
DM
913 if ($this->phase != self::PHASE_SUBMISSION) {
914 // submitting is not allowed but in the submission phase
915 return false;
916 }
917 $now = time();
918 if (!empty($this->submissionstart) and $this->submissionstart > $now) {
919 // if enabled, submitting is not allowed before the date/time defined in the mod_form
920 return false;
921 }
922 if (!empty($this->submissionend) and empty($this->latesubmissions) and $now > $this->submissionend ) {
923 // if enabled, submitting is not allowed after the date/time defined in the mod_form unless late submission is allowed
924 return false;
925 }
926 // here we go, submission is allowed
407b1e91 927 return true;
b8ead2e6
DM
928 }
929
c1e883bb 930 /**
407b1e91 931 * Are reviewers allowed to create/edit their assessments?
c1e883bb 932 *
c1e883bb
DM
933 * @return bool
934 */
407b1e91 935 public function assessing_allowed() {
74bf8a94
DM
936 if ($this->phase != self::PHASE_ASSESSMENT) {
937 // assessing is not allowed but in the assessment phase
938 return false;
939 }
940 $now = time();
941 if (!empty($this->assessmentstart) and $this->assessmentstart > $now) {
942 // if enabled, assessing is not allowed before the date/time defined in the mod_form
943 return false;
944 }
945 if (!empty($this->assessmentend) and $now > $this->assessmentend ) {
946 // if enabled, assessing is not allowed after the date/time defined in the mod_form
947 return false;
948 }
949 // here we go, assessing is allowed
c1e883bb
DM
950 return true;
951 }
952
becec954
DM
953 /**
954 * Are reviewers allowed to create/edit their assessments of the example submissions?
955 *
74bf8a94 956 * Note this does not check other conditions like the number of already submitted examples etc.
becec954 957 *
74bf8a94 958 * @return null|bool
becec954
DM
959 */
960 public function assessing_examples_allowed() {
74bf8a94
DM
961 if (empty($this->useexamples)) {
962 return null;
963 }
964 if (self::EXAMPLES_VOLUNTARY == $this->examplesmode) {
965 return true;
966 }
967 if (self::EXAMPLES_BEFORE_SUBMISSION == $this->examplesmode and self::PHASE_SUBMISSION == $this->phase) {
968 return true;
969 }
970 if (self::EXAMPLES_BEFORE_ASSESSMENT == $this->examplesmode and self::PHASE_ASSESSMENT == $this->phase) {
971 return true;
972 }
973 return false;
becec954 974 }
407b1e91 975
3dc78e5b
DM
976 /**
977 * Are the peer-reviews available to the authors?
978 *
979 * TODO: this depends on the workshop phase
980 *
981 * @return bool
982 */
983 public function assessments_available() {
984 return true;
985 }
986
987 /**
988 * Can the given grades be displayed to the authors?
989 *
990 * Grades are not displayed if {@link self::assessments_available()} return false. The returned
f6e8b318 991 * value may be true (if yes, display grades) or false (no, hide grades yet)
3dc78e5b 992 *
f6e8b318 993 * @return bool
3dc78e5b
DM
994 */
995 public function grades_available() {
996 return true;
997 }
998
b761e6d9
DM
999 /**
1000 * Prepare an individual workshop plan for the given user.
1001 *
f05c168d 1002 * @param int $userid whom the plan is prepared for
7a789aa8
DM
1003 * @param stdclass context of the planned workshop
1004 * @return stdclass data object to be passed to the renderer
b761e6d9 1005 */
d895c6aa 1006 public function prepare_user_plan($userid) {
b761e6d9
DM
1007 global $DB;
1008
1009 $phases = array();
1010
1011 // Prepare tasks for the setup phase
7a789aa8 1012 $phase = new stdclass();
b761e6d9
DM
1013 $phase->title = get_string('phasesetup', 'workshop');
1014 $phase->tasks = array();
d895c6aa 1015 if (has_capability('moodle/course:manageactivities', $this->context, $userid)) {
7a789aa8 1016 $task = new stdclass();
da0b1f70
DM
1017 $task->title = get_string('taskintro', 'workshop');
1018 $task->link = $this->updatemod_url();
1019 $task->completed = !(trim(strip_tags($this->intro)) == '');
1020 $phase->tasks['intro'] = $task;
1021 }
d895c6aa 1022 if (has_capability('moodle/course:manageactivities', $this->context, $userid)) {
7a789aa8 1023 $task = new stdclass();
454e8dd9
DM
1024 $task->title = get_string('taskinstructauthors', 'workshop');
1025 $task->link = $this->updatemod_url();
1026 $task->completed = !(trim(strip_tags($this->instructauthors)) == '');
1027 $phase->tasks['instructauthors'] = $task;
1028 }
d895c6aa 1029 if (has_capability('mod/workshop:editdimensions', $this->context, $userid)) {
7a789aa8 1030 $task = new stdclass();
da0b1f70
DM
1031 $task->title = get_string('editassessmentform', 'workshop');
1032 $task->link = $this->editform_url();
f6e8b318 1033 if ($this->grading_strategy_instance()->form_ready()) {
da0b1f70
DM
1034 $task->completed = true;
1035 } elseif ($this->phase > self::PHASE_SETUP) {
1036 $task->completed = false;
1037 }
b761e6d9
DM
1038 $phase->tasks['editform'] = $task;
1039 }
81eccf0a 1040 if ($this->useexamples and has_capability('mod/workshop:manageexamples', $this->context, $userid)) {
7a789aa8 1041 $task = new stdclass();
81eccf0a
DM
1042 $task->title = get_string('prepareexamples', 'workshop');
1043 if ($DB->count_records('workshop_submissions', array('example' => 1, 'workshopid' => $this->id)) > 0) {
1044 $task->completed = true;
1045 } elseif ($this->phase > self::PHASE_SETUP) {
1046 $task->completed = false;
1047 }
1048 $phase->tasks['prepareexamples'] = $task;
1049 }
da0b1f70
DM
1050 if (empty($phase->tasks) and $this->phase == self::PHASE_SETUP) {
1051 // if we are in the setup phase and there is no task (typical for students), let us
1052 // display some explanation what is going on
7a789aa8 1053 $task = new stdclass();
da0b1f70
DM
1054 $task->title = get_string('undersetup', 'workshop');
1055 $task->completed = 'info';
1056 $phase->tasks['setupinfo'] = $task;
1057 }
b761e6d9
DM
1058 $phases[self::PHASE_SETUP] = $phase;
1059
1060 // Prepare tasks for the submission phase
7a789aa8 1061 $phase = new stdclass();
b761e6d9
DM
1062 $phase->title = get_string('phasesubmission', 'workshop');
1063 $phase->tasks = array();
81eccf0a
DM
1064 if (($this->usepeerassessment or $this->useselfassessment)
1065 and has_capability('moodle/course:manageactivities', $this->context, $userid)) {
7a789aa8 1066 $task = new stdclass();
00aca3c1
DM
1067 $task->title = get_string('taskinstructreviewers', 'workshop');
1068 $task->link = $this->updatemod_url();
1069 if (trim(strip_tags($this->instructreviewers))) {
da0b1f70
DM
1070 $task->completed = true;
1071 } elseif ($this->phase >= self::PHASE_ASSESSMENT) {
1072 $task->completed = false;
da0b1f70 1073 }
00aca3c1 1074 $phase->tasks['instructreviewers'] = $task;
b761e6d9 1075 }
d895c6aa 1076 if (has_capability('mod/workshop:submit', $this->context, $userid, false)) {
7a789aa8 1077 $task = new stdclass();
00aca3c1
DM
1078 $task->title = get_string('tasksubmit', 'workshop');
1079 $task->link = $this->submission_url();
1080 if ($DB->record_exists('workshop_submissions', array('workshopid'=>$this->id, 'example'=>0, 'authorid'=>$userid))) {
da0b1f70
DM
1081 $task->completed = true;
1082 } elseif ($this->phase >= self::PHASE_ASSESSMENT) {
1083 $task->completed = false;
00aca3c1
DM
1084 } else {
1085 $task->completed = null; // still has a chance to submit
da0b1f70 1086 }
00aca3c1 1087 $phase->tasks['submit'] = $task;
da0b1f70 1088 }
d895c6aa 1089 if (has_capability('mod/workshop:allocate', $this->context, $userid)) {
7a789aa8 1090 $task = new stdclass();
da0b1f70
DM
1091 $task->title = get_string('allocate', 'workshop');
1092 $task->link = $this->allocation_url();
d895c6aa 1093 $numofauthors = count(get_users_by_capability($this->context, 'mod/workshop:submit', 'u.id', '', '', '',
a3610b08
DM
1094 '', '', false, true));
1095 $numofsubmissions = $DB->count_records('workshop_submissions', array('workshopid'=>$this->id, 'example'=>0));
1096 $sql = 'SELECT COUNT(s.id) AS nonallocated
1097 FROM {workshop_submissions} s
1098 LEFT JOIN {workshop_assessments} a ON (a.submissionid=s.id)
1099 WHERE s.workshopid = :workshopid AND s.example=0 AND a.submissionid IS NULL';
1100 $params['workshopid'] = $this->id;
1101 $numnonallocated = $DB->count_records_sql($sql, $params);
da0b1f70
DM
1102 if ($numofsubmissions == 0) {
1103 $task->completed = null;
a3610b08 1104 } elseif ($numnonallocated == 0) {
da0b1f70
DM
1105 $task->completed = true;
1106 } elseif ($this->phase > self::PHASE_SUBMISSION) {
1107 $task->completed = false;
1108 } else {
1109 $task->completed = null; // still has a chance to allocate
1110 }
7a789aa8 1111 $a = new stdclass();
3189fb2d
DM
1112 $a->expected = $numofauthors;
1113 $a->submitted = $numofsubmissions;
a3610b08 1114 $a->allocate = $numnonallocated;
3189fb2d 1115 $task->details = get_string('allocatedetails', 'workshop', $a);
da0b1f70 1116 unset($a);
3189fb2d 1117 $phase->tasks['allocate'] = $task;
3dc78e5b
DM
1118
1119 if ($numofsubmissions < $numofauthors and $this->phase >= self::PHASE_SUBMISSION) {
7a789aa8 1120 $task = new stdclass();
3dc78e5b
DM
1121 $task->title = get_string('someuserswosubmission', 'workshop');
1122 $task->completed = 'info';
1123 $phase->tasks['allocateinfo'] = $task;
1124 }
da0b1f70 1125 }
81eccf0a 1126 $phases[self::PHASE_SUBMISSION] = $phase;
b761e6d9
DM
1127
1128 // Prepare tasks for the peer-assessment phase (includes eventual self-assessments)
7a789aa8 1129 $phase = new stdclass();
b761e6d9
DM
1130 $phase->title = get_string('phaseassessment', 'workshop');
1131 $phase->tasks = array();
d895c6aa 1132 $phase->isreviewer = has_capability('mod/workshop:peerassess', $this->context, $userid);
3dc78e5b 1133 $phase->assessments = $this->get_assessments_by_reviewer($userid);
b761e6d9
DM
1134 $numofpeers = 0; // number of allocated peer-assessments
1135 $numofpeerstodo = 0; // number of peer-assessments to do
1136 $numofself = 0; // number of allocated self-assessments - should be 0 or 1
1137 $numofselftodo = 0; // number of self-assessments to do - should be 0 or 1
1138 foreach ($phase->assessments as $a) {
1139 if ($a->authorid == $userid) {
1140 $numofself++;
1141 if (is_null($a->grade)) {
1142 $numofselftodo++;
1143 }
1144 } else {
1145 $numofpeers++;
1146 if (is_null($a->grade)) {
1147 $numofpeerstodo++;
1148 }
1149 }
1150 }
1151 unset($a);
81eccf0a 1152 if ($this->usepeerassessment and $numofpeers) {
7a789aa8 1153 $task = new stdclass();
3dc78e5b
DM
1154 if ($numofpeerstodo == 0) {
1155 $task->completed = true;
1156 } elseif ($this->phase > self::PHASE_ASSESSMENT) {
1157 $task->completed = false;
1158 }
7a789aa8 1159 $a = new stdclass();
b761e6d9
DM
1160 $a->total = $numofpeers;
1161 $a->todo = $numofpeerstodo;
1162 $task->title = get_string('taskassesspeers', 'workshop');
da0b1f70 1163 $task->details = get_string('taskassesspeersdetails', 'workshop', $a);
b761e6d9
DM
1164 unset($a);
1165 $phase->tasks['assesspeers'] = $task;
1166 }
81eccf0a 1167 if ($this->useselfassessment and $numofself) {
7a789aa8 1168 $task = new stdclass();
3dc78e5b
DM
1169 if ($numofselftodo == 0) {
1170 $task->completed = true;
1171 } elseif ($this->phase > self::PHASE_ASSESSMENT) {
1172 $task->completed = false;
1173 }
b761e6d9
DM
1174 $task->title = get_string('taskassessself', 'workshop');
1175 $phase->tasks['assessself'] = $task;
1176 }
1177 $phases[self::PHASE_ASSESSMENT] = $phase;
1178
1fed6ce3 1179 // Prepare tasks for the grading evaluation phase
7a789aa8 1180 $phase = new stdclass();
b761e6d9
DM
1181 $phase->title = get_string('phaseevaluation', 'workshop');
1182 $phase->tasks = array();
1fed6ce3 1183 if (has_capability('mod/workshop:overridegrades', $this->context)) {
f27b70fb
DM
1184 $expected = count($this->get_potential_authors(false));
1185 $calculated = $DB->count_records_select('workshop_submissions',
1186 'workshopid = ? AND (grade IS NOT NULL OR gradeover IS NOT NULL)', array($this->id));
7a789aa8 1187 $task = new stdclass();
10bc4bce 1188 $task->title = get_string('calculatesubmissiongrades', 'workshop');
7a789aa8 1189 $a = new stdclass();
1fed6ce3 1190 $a->expected = $expected;
f27b70fb 1191 $a->calculated = $calculated;
10bc4bce 1192 $task->details = get_string('calculatesubmissiongradesdetails', 'workshop', $a);
f27b70fb 1193 if ($calculated >= $expected) {
1fed6ce3
DM
1194 $task->completed = true;
1195 } elseif ($this->phase > self::PHASE_EVALUATION) {
1196 $task->completed = false;
1197 }
10bc4bce 1198 $phase->tasks['calculatesubmissiongrade'] = $task;
f27b70fb
DM
1199
1200 $expected = count($this->get_potential_reviewers(false));
1201 $calculated = $DB->count_records_select('workshop_aggregations',
1202 'workshopid = ? AND gradinggrade IS NOT NULL', array($this->id));
7a789aa8 1203 $task = new stdclass();
f27b70fb 1204 $task->title = get_string('calculategradinggrades', 'workshop');
7a789aa8 1205 $a = new stdclass();
f27b70fb
DM
1206 $a->expected = $expected;
1207 $a->calculated = $calculated;
1208 $task->details = get_string('calculategradinggradesdetails', 'workshop', $a);
1209 if ($calculated >= $expected) {
1210 $task->completed = true;
1211 } elseif ($this->phase > self::PHASE_EVALUATION) {
1212 $task->completed = false;
f55650e6 1213 }
f27b70fb
DM
1214 $phase->tasks['calculategradinggrade'] = $task;
1215
d183140d 1216 } elseif ($this->phase == self::PHASE_EVALUATION) {
7a789aa8 1217 $task = new stdclass();
1fed6ce3
DM
1218 $task->title = get_string('evaluategradeswait', 'workshop');
1219 $task->completed = 'info';
1220 $phase->tasks['evaluateinfo'] = $task;
1221 }
b761e6d9
DM
1222 $phases[self::PHASE_EVALUATION] = $phase;
1223
1224 // Prepare tasks for the "workshop closed" phase - todo
7a789aa8 1225 $phase = new stdclass();
b761e6d9
DM
1226 $phase->title = get_string('phaseclosed', 'workshop');
1227 $phase->tasks = array();
1228 $phases[self::PHASE_CLOSED] = $phase;
1229
1230 // Polish data, set default values if not done explicitly
1231 foreach ($phases as $phasecode => $phase) {
1232 $phase->title = isset($phase->title) ? $phase->title : '';
1233 $phase->tasks = isset($phase->tasks) ? $phase->tasks : array();
1234 if ($phasecode == $this->phase) {
1235 $phase->active = true;
1236 } else {
1237 $phase->active = false;
1238 }
454e8dd9
DM
1239 if (!isset($phase->actions)) {
1240 $phase->actions = array();
1241 }
b761e6d9
DM
1242
1243 foreach ($phase->tasks as $taskcode => $task) {
1244 $task->title = isset($task->title) ? $task->title : '';
da0b1f70
DM
1245 $task->link = isset($task->link) ? $task->link : null;
1246 $task->details = isset($task->details) ? $task->details : '';
b761e6d9
DM
1247 $task->completed = isset($task->completed) ? $task->completed : null;
1248 }
1249 }
454e8dd9
DM
1250
1251 // Add phase swithing actions
d895c6aa 1252 if (has_capability('mod/workshop:switchphase', $this->context, $userid)) {
454e8dd9
DM
1253 foreach ($phases as $phasecode => $phase) {
1254 if (! $phase->active) {
7a789aa8 1255 $action = new stdclass();
454e8dd9
DM
1256 $action->type = 'switchphase';
1257 $action->url = $this->switchphase_url($phasecode);
1258 $phase->actions[] = $action;
1259 }
1260 }
1261 }
1262
b761e6d9
DM
1263 return $phases;
1264 }
1265
454e8dd9
DM
1266 /**
1267 * Switch to a new workshop phase
1268 *
1269 * Modifies the underlying database record. You should terminate the script shortly after calling this.
1270 *
1271 * @param int $newphase new phase code
1272 * @return bool true if success, false otherwise
1273 */
1274 public function switch_phase($newphase) {
1275 global $DB;
1276
365c2cc2 1277 $known = $this->available_phases_list();
454e8dd9
DM
1278 if (!isset($known[$newphase])) {
1279 return false;
1280 }
f6e8b318
DM
1281
1282 if (self::PHASE_CLOSED == $newphase) {
f27b70fb 1283 // push the grades into the gradebook
7a789aa8 1284 $workshop = new stdclass();
10bc4bce
DM
1285 foreach ($this as $property => $value) {
1286 $workshop->{$property} = $value;
1287 }
1288 $workshop->course = $this->course->id;
1289 $workshop->cmidnumber = $this->cm->id;
1290 $workshop->modname = 'workshop';
1291 workshop_update_grades($workshop);
f6e8b318
DM
1292 }
1293
454e8dd9
DM
1294 $DB->set_field('workshop', 'phase', $newphase, array('id' => $this->id));
1295 return true;
1296 }
ddb59c77
DM
1297
1298 /**
1299 * Saves a raw grade for submission as calculated from the assessment form fields
1300 *
1301 * @param array $assessmentid assessment record id, must exists
00aca3c1 1302 * @param mixed $grade raw percentual grade from 0.00000 to 100.00000
ddb59c77
DM
1303 * @return false|float the saved grade
1304 */
1305 public function set_peer_grade($assessmentid, $grade) {
1306 global $DB;
1307
1308 if (is_null($grade)) {
1309 return false;
1310 }
7a789aa8 1311 $data = new stdclass();
ddb59c77
DM
1312 $data->id = $assessmentid;
1313 $data->grade = $grade;
1314 $DB->update_record('workshop_assessments', $data);
1315 return $grade;
1316 }
6516b9e9 1317
29dc43e7
DM
1318 /**
1319 * Prepares data object with all workshop grades to be rendered
1320 *
5e71cefb
DM
1321 * @param int $userid the user we are preparing the report for
1322 * @param mixed $groups single group or array of groups - only show users who are in one of these group(s). Defaults to all
29dc43e7 1323 * @param int $page the current page (for the pagination)
5e71cefb 1324 * @param int $perpage participants per page (for the pagination)
f27b70fb 1325 * @param string $sortby lastname|firstname|submissiontitle|submissiongrade|gradinggrade
5e71cefb 1326 * @param string $sorthow ASC|DESC
7a789aa8 1327 * @return stdclass data for the renderer
29dc43e7 1328 */
d895c6aa 1329 public function prepare_grading_report($userid, $groups, $page, $perpage, $sortby, $sorthow) {
29dc43e7
DM
1330 global $DB;
1331
d895c6aa
DM
1332 $canviewall = has_capability('mod/workshop:viewallassessments', $this->context, $userid);
1333 $isparticipant = has_any_capability(array('mod/workshop:submit', 'mod/workshop:peerassess'), $this->context, $userid);
29dc43e7
DM
1334
1335 if (!$canviewall and !$isparticipant) {
1336 // who the hell is this?
1337 return array();
1338 }
1339
f27b70fb 1340 if (!in_array($sortby, array('lastname','firstname','submissiontitle','submissiongrade','gradinggrade'))) {
5e71cefb
DM
1341 $sortby = 'lastname';
1342 }
1343
1344 if (!($sorthow === 'ASC' or $sorthow === 'DESC')) {
1345 $sorthow = 'ASC';
1346 }
1347
1348 // get the list of user ids to be displayed
29dc43e7
DM
1349 if ($canviewall) {
1350 // fetch the list of ids of all workshop participants - this may get really long so fetch just id
d895c6aa 1351 $participants = get_users_by_capability($this->context, array('mod/workshop:submit', 'mod/workshop:peerassess'),
5e71cefb 1352 'u.id', '', '', '', $groups, '', false, false, true);
29dc43e7
DM
1353 } else {
1354 // this is an ordinary workshop participant (aka student) - display the report just for him/her
1355 $participants = array($userid => (object)array('id' => $userid));
1356 }
1357
5e71cefb 1358 // we will need to know the number of all records later for the pagination purposes
29dc43e7
DM
1359 $numofparticipants = count($participants);
1360
deea6e7a
DM
1361 if ($numofparticipants > 0) {
1362 // load all fields which can be used for sorting and paginate the records
1363 list($participantids, $params) = $DB->get_in_or_equal(array_keys($participants), SQL_PARAMS_NAMED);
1364 $params['workshopid1'] = $this->id;
1365 $params['workshopid2'] = $this->id;
1366 $sqlsort = $sortby . ' ' . $sorthow . ',u.lastname,u.firstname,u.id';
1367 $sql = "SELECT u.id AS userid,u.firstname,u.lastname,u.picture,u.imagealt,
1368 s.title AS submissiontitle, s.grade AS submissiongrade, ag.gradinggrade
1369 FROM {user} u
1370 LEFT JOIN {workshop_submissions} s ON (s.authorid = u.id AND s.workshopid = :workshopid1 AND s.example = 0)
1371 LEFT JOIN {workshop_aggregations} ag ON (ag.userid = u.id AND ag.workshopid = :workshopid2)
1372 WHERE u.id $participantids
1373 ORDER BY $sqlsort";
1374 $participants = $DB->get_records_sql($sql, $params, $page * $perpage, $perpage);
1375 } else {
1376 $participants = array();
1377 }
29dc43e7
DM
1378
1379 // this will hold the information needed to display user names and pictures
5e71cefb
DM
1380 $userinfo = array();
1381
1382 // get the user details for all participants to display
1383 foreach ($participants as $participant) {
1384 if (!isset($userinfo[$participant->userid])) {
7a789aa8 1385 $userinfo[$participant->userid] = new stdclass();
5e71cefb
DM
1386 $userinfo[$participant->userid]->id = $participant->userid;
1387 $userinfo[$participant->userid]->firstname = $participant->firstname;
1388 $userinfo[$participant->userid]->lastname = $participant->lastname;
1389 $userinfo[$participant->userid]->picture = $participant->picture;
1390 $userinfo[$participant->userid]->imagealt = $participant->imagealt;
1391 }
1392 }
29dc43e7 1393
5e71cefb 1394 // load the submissions details
29dc43e7 1395 $submissions = $this->get_submissions(array_keys($participants));
5e71cefb
DM
1396
1397 // get the user details for all moderators (teachers) that have overridden a submission grade
29dc43e7 1398 foreach ($submissions as $submission) {
29dc43e7 1399 if (!isset($userinfo[$submission->gradeoverby])) {
7a789aa8 1400 $userinfo[$submission->gradeoverby] = new stdclass();
29dc43e7
DM
1401 $userinfo[$submission->gradeoverby]->id = $submission->gradeoverby;
1402 $userinfo[$submission->gradeoverby]->firstname = $submission->overfirstname;
1403 $userinfo[$submission->gradeoverby]->lastname = $submission->overlastname;
1404 $userinfo[$submission->gradeoverby]->picture = $submission->overpicture;
1405 $userinfo[$submission->gradeoverby]->imagealt = $submission->overimagealt;
1406 }
1407 }
1408
5e71cefb 1409 // get the user details for all reviewers of the displayed participants
29dc43e7
DM
1410 $reviewers = array();
1411 if ($submissions) {
1412 list($submissionids, $params) = $DB->get_in_or_equal(array_keys($submissions), SQL_PARAMS_NAMED);
581878b8 1413 $sql = "SELECT a.id AS assessmentid, a.submissionid, a.grade, a.gradinggrade, a.gradinggradeover, a.weight,
29dc43e7
DM
1414 r.id AS reviewerid, r.lastname, r.firstname, r.picture, r.imagealt,
1415 s.id AS submissionid, s.authorid
1416 FROM {workshop_assessments} a
1417 JOIN {user} r ON (a.reviewerid = r.id)
1418 JOIN {workshop_submissions} s ON (a.submissionid = s.id)
1419 WHERE a.submissionid $submissionids";
1420 $reviewers = $DB->get_records_sql($sql, $params);
1421 foreach ($reviewers as $reviewer) {
1422 if (!isset($userinfo[$reviewer->reviewerid])) {
7a789aa8 1423 $userinfo[$reviewer->reviewerid] = new stdclass();
29dc43e7
DM
1424 $userinfo[$reviewer->reviewerid]->id = $reviewer->reviewerid;
1425 $userinfo[$reviewer->reviewerid]->firstname = $reviewer->firstname;
1426 $userinfo[$reviewer->reviewerid]->lastname = $reviewer->lastname;
1427 $userinfo[$reviewer->reviewerid]->picture = $reviewer->picture;
1428 $userinfo[$reviewer->reviewerid]->imagealt = $reviewer->imagealt;
1429 }
1430 }
1431 }
1432
5e71cefb 1433 // get the user details for all reviewees of the displayed participants
934329e5
DM
1434 $reviewees = array();
1435 if ($participants) {
1436 list($participantids, $params) = $DB->get_in_or_equal(array_keys($participants), SQL_PARAMS_NAMED);
1437 $params['workshopid'] = $this->id;
581878b8 1438 $sql = "SELECT a.id AS assessmentid, a.submissionid, a.grade, a.gradinggrade, a.gradinggradeover, a.reviewerid, a.weight,
934329e5
DM
1439 s.id AS submissionid,
1440 e.id AS authorid, e.lastname, e.firstname, e.picture, e.imagealt
1441 FROM {user} u
1442 JOIN {workshop_assessments} a ON (a.reviewerid = u.id)
1443 JOIN {workshop_submissions} s ON (a.submissionid = s.id)
1444 JOIN {user} e ON (s.authorid = e.id)
1445 WHERE u.id $participantids AND s.workshopid = :workshopid";
1446 $reviewees = $DB->get_records_sql($sql, $params);
1447 foreach ($reviewees as $reviewee) {
1448 if (!isset($userinfo[$reviewee->authorid])) {
7a789aa8 1449 $userinfo[$reviewee->authorid] = new stdclass();
934329e5
DM
1450 $userinfo[$reviewee->authorid]->id = $reviewee->authorid;
1451 $userinfo[$reviewee->authorid]->firstname = $reviewee->firstname;
1452 $userinfo[$reviewee->authorid]->lastname = $reviewee->lastname;
1453 $userinfo[$reviewee->authorid]->picture = $reviewee->picture;
1454 $userinfo[$reviewee->authorid]->imagealt = $reviewee->imagealt;
1455 }
29dc43e7
DM
1456 }
1457 }
1458
5e71cefb
DM
1459 // finally populate the object to be rendered
1460 $grades = $participants;
29dc43e7
DM
1461
1462 foreach ($participants as $participant) {
1463 // set up default (null) values
d183140d
DM
1464 $grades[$participant->userid]->submissionid = null;
1465 $grades[$participant->userid]->submissiontitle = null;
1466 $grades[$participant->userid]->submissiongrade = null;
1467 $grades[$participant->userid]->submissiongradeover = null;
1468 $grades[$participant->userid]->submissiongradeoverby = null;
5e71cefb
DM
1469 $grades[$participant->userid]->reviewedby = array();
1470 $grades[$participant->userid]->reviewerof = array();
29dc43e7
DM
1471 }
1472 unset($participants);
1473 unset($participant);
1474
1475 foreach ($submissions as $submission) {
1476 $grades[$submission->authorid]->submissionid = $submission->id;
1477 $grades[$submission->authorid]->submissiontitle = $submission->title;
b4857acb
DM
1478 $grades[$submission->authorid]->submissiongrade = $this->real_grade($submission->grade);
1479 $grades[$submission->authorid]->submissiongradeover = $this->real_grade($submission->gradeover);
29dc43e7
DM
1480 $grades[$submission->authorid]->submissiongradeoverby = $submission->gradeoverby;
1481 }
1482 unset($submissions);
1483 unset($submission);
1484
1485 foreach($reviewers as $reviewer) {
7a789aa8 1486 $info = new stdclass();
29dc43e7
DM
1487 $info->userid = $reviewer->reviewerid;
1488 $info->assessmentid = $reviewer->assessmentid;
1489 $info->submissionid = $reviewer->submissionid;
b4857acb
DM
1490 $info->grade = $this->real_grade($reviewer->grade);
1491 $info->gradinggrade = $this->real_grading_grade($reviewer->gradinggrade);
1492 $info->gradinggradeover = $this->real_grading_grade($reviewer->gradinggradeover);
581878b8 1493 $info->weight = $reviewer->weight;
29dc43e7
DM
1494 $grades[$reviewer->authorid]->reviewedby[$reviewer->reviewerid] = $info;
1495 }
1496 unset($reviewers);
1497 unset($reviewer);
1498
1499 foreach($reviewees as $reviewee) {
7a789aa8 1500 $info = new stdclass();
29dc43e7
DM
1501 $info->userid = $reviewee->authorid;
1502 $info->assessmentid = $reviewee->assessmentid;
1503 $info->submissionid = $reviewee->submissionid;
b4857acb
DM
1504 $info->grade = $this->real_grade($reviewee->grade);
1505 $info->gradinggrade = $this->real_grading_grade($reviewee->gradinggrade);
1506 $info->gradinggradeover = $this->real_grading_grade($reviewee->gradinggradeover);
581878b8 1507 $info->weight = $reviewee->weight;
29dc43e7
DM
1508 $grades[$reviewee->reviewerid]->reviewerof[$reviewee->authorid] = $info;
1509 }
1510 unset($reviewees);
1511 unset($reviewee);
1512
b4857acb
DM
1513 foreach ($grades as $grade) {
1514 $grade->gradinggrade = $this->real_grading_grade($grade->gradinggrade);
b4857acb
DM
1515 }
1516
7a789aa8 1517 $data = new stdclass();
29dc43e7
DM
1518 $data->grades = $grades;
1519 $data->userinfo = $userinfo;
1520 $data->totalcount = $numofparticipants;
b4857acb
DM
1521 $data->maxgrade = $this->real_grade(100);
1522 $data->maxgradinggrade = $this->real_grading_grade(100);
29dc43e7
DM
1523 return $data;
1524 }
1525
29dc43e7 1526 /**
b4857acb 1527 * Calculates the real value of a grade
29dc43e7 1528 *
b4857acb
DM
1529 * @param float $value percentual value from 0 to 100
1530 * @param float $max the maximal grade
1531 * @return string
1532 */
1533 public function real_grade_value($value, $max) {
1534 $localized = true;
557a1100 1535 if (is_null($value) or $value === '') {
b4857acb
DM
1536 return null;
1537 } elseif ($max == 0) {
1538 return 0;
1539 } else {
1540 return format_float($max * $value / 100, $this->gradedecimals, $localized);
1541 }
1542 }
1543
e554671d
DM
1544 /**
1545 * Calculates the raw (percentual) value from a real grade
1546 *
1547 * This is used in cases when a user wants to give a grade such as 12 of 20 and we need to save
1548 * this value in a raw percentual form into DB
1549 * @param float $value given grade
1550 * @param float $max the maximal grade
1551 * @return float suitable to be stored as numeric(10,5)
1552 */
1553 public function raw_grade_value($value, $max) {
557a1100 1554 if (is_null($value) or $value === '') {
e554671d
DM
1555 return null;
1556 }
1557 if ($max == 0 or $value < 0) {
1558 return 0;
1559 }
1560 $p = $value / $max * 100;
1561 if ($p > 100) {
1562 return $max;
1563 }
1564 return grade_floatval($p);
1565 }
1566
b4857acb
DM
1567 /**
1568 * Calculates the real value of grade for submission
1569 *
1570 * @param float $value percentual value from 0 to 100
1571 * @return string
1572 */
1573 public function real_grade($value) {
1574 return $this->real_grade_value($value, $this->grade);
1575 }
1576
1577 /**
1578 * Calculates the real value of grade for assessment
1579 *
1580 * @param float $value percentual value from 0 to 100
1581 * @return string
1582 */
1583 public function real_grading_grade($value) {
1584 return $this->real_grade_value($value, $this->gradinggrade);
29dc43e7
DM
1585 }
1586
89c1aa97 1587 /**
e9a90e69 1588 * Calculates grades for submission for the given participant(s) and updates it in the database
89c1aa97
DM
1589 *
1590 * @param null|int|array $restrict If null, update all authors, otherwise update just grades for the given author(s)
1591 * @return void
1592 */
8a1ba8ac 1593 public function aggregate_submission_grades($restrict=null) {
89c1aa97
DM
1594 global $DB;
1595
1596 // fetch a recordset with all assessments to process
1696f36c 1597 $sql = 'SELECT s.id AS submissionid, s.grade AS submissiongrade,
89c1aa97
DM
1598 a.weight, a.grade
1599 FROM {workshop_submissions} s
1600 LEFT JOIN {workshop_assessments} a ON (a.submissionid = s.id)
1601 WHERE s.example=0 AND s.workshopid=:workshopid'; // to be cont.
1602 $params = array('workshopid' => $this->id);
1603
1604 if (is_null($restrict)) {
1605 // update all users - no more conditions
1606 } elseif (!empty($restrict)) {
1607 list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED);
1608 $sql .= " AND s.authorid $usql";
1609 $params = array_merge($params, $uparams);
1610 } else {
1611 throw new coding_exception('Empty value is not a valid parameter here');
1612 }
1613
1614 $sql .= ' ORDER BY s.id'; // this is important for bulk processing
89c1aa97 1615
e9a90e69
DM
1616 $rs = $DB->get_recordset_sql($sql, $params);
1617 $batch = array(); // will contain a set of all assessments of a single submission
1618 $previous = null; // a previous record in the recordset
1619
89c1aa97
DM
1620 foreach ($rs as $current) {
1621 if (is_null($previous)) {
1622 // we are processing the very first record in the recordset
1623 $previous = $current;
89c1aa97 1624 }
e9a90e69 1625 if ($current->submissionid == $previous->submissionid) {
89c1aa97 1626 // we are still processing the current submission
e9a90e69
DM
1627 $batch[] = $current;
1628 } else {
1629 // process all the assessments of a sigle submission
1630 $this->aggregate_submission_grades_process($batch);
1631 // and then start to process another submission
1632 $batch = array($current);
1633 $previous = $current;
89c1aa97
DM
1634 }
1635 }
e9a90e69
DM
1636 // do not forget to process the last batch!
1637 $this->aggregate_submission_grades_process($batch);
89c1aa97
DM
1638 $rs->close();
1639 }
1640
1641 /**
1642 * Calculates grades for assessment for the given participant(s)
1643 *
39411930
DM
1644 * Grade for assessment is calculated as a simple mean of all grading grades calculated by the grading evaluator.
1645 * The assessment weight is not taken into account here.
89c1aa97
DM
1646 *
1647 * @param null|int|array $restrict If null, update all reviewers, otherwise update just grades for the given reviewer(s)
1648 * @return void
1649 */
8a1ba8ac 1650 public function aggregate_grading_grades($restrict=null) {
89c1aa97
DM
1651 global $DB;
1652
39411930
DM
1653 // fetch a recordset with all assessments to process
1654 $sql = 'SELECT a.reviewerid, a.gradinggrade, a.gradinggradeover,
1655 ag.id AS aggregationid, ag.gradinggrade AS aggregatedgrade
1656 FROM {workshop_assessments} a
1657 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
1658 LEFT JOIN {workshop_aggregations} ag ON (ag.userid = a.reviewerid AND ag.workshopid = s.workshopid)
1659 WHERE s.example=0 AND s.workshopid=:workshopid'; // to be cont.
1660 $params = array('workshopid' => $this->id);
1661
1662 if (is_null($restrict)) {
1663 // update all users - no more conditions
1664 } elseif (!empty($restrict)) {
1665 list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED);
1666 $sql .= " AND a.reviewerid $usql";
1667 $params = array_merge($params, $uparams);
1668 } else {
1669 throw new coding_exception('Empty value is not a valid parameter here');
1670 }
1671
1672 $sql .= ' ORDER BY a.reviewerid'; // this is important for bulk processing
1673
1674 $rs = $DB->get_recordset_sql($sql, $params);
1675 $batch = array(); // will contain a set of all assessments of a single submission
1676 $previous = null; // a previous record in the recordset
1677
1678 foreach ($rs as $current) {
1679 if (is_null($previous)) {
1680 // we are processing the very first record in the recordset
1681 $previous = $current;
1682 }
1683 if ($current->reviewerid == $previous->reviewerid) {
1684 // we are still processing the current reviewer
1685 $batch[] = $current;
1686 } else {
1687 // process all the assessments of a sigle submission
1688 $this->aggregate_grading_grades_process($batch);
1689 // and then start to process another reviewer
1690 $batch = array($current);
1691 $previous = $current;
1692 }
1693 }
1694 // do not forget to process the last batch!
1695 $this->aggregate_grading_grades_process($batch);
1696 $rs->close();
89c1aa97
DM
1697 }
1698
77f43e7d 1699 /**
f6e8b318 1700 * Returns the mform the teachers use to put a feedback for the reviewer
77f43e7d 1701 *
f6e8b318 1702 * @return workshop_feedbackreviewer_form
77f43e7d 1703 */
7a789aa8 1704 public function get_feedbackreviewer_form(moodle_url $actionurl, stdclass $assessment, $editable=true) {
77f43e7d
DM
1705 global $CFG;
1706 require_once(dirname(__FILE__) . '/feedbackreviewer_form.php');
1707
7a789aa8 1708 $current = new stdclass();
e554671d
DM
1709 $current->asid = $assessment->id;
1710 $current->gradinggrade = $this->real_grading_grade($assessment->gradinggrade);
1711 $current->gradinggradeover = $this->real_grading_grade($assessment->gradinggradeover);
1712 $current->feedbackreviewer = $assessment->feedbackreviewer;
1713 $current->feedbackreviewerformat = $assessment->feedbackreviewerformat;
1714 if (is_null($current->gradinggrade)) {
1715 $current->gradinggrade = get_string('nullgrade', 'workshop');
1716 }
1717
1718 // prepare wysiwyg editor
1719 $current = file_prepare_standard_editor($current, 'feedbackreviewer', array());
1720
77f43e7d 1721 return new workshop_feedbackreviewer_form($actionurl,
e554671d 1722 array('workshop' => $this, 'current' => $current, 'feedbackopts' => array()),
77f43e7d
DM
1723 'post', '', null, $editable);
1724 }
1725
67cd00ba
DM
1726 /**
1727 * Returns the mform the teachers use to put a feedback for the author on their submission
1728 *
1729 * @return workshop_feedbackauthor_form
1730 */
7a789aa8 1731 public function get_feedbackauthor_form(moodle_url $actionurl, stdclass $submission, $editable=true) {
67cd00ba
DM
1732 global $CFG;
1733 require_once(dirname(__FILE__) . '/feedbackauthor_form.php');
1734
7a789aa8 1735 $current = new stdclass();
67cd00ba 1736 $current->submissionid = $submission->id;
557a1100
DM
1737 $current->grade = $this->real_grade($submission->grade);
1738 $current->gradeover = $this->real_grade($submission->gradeover);
1739 $current->feedbackauthor = $submission->feedbackauthor;
1740 $current->feedbackauthorformat = $submission->feedbackauthorformat;
67cd00ba
DM
1741 if (is_null($current->grade)) {
1742 $current->grade = get_string('nullgrade', 'workshop');
1743 }
1744
1745 // prepare wysiwyg editor
1746 $current = file_prepare_standard_editor($current, 'feedbackauthor', array());
1747
1748 return new workshop_feedbackauthor_form($actionurl,
1749 array('workshop' => $this, 'current' => $current, 'feedbackopts' => array()),
1750 'post', '', null, $editable);
1751 }
1752
aa40adbf
DM
1753 ////////////////////////////////////////////////////////////////////////////////
1754 // Internal methods (implementation details) //
1755 ////////////////////////////////////////////////////////////////////////////////
6516b9e9 1756
e9a90e69
DM
1757 /**
1758 * Given an array of all assessments of a single submission, calculates the final grade for this submission
1759 *
1760 * This calculates the weighted mean of the passed assessment grades. If, however, the submission grade
1761 * was overridden by a teacher, the gradeover value is returned and the rest of grades are ignored.
1762 *
7a789aa8 1763 * @param array $assessments of stdclass(->submissionid ->submissiongrade ->gradeover ->weight ->grade)
1fed6ce3 1764 * @return void
e9a90e69
DM
1765 */
1766 protected function aggregate_submission_grades_process(array $assessments) {
1767 global $DB;
1768
1769 $submissionid = null; // the id of the submission being processed
1770 $current = null; // the grade currently saved in database
1771 $finalgrade = null; // the new grade to be calculated
1772 $sumgrades = 0;
1773 $sumweights = 0;
1774
1775 foreach ($assessments as $assessment) {
1776 if (is_null($submissionid)) {
1777 // the id is the same in all records, fetch it during the first loop cycle
1778 $submissionid = $assessment->submissionid;
1779 }
1780 if (is_null($current)) {
1781 // the currently saved grade is the same in all records, fetch it during the first loop cycle
1782 $current = $assessment->submissiongrade;
1783 }
e9a90e69
DM
1784 if (is_null($assessment->grade)) {
1785 // this was not assessed yet
1786 continue;
1787 }
1788 if ($assessment->weight == 0) {
1789 // this does not influence the calculation
1790 continue;
1791 }
1792 $sumgrades += $assessment->grade * $assessment->weight;
1793 $sumweights += $assessment->weight;
1794 }
1795 if ($sumweights > 0 and is_null($finalgrade)) {
1796 $finalgrade = grade_floatval($sumgrades / $sumweights);
1797 }
1798 // check if the new final grade differs from the one stored in the database
1799 if (grade_floats_different($finalgrade, $current)) {
1800 // we need to save new calculation into the database
7a789aa8 1801 $record = new stdclass();
10bc4bce
DM
1802 $record->id = $submissionid;
1803 $record->grade = $finalgrade;
1804 $record->timegraded = time();
1805 $DB->update_record('workshop_submissions', $record);
e9a90e69
DM
1806 }
1807 }
1808
39411930
DM
1809 /**
1810 * Given an array of all assessments done by a single reviewer, calculates the final grading grade
1811 *
1812 * This calculates the simple mean of the passed grading grades. If, however, the grading grade
1813 * was overridden by a teacher, the gradinggradeover value is returned and the rest of grades are ignored.
1814 *
7a789aa8 1815 * @param array $assessments of stdclass(->reviewerid ->gradinggrade ->gradinggradeover ->aggregationid ->aggregatedgrade)
1fed6ce3 1816 * @return void
39411930
DM
1817 */
1818 protected function aggregate_grading_grades_process(array $assessments) {
1819 global $DB;
1820
1821 $reviewerid = null; // the id of the reviewer being processed
1822 $current = null; // the gradinggrade currently saved in database
1823 $finalgrade = null; // the new grade to be calculated
1824 $agid = null; // aggregation id
1825 $sumgrades = 0;
1826 $count = 0;
1827
1828 foreach ($assessments as $assessment) {
1829 if (is_null($reviewerid)) {
1830 // the id is the same in all records, fetch it during the first loop cycle
1831 $reviewerid = $assessment->reviewerid;
1832 }
1833 if (is_null($agid)) {
1834 // the id is the same in all records, fetch it during the first loop cycle
1835 $agid = $assessment->aggregationid;
1836 }
1837 if (is_null($current)) {
1838 // the currently saved grade is the same in all records, fetch it during the first loop cycle
1839 $current = $assessment->aggregatedgrade;
1840 }
1841 if (!is_null($assessment->gradinggradeover)) {
1842 // the grading grade for this assessment is overriden by a teacher
1843 $sumgrades += $assessment->gradinggradeover;
1844 $count++;
1845 } else {
1846 if (!is_null($assessment->gradinggrade)) {
1847 $sumgrades += $assessment->gradinggrade;
1848 $count++;
1849 }
1850 }
1851 }
1852 if ($count > 0) {
1853 $finalgrade = grade_floatval($sumgrades / $count);
1854 }
1855 // check if the new final grade differs from the one stored in the database
1856 if (grade_floats_different($finalgrade, $current)) {
1857 // we need to save new calculation into the database
1858 if (is_null($agid)) {
1859 // no aggregation record yet
7a789aa8 1860 $record = new stdclass();
39411930
DM
1861 $record->workshopid = $this->id;
1862 $record->userid = $reviewerid;
1863 $record->gradinggrade = $finalgrade;
10bc4bce 1864 $record->timegraded = time();
39411930
DM
1865 $DB->insert_record('workshop_aggregations', $record);
1866 } else {
7a789aa8 1867 $record = new stdclass();
10bc4bce
DM
1868 $record->id = $agid;
1869 $record->gradinggrade = $finalgrade;
1870 $record->timegraded = time();
1871 $DB->update_record('workshop_aggregations', $record);
39411930
DM
1872 }
1873 }
1874 }
1875
6516b9e9 1876 /**
aa40adbf 1877 * Given a list of user ids, returns the filtered one containing just ids of users with own submission
6516b9e9 1878 *
aa40adbf
DM
1879 * Example submissions are ignored.
1880 *
1881 * @param array $userids
6516b9e9
DM
1882 * @return array
1883 */
aa40adbf
DM
1884 protected function users_with_submission(array $userids) {
1885 global $DB;
1886
1887 if (empty($userids)) {
1888 return array();
1889 }
1890 $userswithsubmission = array();
1891 list($usql, $uparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
00aca3c1 1892 $sql = "SELECT id,authorid
aa40adbf 1893 FROM {workshop_submissions}
00aca3c1 1894 WHERE example = 0 AND workshopid = :workshopid AND authorid $usql";
aa40adbf
DM
1895 $params = array('workshopid' => $this->id);
1896 $params = array_merge($params, $uparams);
1897 $submissions = $DB->get_records_sql($sql, $params);
1898 foreach ($submissions as $submission) {
00aca3c1 1899 $userswithsubmission[$submission->authorid] = true;
aa40adbf
DM
1900 }
1901
1902 return $userswithsubmission;
6516b9e9
DM
1903 }
1904
aa40adbf
DM
1905 /**
1906 * @return array of available workshop phases
1907 */
365c2cc2 1908 protected function available_phases_list() {
aa40adbf
DM
1909 return array(
1910 self::PHASE_SETUP => true,
1911 self::PHASE_SUBMISSION => true,
1912 self::PHASE_ASSESSMENT => true,
1913 self::PHASE_EVALUATION => true,
1914 self::PHASE_CLOSED => true,
1915 );
1916 }
1917
66c9894d 1918}