workshop uses plain checkbox instead of advcheckbox
[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 /**
67cd00ba 648 * @return moodle_url of the page to view a submission, defaults to the own one
39861053 649 */
67cd00ba 650 public function submission_url($id=null) {
39861053 651 global $CFG;
67cd00ba 652 return new moodle_url($CFG->wwwroot . '/mod/workshop/submission.php', array('cmid' => $this->cm->id, 'id' => $id));
39861053
DM
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 908 if (has_capability('mod/workshop:overridegrades', $this->context)) {
f27b70fb
DM
909 $expected = count($this->get_potential_authors(false));
910 $calculated = $DB->count_records_select('workshop_submissions',
911 'workshopid = ? AND (grade IS NOT NULL OR gradeover IS NOT NULL)', array($this->id));
1fed6ce3 912 $task = new stdClass();
10bc4bce 913 $task->title = get_string('calculatesubmissiongrades', 'workshop');
1fed6ce3
DM
914 $a = new stdClass();
915 $a->expected = $expected;
f27b70fb 916 $a->calculated = $calculated;
10bc4bce 917 $task->details = get_string('calculatesubmissiongradesdetails', 'workshop', $a);
f27b70fb 918 if ($calculated >= $expected) {
1fed6ce3
DM
919 $task->completed = true;
920 } elseif ($this->phase > self::PHASE_EVALUATION) {
921 $task->completed = false;
922 }
10bc4bce 923 $phase->tasks['calculatesubmissiongrade'] = $task;
f27b70fb
DM
924
925 $expected = count($this->get_potential_reviewers(false));
926 $calculated = $DB->count_records_select('workshop_aggregations',
927 'workshopid = ? AND gradinggrade IS NOT NULL', array($this->id));
928 $task = new stdClass();
929 $task->title = get_string('calculategradinggrades', 'workshop');
930 $a = new stdClass();
931 $a->expected = $expected;
932 $a->calculated = $calculated;
933 $task->details = get_string('calculategradinggradesdetails', 'workshop', $a);
934 if ($calculated >= $expected) {
935 $task->completed = true;
936 } elseif ($this->phase > self::PHASE_EVALUATION) {
937 $task->completed = false;
f55650e6 938 }
f27b70fb
DM
939 $phase->tasks['calculategradinggrade'] = $task;
940
d183140d 941 } elseif ($this->phase == self::PHASE_EVALUATION) {
1fed6ce3
DM
942 $task = new stdClass();
943 $task->title = get_string('evaluategradeswait', 'workshop');
944 $task->completed = 'info';
945 $phase->tasks['evaluateinfo'] = $task;
946 }
b761e6d9
DM
947 $phases[self::PHASE_EVALUATION] = $phase;
948
949 // Prepare tasks for the "workshop closed" phase - todo
950 $phase = new stdClass();
951 $phase->title = get_string('phaseclosed', 'workshop');
952 $phase->tasks = array();
953 $phases[self::PHASE_CLOSED] = $phase;
954
955 // Polish data, set default values if not done explicitly
956 foreach ($phases as $phasecode => $phase) {
957 $phase->title = isset($phase->title) ? $phase->title : '';
958 $phase->tasks = isset($phase->tasks) ? $phase->tasks : array();
959 if ($phasecode == $this->phase) {
960 $phase->active = true;
961 } else {
962 $phase->active = false;
963 }
454e8dd9
DM
964 if (!isset($phase->actions)) {
965 $phase->actions = array();
966 }
b761e6d9
DM
967
968 foreach ($phase->tasks as $taskcode => $task) {
969 $task->title = isset($task->title) ? $task->title : '';
da0b1f70
DM
970 $task->link = isset($task->link) ? $task->link : null;
971 $task->details = isset($task->details) ? $task->details : '';
b761e6d9
DM
972 $task->completed = isset($task->completed) ? $task->completed : null;
973 }
974 }
454e8dd9
DM
975
976 // Add phase swithing actions
d895c6aa 977 if (has_capability('mod/workshop:switchphase', $this->context, $userid)) {
454e8dd9
DM
978 foreach ($phases as $phasecode => $phase) {
979 if (! $phase->active) {
980 $action = new stdClass();
981 $action->type = 'switchphase';
982 $action->url = $this->switchphase_url($phasecode);
983 $phase->actions[] = $action;
984 }
985 }
986 }
987
b761e6d9
DM
988 return $phases;
989 }
990
454e8dd9
DM
991 /**
992 * Switch to a new workshop phase
993 *
994 * Modifies the underlying database record. You should terminate the script shortly after calling this.
995 *
996 * @param int $newphase new phase code
997 * @return bool true if success, false otherwise
998 */
999 public function switch_phase($newphase) {
1000 global $DB;
1001
365c2cc2 1002 $known = $this->available_phases_list();
454e8dd9
DM
1003 if (!isset($known[$newphase])) {
1004 return false;
1005 }
f6e8b318
DM
1006
1007 if (self::PHASE_CLOSED == $newphase) {
f27b70fb 1008 // push the grades into the gradebook
10bc4bce
DM
1009 $workshop = new stdClass();
1010 foreach ($this as $property => $value) {
1011 $workshop->{$property} = $value;
1012 }
1013 $workshop->course = $this->course->id;
1014 $workshop->cmidnumber = $this->cm->id;
1015 $workshop->modname = 'workshop';
1016 workshop_update_grades($workshop);
f6e8b318
DM
1017 }
1018
454e8dd9
DM
1019 $DB->set_field('workshop', 'phase', $newphase, array('id' => $this->id));
1020 return true;
1021 }
ddb59c77
DM
1022
1023 /**
1024 * Saves a raw grade for submission as calculated from the assessment form fields
1025 *
1026 * @param array $assessmentid assessment record id, must exists
00aca3c1 1027 * @param mixed $grade raw percentual grade from 0.00000 to 100.00000
ddb59c77
DM
1028 * @return false|float the saved grade
1029 */
1030 public function set_peer_grade($assessmentid, $grade) {
1031 global $DB;
1032
1033 if (is_null($grade)) {
1034 return false;
1035 }
1036 $data = new stdClass();
1037 $data->id = $assessmentid;
1038 $data->grade = $grade;
1039 $DB->update_record('workshop_assessments', $data);
1040 return $grade;
1041 }
6516b9e9 1042
29dc43e7
DM
1043 /**
1044 * Prepares data object with all workshop grades to be rendered
1045 *
5e71cefb
DM
1046 * @param int $userid the user we are preparing the report for
1047 * @param mixed $groups single group or array of groups - only show users who are in one of these group(s). Defaults to all
29dc43e7 1048 * @param int $page the current page (for the pagination)
5e71cefb 1049 * @param int $perpage participants per page (for the pagination)
f27b70fb 1050 * @param string $sortby lastname|firstname|submissiontitle|submissiongrade|gradinggrade
5e71cefb 1051 * @param string $sorthow ASC|DESC
29dc43e7
DM
1052 * @return stdClass data for the renderer
1053 */
d895c6aa 1054 public function prepare_grading_report($userid, $groups, $page, $perpage, $sortby, $sorthow) {
29dc43e7
DM
1055 global $DB;
1056
d895c6aa
DM
1057 $canviewall = has_capability('mod/workshop:viewallassessments', $this->context, $userid);
1058 $isparticipant = has_any_capability(array('mod/workshop:submit', 'mod/workshop:peerassess'), $this->context, $userid);
29dc43e7
DM
1059
1060 if (!$canviewall and !$isparticipant) {
1061 // who the hell is this?
1062 return array();
1063 }
1064
f27b70fb 1065 if (!in_array($sortby, array('lastname','firstname','submissiontitle','submissiongrade','gradinggrade'))) {
5e71cefb
DM
1066 $sortby = 'lastname';
1067 }
1068
1069 if (!($sorthow === 'ASC' or $sorthow === 'DESC')) {
1070 $sorthow = 'ASC';
1071 }
1072
1073 // get the list of user ids to be displayed
29dc43e7
DM
1074 if ($canviewall) {
1075 // fetch the list of ids of all workshop participants - this may get really long so fetch just id
d895c6aa 1076 $participants = get_users_by_capability($this->context, array('mod/workshop:submit', 'mod/workshop:peerassess'),
5e71cefb 1077 'u.id', '', '', '', $groups, '', false, false, true);
29dc43e7
DM
1078 } else {
1079 // this is an ordinary workshop participant (aka student) - display the report just for him/her
1080 $participants = array($userid => (object)array('id' => $userid));
1081 }
1082
5e71cefb 1083 // we will need to know the number of all records later for the pagination purposes
29dc43e7
DM
1084 $numofparticipants = count($participants);
1085
d183140d 1086 // load all fields which can be used for sorting and paginate the records
5e71cefb 1087 list($participantids, $params) = $DB->get_in_or_equal(array_keys($participants), SQL_PARAMS_NAMED);
d183140d
DM
1088 $params['workshopid1'] = $this->id;
1089 $params['workshopid2'] = $this->id;
5e71cefb
DM
1090 $sqlsort = $sortby . ' ' . $sorthow . ',u.lastname,u.firstname,u.id';
1091 $sql = "SELECT u.id AS userid,u.firstname,u.lastname,u.picture,u.imagealt,
f27b70fb 1092 s.title AS submissiontitle, s.grade AS submissiongrade, ag.gradinggrade
5e71cefb 1093 FROM {user} u
d183140d
DM
1094 LEFT JOIN {workshop_submissions} s ON (s.authorid = u.id AND s.workshopid = :workshopid1 AND s.example = 0)
1095 LEFT JOIN {workshop_aggregations} ag ON (ag.userid = u.id AND ag.workshopid = :workshopid2)
1096 WHERE u.id $participantids
5e71cefb
DM
1097 ORDER BY $sqlsort";
1098 $participants = $DB->get_records_sql($sql, $params, $page * $perpage, $perpage);
29dc43e7
DM
1099
1100 // this will hold the information needed to display user names and pictures
5e71cefb
DM
1101 $userinfo = array();
1102
1103 // get the user details for all participants to display
1104 foreach ($participants as $participant) {
1105 if (!isset($userinfo[$participant->userid])) {
1106 $userinfo[$participant->userid] = new stdClass();
1107 $userinfo[$participant->userid]->id = $participant->userid;
1108 $userinfo[$participant->userid]->firstname = $participant->firstname;
1109 $userinfo[$participant->userid]->lastname = $participant->lastname;
1110 $userinfo[$participant->userid]->picture = $participant->picture;
1111 $userinfo[$participant->userid]->imagealt = $participant->imagealt;
1112 }
1113 }
29dc43e7 1114
5e71cefb 1115 // load the submissions details
29dc43e7 1116 $submissions = $this->get_submissions(array_keys($participants));
5e71cefb
DM
1117
1118 // get the user details for all moderators (teachers) that have overridden a submission grade
29dc43e7 1119 foreach ($submissions as $submission) {
29dc43e7
DM
1120 if (!isset($userinfo[$submission->gradeoverby])) {
1121 $userinfo[$submission->gradeoverby] = new stdClass();
1122 $userinfo[$submission->gradeoverby]->id = $submission->gradeoverby;
1123 $userinfo[$submission->gradeoverby]->firstname = $submission->overfirstname;
1124 $userinfo[$submission->gradeoverby]->lastname = $submission->overlastname;
1125 $userinfo[$submission->gradeoverby]->picture = $submission->overpicture;
1126 $userinfo[$submission->gradeoverby]->imagealt = $submission->overimagealt;
1127 }
1128 }
1129
5e71cefb 1130 // get the user details for all reviewers of the displayed participants
29dc43e7
DM
1131 $reviewers = array();
1132 if ($submissions) {
1133 list($submissionids, $params) = $DB->get_in_or_equal(array_keys($submissions), SQL_PARAMS_NAMED);
1134 $sql = "SELECT a.id AS assessmentid, a.submissionid, a.grade, a.gradinggrade, a.gradinggradeover,
1135 r.id AS reviewerid, r.lastname, r.firstname, r.picture, r.imagealt,
1136 s.id AS submissionid, s.authorid
1137 FROM {workshop_assessments} a
1138 JOIN {user} r ON (a.reviewerid = r.id)
1139 JOIN {workshop_submissions} s ON (a.submissionid = s.id)
1140 WHERE a.submissionid $submissionids";
1141 $reviewers = $DB->get_records_sql($sql, $params);
1142 foreach ($reviewers as $reviewer) {
1143 if (!isset($userinfo[$reviewer->reviewerid])) {
1144 $userinfo[$reviewer->reviewerid] = new stdClass();
1145 $userinfo[$reviewer->reviewerid]->id = $reviewer->reviewerid;
1146 $userinfo[$reviewer->reviewerid]->firstname = $reviewer->firstname;
1147 $userinfo[$reviewer->reviewerid]->lastname = $reviewer->lastname;
1148 $userinfo[$reviewer->reviewerid]->picture = $reviewer->picture;
1149 $userinfo[$reviewer->reviewerid]->imagealt = $reviewer->imagealt;
1150 }
1151 }
1152 }
1153
5e71cefb 1154 // get the user details for all reviewees of the displayed participants
934329e5
DM
1155 $reviewees = array();
1156 if ($participants) {
1157 list($participantids, $params) = $DB->get_in_or_equal(array_keys($participants), SQL_PARAMS_NAMED);
1158 $params['workshopid'] = $this->id;
d183140d 1159 $sql = "SELECT a.id AS assessmentid, a.submissionid, a.grade, a.gradinggrade, a.gradinggradeover, a.reviewerid,
934329e5
DM
1160 s.id AS submissionid,
1161 e.id AS authorid, e.lastname, e.firstname, e.picture, e.imagealt
1162 FROM {user} u
1163 JOIN {workshop_assessments} a ON (a.reviewerid = u.id)
1164 JOIN {workshop_submissions} s ON (a.submissionid = s.id)
1165 JOIN {user} e ON (s.authorid = e.id)
1166 WHERE u.id $participantids AND s.workshopid = :workshopid";
1167 $reviewees = $DB->get_records_sql($sql, $params);
1168 foreach ($reviewees as $reviewee) {
1169 if (!isset($userinfo[$reviewee->authorid])) {
1170 $userinfo[$reviewee->authorid] = new stdClass();
1171 $userinfo[$reviewee->authorid]->id = $reviewee->authorid;
1172 $userinfo[$reviewee->authorid]->firstname = $reviewee->firstname;
1173 $userinfo[$reviewee->authorid]->lastname = $reviewee->lastname;
1174 $userinfo[$reviewee->authorid]->picture = $reviewee->picture;
1175 $userinfo[$reviewee->authorid]->imagealt = $reviewee->imagealt;
1176 }
29dc43e7
DM
1177 }
1178 }
1179
5e71cefb
DM
1180 // finally populate the object to be rendered
1181 $grades = $participants;
29dc43e7
DM
1182
1183 foreach ($participants as $participant) {
1184 // set up default (null) values
d183140d
DM
1185 $grades[$participant->userid]->submissionid = null;
1186 $grades[$participant->userid]->submissiontitle = null;
1187 $grades[$participant->userid]->submissiongrade = null;
1188 $grades[$participant->userid]->submissiongradeover = null;
1189 $grades[$participant->userid]->submissiongradeoverby = null;
5e71cefb
DM
1190 $grades[$participant->userid]->reviewedby = array();
1191 $grades[$participant->userid]->reviewerof = array();
29dc43e7
DM
1192 }
1193 unset($participants);
1194 unset($participant);
1195
1196 foreach ($submissions as $submission) {
1197 $grades[$submission->authorid]->submissionid = $submission->id;
1198 $grades[$submission->authorid]->submissiontitle = $submission->title;
b4857acb
DM
1199 $grades[$submission->authorid]->submissiongrade = $this->real_grade($submission->grade);
1200 $grades[$submission->authorid]->submissiongradeover = $this->real_grade($submission->gradeover);
29dc43e7
DM
1201 $grades[$submission->authorid]->submissiongradeoverby = $submission->gradeoverby;
1202 }
1203 unset($submissions);
1204 unset($submission);
1205
1206 foreach($reviewers as $reviewer) {
1207 $info = new stdClass();
1208 $info->userid = $reviewer->reviewerid;
1209 $info->assessmentid = $reviewer->assessmentid;
1210 $info->submissionid = $reviewer->submissionid;
b4857acb
DM
1211 $info->grade = $this->real_grade($reviewer->grade);
1212 $info->gradinggrade = $this->real_grading_grade($reviewer->gradinggrade);
1213 $info->gradinggradeover = $this->real_grading_grade($reviewer->gradinggradeover);
29dc43e7
DM
1214 $grades[$reviewer->authorid]->reviewedby[$reviewer->reviewerid] = $info;
1215 }
1216 unset($reviewers);
1217 unset($reviewer);
1218
1219 foreach($reviewees as $reviewee) {
1220 $info = new stdClass();
1221 $info->userid = $reviewee->authorid;
1222 $info->assessmentid = $reviewee->assessmentid;
1223 $info->submissionid = $reviewee->submissionid;
b4857acb
DM
1224 $info->grade = $this->real_grade($reviewee->grade);
1225 $info->gradinggrade = $this->real_grading_grade($reviewee->gradinggrade);
1226 $info->gradinggradeover = $this->real_grading_grade($reviewee->gradinggradeover);
29dc43e7
DM
1227 $grades[$reviewee->reviewerid]->reviewerof[$reviewee->authorid] = $info;
1228 }
1229 unset($reviewees);
1230 unset($reviewee);
1231
b4857acb
DM
1232 foreach ($grades as $grade) {
1233 $grade->gradinggrade = $this->real_grading_grade($grade->gradinggrade);
b4857acb
DM
1234 }
1235
29dc43e7
DM
1236 $data = new stdClass();
1237 $data->grades = $grades;
1238 $data->userinfo = $userinfo;
1239 $data->totalcount = $numofparticipants;
b4857acb
DM
1240 $data->maxgrade = $this->real_grade(100);
1241 $data->maxgradinggrade = $this->real_grading_grade(100);
29dc43e7
DM
1242 return $data;
1243 }
1244
29dc43e7 1245 /**
b4857acb 1246 * Calculates the real value of a grade
29dc43e7 1247 *
b4857acb
DM
1248 * @param float $value percentual value from 0 to 100
1249 * @param float $max the maximal grade
1250 * @return string
1251 */
1252 public function real_grade_value($value, $max) {
1253 $localized = true;
557a1100 1254 if (is_null($value) or $value === '') {
b4857acb
DM
1255 return null;
1256 } elseif ($max == 0) {
1257 return 0;
1258 } else {
1259 return format_float($max * $value / 100, $this->gradedecimals, $localized);
1260 }
1261 }
1262
e554671d
DM
1263 /**
1264 * Calculates the raw (percentual) value from a real grade
1265 *
1266 * This is used in cases when a user wants to give a grade such as 12 of 20 and we need to save
1267 * this value in a raw percentual form into DB
1268 * @param float $value given grade
1269 * @param float $max the maximal grade
1270 * @return float suitable to be stored as numeric(10,5)
1271 */
1272 public function raw_grade_value($value, $max) {
557a1100 1273 if (is_null($value) or $value === '') {
e554671d
DM
1274 return null;
1275 }
1276 if ($max == 0 or $value < 0) {
1277 return 0;
1278 }
1279 $p = $value / $max * 100;
1280 if ($p > 100) {
1281 return $max;
1282 }
1283 return grade_floatval($p);
1284 }
1285
b4857acb
DM
1286 /**
1287 * Calculates the real value of grade for submission
1288 *
1289 * @param float $value percentual value from 0 to 100
1290 * @return string
1291 */
1292 public function real_grade($value) {
1293 return $this->real_grade_value($value, $this->grade);
1294 }
1295
1296 /**
1297 * Calculates the real value of grade for assessment
1298 *
1299 * @param float $value percentual value from 0 to 100
1300 * @return string
1301 */
1302 public function real_grading_grade($value) {
1303 return $this->real_grade_value($value, $this->gradinggrade);
29dc43e7
DM
1304 }
1305
89c1aa97 1306 /**
e9a90e69 1307 * Calculates grades for submission for the given participant(s) and updates it in the database
89c1aa97
DM
1308 *
1309 * @param null|int|array $restrict If null, update all authors, otherwise update just grades for the given author(s)
1310 * @return void
1311 */
8a1ba8ac 1312 public function aggregate_submission_grades($restrict=null) {
89c1aa97
DM
1313 global $DB;
1314
1315 // fetch a recordset with all assessments to process
1696f36c 1316 $sql = 'SELECT s.id AS submissionid, s.grade AS submissiongrade,
89c1aa97
DM
1317 a.weight, a.grade
1318 FROM {workshop_submissions} s
1319 LEFT JOIN {workshop_assessments} a ON (a.submissionid = s.id)
1320 WHERE s.example=0 AND s.workshopid=:workshopid'; // to be cont.
1321 $params = array('workshopid' => $this->id);
1322
1323 if (is_null($restrict)) {
1324 // update all users - no more conditions
1325 } elseif (!empty($restrict)) {
1326 list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED);
1327 $sql .= " AND s.authorid $usql";
1328 $params = array_merge($params, $uparams);
1329 } else {
1330 throw new coding_exception('Empty value is not a valid parameter here');
1331 }
1332
1333 $sql .= ' ORDER BY s.id'; // this is important for bulk processing
89c1aa97 1334
e9a90e69
DM
1335 $rs = $DB->get_recordset_sql($sql, $params);
1336 $batch = array(); // will contain a set of all assessments of a single submission
1337 $previous = null; // a previous record in the recordset
1338
89c1aa97
DM
1339 foreach ($rs as $current) {
1340 if (is_null($previous)) {
1341 // we are processing the very first record in the recordset
1342 $previous = $current;
89c1aa97 1343 }
e9a90e69 1344 if ($current->submissionid == $previous->submissionid) {
89c1aa97 1345 // we are still processing the current submission
e9a90e69
DM
1346 $batch[] = $current;
1347 } else {
1348 // process all the assessments of a sigle submission
1349 $this->aggregate_submission_grades_process($batch);
1350 // and then start to process another submission
1351 $batch = array($current);
1352 $previous = $current;
89c1aa97
DM
1353 }
1354 }
e9a90e69
DM
1355 // do not forget to process the last batch!
1356 $this->aggregate_submission_grades_process($batch);
89c1aa97
DM
1357 $rs->close();
1358 }
1359
1360 /**
1361 * Calculates grades for assessment for the given participant(s)
1362 *
39411930
DM
1363 * Grade for assessment is calculated as a simple mean of all grading grades calculated by the grading evaluator.
1364 * The assessment weight is not taken into account here.
89c1aa97
DM
1365 *
1366 * @param null|int|array $restrict If null, update all reviewers, otherwise update just grades for the given reviewer(s)
1367 * @return void
1368 */
8a1ba8ac 1369 public function aggregate_grading_grades($restrict=null) {
89c1aa97
DM
1370 global $DB;
1371
39411930
DM
1372 // fetch a recordset with all assessments to process
1373 $sql = 'SELECT a.reviewerid, a.gradinggrade, a.gradinggradeover,
1374 ag.id AS aggregationid, ag.gradinggrade AS aggregatedgrade
1375 FROM {workshop_assessments} a
1376 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id)
1377 LEFT JOIN {workshop_aggregations} ag ON (ag.userid = a.reviewerid AND ag.workshopid = s.workshopid)
1378 WHERE s.example=0 AND s.workshopid=:workshopid'; // to be cont.
1379 $params = array('workshopid' => $this->id);
1380
1381 if (is_null($restrict)) {
1382 // update all users - no more conditions
1383 } elseif (!empty($restrict)) {
1384 list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED);
1385 $sql .= " AND a.reviewerid $usql";
1386 $params = array_merge($params, $uparams);
1387 } else {
1388 throw new coding_exception('Empty value is not a valid parameter here');
1389 }
1390
1391 $sql .= ' ORDER BY a.reviewerid'; // this is important for bulk processing
1392
1393 $rs = $DB->get_recordset_sql($sql, $params);
1394 $batch = array(); // will contain a set of all assessments of a single submission
1395 $previous = null; // a previous record in the recordset
1396
1397 foreach ($rs as $current) {
1398 if (is_null($previous)) {
1399 // we are processing the very first record in the recordset
1400 $previous = $current;
1401 }
1402 if ($current->reviewerid == $previous->reviewerid) {
1403 // we are still processing the current reviewer
1404 $batch[] = $current;
1405 } else {
1406 // process all the assessments of a sigle submission
1407 $this->aggregate_grading_grades_process($batch);
1408 // and then start to process another reviewer
1409 $batch = array($current);
1410 $previous = $current;
1411 }
1412 }
1413 // do not forget to process the last batch!
1414 $this->aggregate_grading_grades_process($batch);
1415 $rs->close();
89c1aa97
DM
1416 }
1417
77f43e7d 1418 /**
f6e8b318 1419 * Returns the mform the teachers use to put a feedback for the reviewer
77f43e7d 1420 *
f6e8b318 1421 * @return workshop_feedbackreviewer_form
77f43e7d 1422 */
e554671d 1423 public function get_feedbackreviewer_form(moodle_url $actionurl, stdClass $assessment, $editable=true) {
77f43e7d
DM
1424 global $CFG;
1425 require_once(dirname(__FILE__) . '/feedbackreviewer_form.php');
1426
e554671d
DM
1427 $current = new stdClass();
1428 $current->asid = $assessment->id;
1429 $current->gradinggrade = $this->real_grading_grade($assessment->gradinggrade);
1430 $current->gradinggradeover = $this->real_grading_grade($assessment->gradinggradeover);
1431 $current->feedbackreviewer = $assessment->feedbackreviewer;
1432 $current->feedbackreviewerformat = $assessment->feedbackreviewerformat;
1433 if (is_null($current->gradinggrade)) {
1434 $current->gradinggrade = get_string('nullgrade', 'workshop');
1435 }
1436
1437 // prepare wysiwyg editor
1438 $current = file_prepare_standard_editor($current, 'feedbackreviewer', array());
1439
77f43e7d 1440 return new workshop_feedbackreviewer_form($actionurl,
e554671d 1441 array('workshop' => $this, 'current' => $current, 'feedbackopts' => array()),
77f43e7d
DM
1442 'post', '', null, $editable);
1443 }
1444
67cd00ba
DM
1445 /**
1446 * Returns the mform the teachers use to put a feedback for the author on their submission
1447 *
1448 * @return workshop_feedbackauthor_form
1449 */
1450 public function get_feedbackauthor_form(moodle_url $actionurl, stdClass $submission, $editable=true) {
1451 global $CFG;
1452 require_once(dirname(__FILE__) . '/feedbackauthor_form.php');
1453
1454 $current = new stdClass();
1455 $current->submissionid = $submission->id;
557a1100
DM
1456 $current->grade = $this->real_grade($submission->grade);
1457 $current->gradeover = $this->real_grade($submission->gradeover);
1458 $current->feedbackauthor = $submission->feedbackauthor;
1459 $current->feedbackauthorformat = $submission->feedbackauthorformat;
67cd00ba
DM
1460 if (is_null($current->grade)) {
1461 $current->grade = get_string('nullgrade', 'workshop');
1462 }
1463
1464 // prepare wysiwyg editor
1465 $current = file_prepare_standard_editor($current, 'feedbackauthor', array());
1466
1467 return new workshop_feedbackauthor_form($actionurl,
1468 array('workshop' => $this, 'current' => $current, 'feedbackopts' => array()),
1469 'post', '', null, $editable);
1470 }
1471
aa40adbf
DM
1472 ////////////////////////////////////////////////////////////////////////////////
1473 // Internal methods (implementation details) //
1474 ////////////////////////////////////////////////////////////////////////////////
6516b9e9 1475
e9a90e69
DM
1476 /**
1477 * Given an array of all assessments of a single submission, calculates the final grade for this submission
1478 *
1479 * This calculates the weighted mean of the passed assessment grades. If, however, the submission grade
1480 * was overridden by a teacher, the gradeover value is returned and the rest of grades are ignored.
1481 *
1482 * @param array $assessments of stdClass(->submissionid ->submissiongrade ->gradeover ->weight ->grade)
1fed6ce3 1483 * @return void
e9a90e69
DM
1484 */
1485 protected function aggregate_submission_grades_process(array $assessments) {
1486 global $DB;
1487
1488 $submissionid = null; // the id of the submission being processed
1489 $current = null; // the grade currently saved in database
1490 $finalgrade = null; // the new grade to be calculated
1491 $sumgrades = 0;
1492 $sumweights = 0;
1493
1494 foreach ($assessments as $assessment) {
1495 if (is_null($submissionid)) {
1496 // the id is the same in all records, fetch it during the first loop cycle
1497 $submissionid = $assessment->submissionid;
1498 }
1499 if (is_null($current)) {
1500 // the currently saved grade is the same in all records, fetch it during the first loop cycle
1501 $current = $assessment->submissiongrade;
1502 }
e9a90e69
DM
1503 if (is_null($assessment->grade)) {
1504 // this was not assessed yet
1505 continue;
1506 }
1507 if ($assessment->weight == 0) {
1508 // this does not influence the calculation
1509 continue;
1510 }
1511 $sumgrades += $assessment->grade * $assessment->weight;
1512 $sumweights += $assessment->weight;
1513 }
1514 if ($sumweights > 0 and is_null($finalgrade)) {
1515 $finalgrade = grade_floatval($sumgrades / $sumweights);
1516 }
1517 // check if the new final grade differs from the one stored in the database
1518 if (grade_floats_different($finalgrade, $current)) {
1519 // we need to save new calculation into the database
10bc4bce
DM
1520 $record = new stdClass();
1521 $record->id = $submissionid;
1522 $record->grade = $finalgrade;
1523 $record->timegraded = time();
1524 $DB->update_record('workshop_submissions', $record);
e9a90e69
DM
1525 }
1526 }
1527
39411930
DM
1528 /**
1529 * Given an array of all assessments done by a single reviewer, calculates the final grading grade
1530 *
1531 * This calculates the simple mean of the passed grading grades. If, however, the grading grade
1532 * was overridden by a teacher, the gradinggradeover value is returned and the rest of grades are ignored.
1533 *
1534 * @param array $assessments of stdClass(->reviewerid ->gradinggrade ->gradinggradeover ->aggregationid ->aggregatedgrade)
1fed6ce3 1535 * @return void
39411930
DM
1536 */
1537 protected function aggregate_grading_grades_process(array $assessments) {
1538 global $DB;
1539
1540 $reviewerid = null; // the id of the reviewer being processed
1541 $current = null; // the gradinggrade currently saved in database
1542 $finalgrade = null; // the new grade to be calculated
1543 $agid = null; // aggregation id
1544 $sumgrades = 0;
1545 $count = 0;
1546
1547 foreach ($assessments as $assessment) {
1548 if (is_null($reviewerid)) {
1549 // the id is the same in all records, fetch it during the first loop cycle
1550 $reviewerid = $assessment->reviewerid;
1551 }
1552 if (is_null($agid)) {
1553 // the id is the same in all records, fetch it during the first loop cycle
1554 $agid = $assessment->aggregationid;
1555 }
1556 if (is_null($current)) {
1557 // the currently saved grade is the same in all records, fetch it during the first loop cycle
1558 $current = $assessment->aggregatedgrade;
1559 }
1560 if (!is_null($assessment->gradinggradeover)) {
1561 // the grading grade for this assessment is overriden by a teacher
1562 $sumgrades += $assessment->gradinggradeover;
1563 $count++;
1564 } else {
1565 if (!is_null($assessment->gradinggrade)) {
1566 $sumgrades += $assessment->gradinggrade;
1567 $count++;
1568 }
1569 }
1570 }
1571 if ($count > 0) {
1572 $finalgrade = grade_floatval($sumgrades / $count);
1573 }
1574 // check if the new final grade differs from the one stored in the database
1575 if (grade_floats_different($finalgrade, $current)) {
1576 // we need to save new calculation into the database
1577 if (is_null($agid)) {
1578 // no aggregation record yet
1579 $record = new stdClass();
1580 $record->workshopid = $this->id;
1581 $record->userid = $reviewerid;
1582 $record->gradinggrade = $finalgrade;
10bc4bce 1583 $record->timegraded = time();
39411930
DM
1584 $DB->insert_record('workshop_aggregations', $record);
1585 } else {
10bc4bce
DM
1586 $record = new stdClass();
1587 $record->id = $agid;
1588 $record->gradinggrade = $finalgrade;
1589 $record->timegraded = time();
1590 $DB->update_record('workshop_aggregations', $record);
39411930
DM
1591 }
1592 }
1593 }
1594
6516b9e9 1595 /**
aa40adbf 1596 * Given a list of user ids, returns the filtered one containing just ids of users with own submission
6516b9e9 1597 *
aa40adbf
DM
1598 * Example submissions are ignored.
1599 *
1600 * @param array $userids
6516b9e9
DM
1601 * @return array
1602 */
aa40adbf
DM
1603 protected function users_with_submission(array $userids) {
1604 global $DB;
1605
1606 if (empty($userids)) {
1607 return array();
1608 }
1609 $userswithsubmission = array();
1610 list($usql, $uparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
00aca3c1 1611 $sql = "SELECT id,authorid
aa40adbf 1612 FROM {workshop_submissions}
00aca3c1 1613 WHERE example = 0 AND workshopid = :workshopid AND authorid $usql";
aa40adbf
DM
1614 $params = array('workshopid' => $this->id);
1615 $params = array_merge($params, $uparams);
1616 $submissions = $DB->get_records_sql($sql, $params);
1617 foreach ($submissions as $submission) {
00aca3c1 1618 $userswithsubmission[$submission->authorid] = true;
aa40adbf
DM
1619 }
1620
1621 return $userswithsubmission;
6516b9e9
DM
1622 }
1623
aa40adbf
DM
1624 /**
1625 * @return array of available workshop phases
1626 */
365c2cc2 1627 protected function available_phases_list() {
aa40adbf
DM
1628 return array(
1629 self::PHASE_SETUP => true,
1630 self::PHASE_SUBMISSION => true,
1631 self::PHASE_ASSESSMENT => true,
1632 self::PHASE_EVALUATION => true,
1633 self::PHASE_CLOSED => true,
1634 );
1635 }
1636
66c9894d 1637}