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