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