MDL-36805 Correct docs for workshop_grade_item_update in mod_workshop
[moodle.git] / mod / workshop / lib.php
CommitLineData
4eab2e7f 1<?php
53fad4b9
DM
2
3// This file is part of Moodle - http://moodle.org/
4//
4eab2e7f
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//
4eab2e7f
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
4eab2e7f 18/**
7a5f4be0 19 * Library of workshop module functions needed by Moodle core and other subsystems
4eab2e7f 20 *
365c2cc2
DM
21 * All the functions neeeded by Moodle core, gradebook, file subsystem etc
22 * are placed here.
4eab2e7f 23 *
7a5f4be0
DM
24 * @package mod
25 * @subpackage workshop
26 * @copyright 2009 David Mudrak <david.mudrak@gmail.com>
27 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4eab2e7f
DM
28 */
29
6867e05d 30defined('MOODLE_INTERNAL') || die();
4eab2e7f 31
ac069aeb
DM
32require_once($CFG->dirroot . '/calendar/lib.php');
33
365c2cc2
DM
34////////////////////////////////////////////////////////////////////////////////
35// Moodle core API //
36////////////////////////////////////////////////////////////////////////////////
37
b761e6d9
DM
38/**
39 * Returns the information if the module supports a feature
40 *
41 * @see plugin_supports() in lib/moodlelib.php
42 * @param string $feature FEATURE_xx constant for requested feature
43 * @return mixed true if the feature is supported, null if unknown
44 */
45function workshop_supports($feature) {
46 switch($feature) {
47 case FEATURE_GRADE_HAS_GRADE: return true;
48 case FEATURE_GROUPS: return true;
49 case FEATURE_GROUPINGS: return true;
50 case FEATURE_GROUPMEMBERSONLY: return true;
51 case FEATURE_MOD_INTRO: return true;
0fc5a5fa 52 case FEATURE_BACKUP_MOODLE2: return true;
3a7507d0
SM
53 case FEATURE_COMPLETION_TRACKS_VIEWS:
54 return true;
3e4c2435 55 case FEATURE_SHOW_DESCRIPTION: return true;
b761e6d9
DM
56 default: return null;
57 }
58}
59
6e309973 60/**
a39d7d87 61 * Saves a new instance of the workshop into the database
6e309973 62 *
4eab2e7f
DM
63 * Given an object containing all the necessary data,
64 * (defined by the form in mod_form.php) this function
a39d7d87 65 * will save a new instance and return the id number
4eab2e7f
DM
66 * of the new instance.
67 *
5924db72 68 * @param stdClass $workshop An object from the form in mod_form.php
4eab2e7f
DM
69 * @return int The id of the newly inserted workshop record
70 */
7a789aa8 71function workshop_add_instance(stdclass $workshop) {
454e8dd9
DM
72 global $CFG, $DB;
73 require_once(dirname(__FILE__) . '/locallib.php');
4eab2e7f 74
3ff08057
DM
75 $workshop->phase = workshop::PHASE_SETUP;
76 $workshop->timecreated = time();
77 $workshop->timemodified = $workshop->timecreated;
78 $workshop->useexamples = (int)!empty($workshop->useexamples);
79 $workshop->usepeerassessment = (int)!empty($workshop->usepeerassessment);
80 $workshop->useselfassessment = (int)!empty($workshop->useselfassessment);
81 $workshop->latesubmissions = (int)!empty($workshop->latesubmissions);
82 $workshop->phaseswitchassessment = (int)!empty($workshop->phaseswitchassessment);
83 $workshop->evaluation = 'best';
a39d7d87 84
6516b9e9 85 // insert the new record so we get the id
365c2cc2 86 $workshop->id = $DB->insert_record('workshop', $workshop);
6516b9e9
DM
87
88 // we need to use context now, so we need to make sure all needed info is already in db
365c2cc2
DM
89 $cmid = $workshop->coursemodule;
90 $DB->set_field('course_modules', 'instance', $workshop->id, array('id' => $cmid));
6516b9e9
DM
91 $context = get_context_instance(CONTEXT_MODULE, $cmid);
92
93 // process the custom wysiwyg editors
365c2cc2 94 if ($draftitemid = $workshop->instructauthorseditor['itemid']) {
64f93798 95 $workshop->instructauthors = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructauthors',
365c2cc2
DM
96 0, workshop::instruction_editors_options($context), $workshop->instructauthorseditor['text']);
97 $workshop->instructauthorsformat = $workshop->instructauthorseditor['format'];
6516b9e9
DM
98 }
99
365c2cc2 100 if ($draftitemid = $workshop->instructreviewerseditor['itemid']) {
64f93798 101 $workshop->instructreviewers = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructreviewers',
365c2cc2
DM
102 0, workshop::instruction_editors_options($context), $workshop->instructreviewerseditor['text']);
103 $workshop->instructreviewersformat = $workshop->instructreviewerseditor['format'];
15d12b54
DM
104 }
105
6516b9e9 106 // re-save the record with the replaced URLs in editor fields
365c2cc2 107 $DB->update_record('workshop', $workshop);
6516b9e9 108
a74cadfa 109 // create gradebook items
365c2cc2 110 workshop_grade_item_update($workshop);
a74cadfa 111 workshop_grade_item_category_update($workshop);
365c2cc2 112
ac069aeb
DM
113 // create calendar events
114 workshop_calendar_update($workshop, $workshop->coursemodule);
115
365c2cc2 116 return $workshop->id;
a39d7d87 117}
a7c5b918 118
4eab2e7f
DM
119/**
120 * Given an object containing all the necessary data,
121 * (defined by the form in mod_form.php) this function
122 * will update an existing instance with new data.
123 *
5924db72 124 * @param stdClass $workshop An object from the form in mod_form.php
6516b9e9 125 * @return bool success
4eab2e7f 126 */
7a789aa8 127function workshop_update_instance(stdclass $workshop) {
6516b9e9
DM
128 global $CFG, $DB;
129 require_once(dirname(__FILE__) . '/locallib.php');
130
3ff08057
DM
131 $workshop->timemodified = time();
132 $workshop->id = $workshop->instance;
133 $workshop->useexamples = (int)!empty($workshop->useexamples);
134 $workshop->usepeerassessment = (int)!empty($workshop->usepeerassessment);
135 $workshop->useselfassessment = (int)!empty($workshop->useselfassessment);
136 $workshop->latesubmissions = (int)!empty($workshop->latesubmissions);
137 $workshop->phaseswitchassessment = (int)!empty($workshop->phaseswitchassessment);
138 $workshop->evaluation = 'best';
4eab2e7f 139
f05c168d
DM
140 // todo - if the grading strategy is being changed, we must replace all aggregated peer grades with nulls
141 // todo - if maximum grades are being changed, we should probably recalculate or invalidate them
142
365c2cc2
DM
143 $DB->update_record('workshop', $workshop);
144 $context = get_context_instance(CONTEXT_MODULE, $workshop->coursemodule);
4eab2e7f 145
6516b9e9 146 // process the custom wysiwyg editors
365c2cc2 147 if ($draftitemid = $workshop->instructauthorseditor['itemid']) {
64f93798 148 $workshop->instructauthors = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructauthors',
365c2cc2
DM
149 0, workshop::instruction_editors_options($context), $workshop->instructauthorseditor['text']);
150 $workshop->instructauthorsformat = $workshop->instructauthorseditor['format'];
6516b9e9
DM
151 }
152
365c2cc2 153 if ($draftitemid = $workshop->instructreviewerseditor['itemid']) {
64f93798 154 $workshop->instructreviewers = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructreviewers',
365c2cc2
DM
155 0, workshop::instruction_editors_options($context), $workshop->instructreviewerseditor['text']);
156 $workshop->instructreviewersformat = $workshop->instructreviewerseditor['format'];
15d12b54
DM
157 }
158
6516b9e9 159 // re-save the record with the replaced URLs in editor fields
365c2cc2
DM
160 $DB->update_record('workshop', $workshop);
161
a74cadfa 162 // update gradebook items
365c2cc2 163 workshop_grade_item_update($workshop);
a74cadfa 164 workshop_grade_item_category_update($workshop);
365c2cc2 165
ac069aeb
DM
166 // update calendar events
167 workshop_calendar_update($workshop, $workshop->coursemodule);
168
365c2cc2 169 return true;
4eab2e7f
DM
170}
171
4eab2e7f
DM
172/**
173 * Given an ID of an instance of this module,
174 * this function will permanently delete the instance
175 * and any data that depends on it.
176 *
177 * @param int $id Id of the module instance
178 * @return boolean Success/Failure
179 */
180function workshop_delete_instance($id) {
f7d3a965
DM
181 global $CFG, $DB;
182 require_once($CFG->libdir.'/gradelib.php');
183
4eab2e7f
DM
184 if (! $workshop = $DB->get_record('workshop', array('id' => $id))) {
185 return false;
186 }
346af1a4 187
8a1ba8ac
DM
188 // delete all associated aggregations
189 $DB->delete_records('workshop_aggregations', array('workshopid' => $workshop->id));
346af1a4 190
8a1ba8ac
DM
191 // get the list of ids of all submissions
192 $submissions = $DB->get_records('workshop_submissions', array('workshopid' => $workshop->id), '', 'id');
346af1a4 193
8a1ba8ac
DM
194 // get the list of all allocated assessments
195 $assessments = $DB->get_records_list('workshop_assessments', 'submissionid', array_keys($submissions), '', 'id');
346af1a4 196
8a1ba8ac
DM
197 // delete the associated records from the workshop core tables
198 $DB->delete_records_list('workshop_grades', 'assessmentid', array_keys($assessments));
199 $DB->delete_records_list('workshop_assessments', 'id', array_keys($assessments));
200 $DB->delete_records_list('workshop_submissions', 'id', array_keys($submissions));
346af1a4
DM
201
202 // call the static clean-up methods of all available subplugins
203 $strategies = get_plugin_list('workshopform');
204 foreach ($strategies as $strategy => $path) {
205 require_once($path.'/lib.php');
206 $classname = 'workshop_'.$strategy.'_strategy';
207 call_user_func($classname.'::delete_instance', $workshop->id);
208 }
209
210 $allocators = get_plugin_list('workshopallocation');
211 foreach ($allocators as $allocator => $path) {
212 require_once($path.'/lib.php');
213 $classname = 'workshop_'.$allocator.'_allocator';
214 call_user_func($classname.'::delete_instance', $workshop->id);
215 }
216
217 $evaluators = get_plugin_list('workshopeval');
218 foreach ($evaluators as $evaluator => $path) {
219 require_once($path.'/lib.php');
220 $classname = 'workshop_'.$evaluator.'_evaluation';
221 call_user_func($classname.'::delete_instance', $workshop->id);
222 }
223
33d0cb3c
DM
224 // delete the calendar events
225 $events = $DB->get_records('event', array('modulename' => 'workshop', 'instance' => $workshop->id));
226 foreach ($events as $event) {
227 $event = calendar_event::load($event);
228 $event->delete();
229 }
230
8a1ba8ac
DM
231 // finally remove the workshop record itself
232 $DB->delete_records('workshop', array('id' => $workshop->id));
4eab2e7f 233
365c2cc2
DM
234 // gradebook cleanup
235 grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 0, null, array('deleted' => true));
236 grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 1, null, array('deleted' => true));
237
8a1ba8ac 238 return true;
4eab2e7f
DM
239}
240
4eab2e7f
DM
241/**
242 * Return a small object with summary information about what a
243 * user has done with a given particular instance of this module
244 * Used for user activity reports.
245 * $return->time = the time they did it
246 * $return->info = a short text description
247 *
7a5f4be0 248 * @return stdclass|null
4eab2e7f
DM
249 */
250function workshop_user_outline($course, $user, $mod, $workshop) {
7a5f4be0
DM
251 global $CFG, $DB;
252 require_once($CFG->libdir.'/gradelib.php');
253
254 $grades = grade_get_grades($course->id, 'mod', 'workshop', $workshop->id, $user->id);
255
256 $submissiongrade = null;
257 $assessmentgrade = null;
258
259 $info = '';
260 $time = 0;
261
262 if (!empty($grades->items[0]->grades)) {
263 $submissiongrade = reset($grades->items[0]->grades);
264 $info .= get_string('submissiongrade', 'workshop') . ': ' . $submissiongrade->str_long_grade . html_writer::empty_tag('br');
265 $time = max($time, $submissiongrade->dategraded);
266 }
267 if (!empty($grades->items[1]->grades)) {
268 $assessmentgrade = reset($grades->items[1]->grades);
269 $info .= get_string('gradinggrade', 'workshop') . ': ' . $assessmentgrade->str_long_grade;
270 $time = max($time, $assessmentgrade->dategraded);
271 }
272
273 if (!empty($info) and !empty($time)) {
274 $return = new stdclass();
275 $return->time = $time;
276 $return->info = $info;
277 return $return;
278 }
279
280 return null;
4eab2e7f
DM
281}
282
4eab2e7f
DM
283/**
284 * Print a detailed representation of what a user has done with
285 * a given particular instance of this module, for user activity reports.
286 *
7a5f4be0 287 * @return string HTML
4eab2e7f
DM
288 */
289function workshop_user_complete($course, $user, $mod, $workshop) {
7a5f4be0
DM
290 global $CFG, $DB, $OUTPUT;
291 require_once(dirname(__FILE__).'/locallib.php');
292 require_once($CFG->libdir.'/gradelib.php');
293
294 $workshop = new workshop($workshop, $mod, $course);
295 $grades = grade_get_grades($course->id, 'mod', 'workshop', $workshop->id, $user->id);
296
297 if (!empty($grades->items[0]->grades)) {
298 $submissiongrade = reset($grades->items[0]->grades);
299 $info = get_string('submissiongrade', 'workshop') . ': ' . $submissiongrade->str_long_grade;
300 echo html_writer::tag('li', $info, array('class'=>'submissiongrade'));
301 }
302 if (!empty($grades->items[1]->grades)) {
303 $assessmentgrade = reset($grades->items[1]->grades);
304 $info = get_string('gradinggrade', 'workshop') . ': ' . $assessmentgrade->str_long_grade;
305 echo html_writer::tag('li', $info, array('class'=>'gradinggrade'));
306 }
307
308 if (has_capability('mod/workshop:viewallsubmissions', $workshop->context)) {
a4e84836
DM
309 $canviewsubmission = true;
310 if (groups_get_activity_groupmode($workshop->cm) == SEPARATEGROUPS) {
311 // user must have accessallgroups or share at least one group with the submission author
312 if (!has_capability('moodle/site:accessallgroups', $workshop->context)) {
313 $usersgroups = groups_get_activity_allowed_groups($workshop->cm);
314 $authorsgroups = groups_get_all_groups($workshop->course->id, $user->id, $workshop->cm->groupingid, 'g.id');
315 $sharedgroups = array_intersect_key($usersgroups, $authorsgroups);
316 if (empty($sharedgroups)) {
317 $canviewsubmission = false;
318 }
319 }
320 }
321 if ($canviewsubmission and $submission = $workshop->get_submission_by_author($user->id)) {
7a5f4be0
DM
322 $title = format_string($submission->title);
323 $url = $workshop->submission_url($submission->id);
324 $link = html_writer::link($url, $title);
325 $info = get_string('submission', 'workshop').': '.$link;
326 echo html_writer::tag('li', $info, array('class'=>'submission'));
327 }
328 }
329
330 if (has_capability('mod/workshop:viewallassessments', $workshop->context)) {
331 if ($assessments = $workshop->get_assessments_by_reviewer($user->id)) {
332 foreach ($assessments as $assessment) {
333 $a = new stdclass();
334 $a->submissionurl = $workshop->submission_url($assessment->submissionid)->out();
335 $a->assessmenturl = $workshop->assess_url($assessment->id)->out();
336 $a->submissiontitle = s($assessment->submissiontitle);
337 echo html_writer::tag('li', get_string('assessmentofsubmission', 'workshop', $a));
338 }
339 }
340 }
4eab2e7f
DM
341}
342
4eab2e7f
DM
343/**
344 * Given a course and a time, this module should find recent activity
345 * that has occurred in workshop activities and print it out.
346 * Return true if there was output, or false is there was none.
347 *
5924db72 348 * @param stdClass $course
7a5f4be0
DM
349 * @param bool $viewfullnames
350 * @param int $timestart
4eab2e7f 351 * @return boolean
4eab2e7f 352 */
7a5f4be0
DM
353function workshop_print_recent_activity($course, $viewfullnames, $timestart) {
354 global $CFG, $USER, $DB, $OUTPUT;
355
356 $sql = "SELECT s.id AS submissionid, s.title AS submissiontitle, s.timemodified AS submissionmodified,
357 author.id AS authorid, author.lastname AS authorlastname, author.firstname AS authorfirstname,
358 a.id AS assessmentid, a.timemodified AS assessmentmodified,
359 reviewer.id AS reviewerid, reviewer.lastname AS reviewerlastname, reviewer.firstname AS reviewerfirstname,
360 cm.id AS cmid
361 FROM {workshop} w
362 INNER JOIN {course_modules} cm ON cm.instance = w.id
363 INNER JOIN {modules} md ON md.id = cm.module
364 INNER JOIN {workshop_submissions} s ON s.workshopid = w.id
365 INNER JOIN {user} author ON s.authorid = author.id
366 LEFT JOIN {workshop_assessments} a ON a.submissionid = s.id
367 LEFT JOIN {user} reviewer ON a.reviewerid = reviewer.id
368 WHERE cm.course = ?
369 AND md.name = 'workshop'
370 AND s.example = 0
371 AND (s.timemodified > ? OR a.timemodified > ?)";
372
373 $rs = $DB->get_recordset_sql($sql, array($course->id, $timestart, $timestart));
374
f20edd52 375 $modinfo = get_fast_modinfo($course); // reference needed because we might load the groups
7a5f4be0
DM
376
377 $submissions = array(); // recent submissions indexed by submission id
378 $assessments = array(); // recent assessments indexed by assessment id
379 $users = array();
380
381 foreach ($rs as $activity) {
382 if (!array_key_exists($activity->cmid, $modinfo->cms)) {
383 // this should not happen but just in case
384 continue;
385 }
386
387 $cm = $modinfo->cms[$activity->cmid];
388 if (!$cm->uservisible) {
389 continue;
390 }
391
392 if ($viewfullnames) {
393 // remember all user names we can use later
394 if (empty($users[$activity->authorid])) {
395 $u = new stdclass();
396 $u->lastname = $activity->authorlastname;
397 $u->firstname = $activity->authorfirstname;
398 $users[$activity->authorid] = $u;
399 }
400 if ($activity->reviewerid and empty($users[$activity->reviewerid])) {
401 $u = new stdclass();
402 $u->lastname = $activity->reviewerlastname;
403 $u->firstname = $activity->reviewerfirstname;
404 $users[$activity->reviewerid] = $u;
405 }
406 }
407
408 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
409 $groupmode = groups_get_activity_groupmode($cm, $course);
410
411 if ($activity->submissionmodified > $timestart and empty($submissions[$activity->submissionid])) {
412 $s = new stdclass();
413 $s->title = $activity->submissiontitle;
414 $s->authorid = $activity->authorid;
415 $s->timemodified = $activity->submissionmodified;
416 $s->cmid = $activity->cmid;
417 if (has_capability('mod/workshop:viewauthornames', $context)) {
418 $s->authornamevisible = true;
419 } else {
420 $s->authornamevisible = false;
421 }
422
423 // the following do-while wrapper allows to break from deeply nested if-statements
424 do {
425 if ($s->authorid === $USER->id) {
426 // own submissions always visible
427 $submissions[$activity->submissionid] = $s;
428 break;
429 }
430
431 if (has_capability('mod/workshop:viewallsubmissions', $context)) {
432 if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
433 if (isguestuser()) {
434 // shortcut - guest user does not belong into any group
435 break;
436 }
437
438 if (is_null($modinfo->groups)) {
439 $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
440 }
441
442 // this might be slow - show only submissions by users who share group with me in this cm
443 if (empty($modinfo->groups[$cm->id])) {
444 break;
445 }
446 $authorsgroups = groups_get_all_groups($course->id, $s->authorid, $cm->groupingid);
447 if (is_array($authorsgroups)) {
448 $authorsgroups = array_keys($authorsgroups);
449 $intersect = array_intersect($authorsgroups, $modinfo->groups[$cm->id]);
450 if (empty($intersect)) {
451 break;
452 } else {
453 // can see all submissions and shares a group with the author
454 $submissions[$activity->submissionid] = $s;
455 break;
456 }
457 }
458
459 } else {
460 // can see all submissions from all groups
461 $submissions[$activity->submissionid] = $s;
462 }
463 }
464 } while (0);
465 }
466
467 if ($activity->assessmentmodified > $timestart and empty($assessments[$activity->assessmentid])) {
468 $a = new stdclass();
469 $a->submissionid = $activity->submissionid;
470 $a->submissiontitle = $activity->submissiontitle;
471 $a->reviewerid = $activity->reviewerid;
472 $a->timemodified = $activity->assessmentmodified;
473 $a->cmid = $activity->cmid;
474 if (has_capability('mod/workshop:viewreviewernames', $context)) {
475 $a->reviewernamevisible = true;
476 } else {
477 $a->reviewernamevisible = false;
478 }
479
480 // the following do-while wrapper allows to break from deeply nested if-statements
481 do {
482 if ($a->reviewerid === $USER->id) {
483 // own assessments always visible
484 $assessments[$activity->assessmentid] = $a;
485 break;
486 }
487
488 if (has_capability('mod/workshop:viewallassessments', $context)) {
489 if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
490 if (isguestuser()) {
491 // shortcut - guest user does not belong into any group
492 break;
493 }
494
495 if (is_null($modinfo->groups)) {
496 $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
497 }
498
499 // this might be slow - show only submissions by users who share group with me in this cm
500 if (empty($modinfo->groups[$cm->id])) {
501 break;
502 }
503 $reviewersgroups = groups_get_all_groups($course->id, $a->reviewerid, $cm->groupingid);
504 if (is_array($reviewersgroups)) {
505 $reviewersgroups = array_keys($reviewersgroups);
506 $intersect = array_intersect($reviewersgroups, $modinfo->groups[$cm->id]);
507 if (empty($intersect)) {
508 break;
509 } else {
510 // can see all assessments and shares a group with the reviewer
511 $assessments[$activity->assessmentid] = $a;
512 break;
513 }
514 }
515
516 } else {
517 // can see all assessments from all groups
518 $assessments[$activity->assessmentid] = $a;
519 }
520 }
521 } while (0);
522 }
523 }
524 $rs->close();
525
526 $shown = false;
527
528 if (!empty($submissions)) {
529 $shown = true;
530 echo $OUTPUT->heading(get_string('recentsubmissions', 'workshop'), 3);
531 foreach ($submissions as $id => $submission) {
532 $link = new moodle_url('/mod/workshop/submission.php', array('id'=>$id, 'cmid'=>$submission->cmid));
533 if ($viewfullnames and $submission->authornamevisible) {
534 $author = $users[$submission->authorid];
535 } else {
536 $author = null;
537 }
538 print_recent_activity_note($submission->timemodified, $author, $submission->title, $link->out(), false, $viewfullnames);
539 }
540 }
541
542 if (!empty($assessments)) {
543 $shown = true;
544 echo $OUTPUT->heading(get_string('recentassessments', 'workshop'), 3);
545 foreach ($assessments as $id => $assessment) {
546 $link = new moodle_url('/mod/workshop/assessment.php', array('asid' => $id));
547 if ($viewfullnames and $assessment->reviewernamevisible) {
548 $reviewer = $users[$assessment->reviewerid];
549 } else {
550 $reviewer = null;
551 }
552 print_recent_activity_note($assessment->timemodified, $reviewer, $assessment->submissiontitle, $link->out(), false, $viewfullnames);
553 }
554 }
555
556 if ($shown) {
557 return true;
558 }
559
560 return false;
561}
562
563/**
564 * Returns all activity in course workshops since a given time
565 *
566 * @param array $activities sequentially indexed array of objects
567 * @param int $index
568 * @param int $timestart
569 * @param int $courseid
570 * @param int $cmid
571 * @param int $userid defaults to 0
572 * @param int $groupid defaults to 0
573 * @return void adds items into $activities and increases $index
574 */
575function workshop_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid=0, $groupid=0) {
576 global $CFG, $COURSE, $USER, $DB;
577
578 if ($COURSE->id == $courseid) {
579 $course = $COURSE;
580 } else {
581 $course = $DB->get_record('course', array('id'=>$courseid));
582 }
583
f20edd52 584 $modinfo = get_fast_modinfo($course);
7a5f4be0
DM
585
586 $cm = $modinfo->cms[$cmid];
587
588 $params = array();
589 if ($userid) {
590 $userselect = "AND (author.id = :authorid OR reviewer.id = :reviewerid)";
591 $params['authorid'] = $userid;
592 $params['reviewerid'] = $userid;
593 } else {
594 $userselect = "";
595 }
596
597 if ($groupid) {
598 $groupselect = "AND (authorgroupmembership.groupid = :authorgroupid OR reviewergroupmembership.groupid = :reviewergroupid)";
6c09cb4a 599 $groupjoin = "LEFT JOIN {groups_members} authorgroupmembership ON authorgroupmembership.userid = author.id
114dd080 600 LEFT JOIN {groups_members} reviewergroupmembership ON reviewergroupmembership.userid = reviewer.id";
7a5f4be0
DM
601 $params['authorgroupid'] = $groupid;
602 $params['reviewergroupid'] = $groupid;
603 } else {
604 $groupselect = "";
605 $groupjoin = "";
606 }
607
608 $params['cminstance'] = $cm->instance;
609 $params['submissionmodified'] = $timestart;
610 $params['assessmentmodified'] = $timestart;
611
612 $sql = "SELECT s.id AS submissionid, s.title AS submissiontitle, s.timemodified AS submissionmodified,
613 author.id AS authorid, author.lastname AS authorlastname, author.firstname AS authorfirstname,
614 author.picture AS authorpicture, author.imagealt AS authorimagealt, author.email AS authoremail,
615 a.id AS assessmentid, a.timemodified AS assessmentmodified,
616 reviewer.id AS reviewerid, reviewer.lastname AS reviewerlastname, reviewer.firstname AS reviewerfirstname,
617 reviewer.picture AS reviewerpicture, reviewer.imagealt AS reviewerimagealt, reviewer.email AS revieweremail
618 FROM {workshop_submissions} s
619 INNER JOIN {workshop} w ON s.workshopid = w.id
620 INNER JOIN {user} author ON s.authorid = author.id
621 LEFT JOIN {workshop_assessments} a ON a.submissionid = s.id
622 LEFT JOIN {user} reviewer ON a.reviewerid = reviewer.id
623 $groupjoin
624 WHERE w.id = :cminstance
625 AND s.example = 0
626 $userselect $groupselect
627 AND (s.timemodified > :submissionmodified OR a.timemodified > :assessmentmodified)
628 ORDER BY s.timemodified ASC, a.timemodified ASC";
629
630 $rs = $DB->get_recordset_sql($sql, $params);
631
632 $groupmode = groups_get_activity_groupmode($cm, $course);
633 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
634 $grader = has_capability('moodle/grade:viewall', $context);
635 $accessallgroups = has_capability('moodle/site:accessallgroups', $context);
636 $viewfullnames = has_capability('moodle/site:viewfullnames', $context);
637 $viewauthors = has_capability('mod/workshop:viewauthornames', $context);
638 $viewreviewers = has_capability('mod/workshop:viewreviewernames', $context);
639
640 if (is_null($modinfo->groups)) {
641 $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
642 }
643
644 $submissions = array(); // recent submissions indexed by submission id
645 $assessments = array(); // recent assessments indexed by assessment id
646 $users = array();
647
648 foreach ($rs as $activity) {
649
650 if ($viewfullnames) {
651 // remember all user names we can use later
652 if (empty($users[$activity->authorid])) {
653 $u = new stdclass();
654 $u->id = $activity->authorid;
655 $u->lastname = $activity->authorlastname;
656 $u->firstname = $activity->authorfirstname;
657 $u->picture = $activity->authorpicture;
658 $u->imagealt = $activity->authorimagealt;
659 $u->email = $activity->authoremail;
660 $users[$activity->authorid] = $u;
661 }
662 if ($activity->reviewerid and empty($users[$activity->reviewerid])) {
663 $u = new stdclass();
664 $u->id = $activity->reviewerid;
665 $u->lastname = $activity->reviewerlastname;
666 $u->firstname = $activity->reviewerfirstname;
667 $u->picture = $activity->reviewerpicture;
668 $u->imagealt = $activity->reviewerimagealt;
669 $u->email = $activity->revieweremail;
670 $users[$activity->reviewerid] = $u;
671 }
672 }
673
674 if ($activity->submissionmodified > $timestart and empty($submissions[$activity->submissionid])) {
675 $s = new stdclass();
676 $s->id = $activity->submissionid;
677 $s->title = $activity->submissiontitle;
678 $s->authorid = $activity->authorid;
679 $s->timemodified = $activity->submissionmodified;
680 if (has_capability('mod/workshop:viewauthornames', $context)) {
681 $s->authornamevisible = true;
682 } else {
683 $s->authornamevisible = false;
684 }
685
686 // the following do-while wrapper allows to break from deeply nested if-statements
687 do {
688 if ($s->authorid === $USER->id) {
689 // own submissions always visible
690 $submissions[$activity->submissionid] = $s;
691 break;
692 }
693
694 if (has_capability('mod/workshop:viewallsubmissions', $context)) {
695 if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
696 if (isguestuser()) {
697 // shortcut - guest user does not belong into any group
698 break;
699 }
700
701 // this might be slow - show only submissions by users who share group with me in this cm
702 if (empty($modinfo->groups[$cm->id])) {
703 break;
704 }
705 $authorsgroups = groups_get_all_groups($course->id, $s->authorid, $cm->groupingid);
706 if (is_array($authorsgroups)) {
707 $authorsgroups = array_keys($authorsgroups);
708 $intersect = array_intersect($authorsgroups, $modinfo->groups[$cm->id]);
709 if (empty($intersect)) {
710 break;
711 } else {
712 // can see all submissions and shares a group with the author
713 $submissions[$activity->submissionid] = $s;
714 break;
715 }
716 }
717
718 } else {
719 // can see all submissions from all groups
720 $submissions[$activity->submissionid] = $s;
721 }
722 }
723 } while (0);
724 }
725
726 if ($activity->assessmentmodified > $timestart and empty($assessments[$activity->assessmentid])) {
727 $a = new stdclass();
728 $a->id = $activity->assessmentid;
729 $a->submissionid = $activity->submissionid;
730 $a->submissiontitle = $activity->submissiontitle;
731 $a->reviewerid = $activity->reviewerid;
732 $a->timemodified = $activity->assessmentmodified;
733 if (has_capability('mod/workshop:viewreviewernames', $context)) {
734 $a->reviewernamevisible = true;
735 } else {
736 $a->reviewernamevisible = false;
737 }
738
739 // the following do-while wrapper allows to break from deeply nested if-statements
740 do {
741 if ($a->reviewerid === $USER->id) {
742 // own assessments always visible
743 $assessments[$activity->assessmentid] = $a;
744 break;
745 }
746
747 if (has_capability('mod/workshop:viewallassessments', $context)) {
748 if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
749 if (isguestuser()) {
750 // shortcut - guest user does not belong into any group
751 break;
752 }
753
754 // this might be slow - show only submissions by users who share group with me in this cm
755 if (empty($modinfo->groups[$cm->id])) {
756 break;
757 }
758 $reviewersgroups = groups_get_all_groups($course->id, $a->reviewerid, $cm->groupingid);
759 if (is_array($reviewersgroups)) {
760 $reviewersgroups = array_keys($reviewersgroups);
761 $intersect = array_intersect($reviewersgroups, $modinfo->groups[$cm->id]);
762 if (empty($intersect)) {
763 break;
764 } else {
765 // can see all assessments and shares a group with the reviewer
766 $assessments[$activity->assessmentid] = $a;
767 break;
768 }
769 }
770
771 } else {
772 // can see all assessments from all groups
773 $assessments[$activity->assessmentid] = $a;
774 }
775 }
776 } while (0);
777 }
778 }
779 $rs->close();
780
781 $workshopname = format_string($cm->name, true);
782
783 if ($grader) {
784 require_once($CFG->libdir.'/gradelib.php');
785 $grades = grade_get_grades($courseid, 'mod', 'workshop', $cm->instance, array_keys($users));
786 }
787
788 foreach ($submissions as $submission) {
789 $tmpactivity = new stdclass();
790 $tmpactivity->type = 'workshop';
791 $tmpactivity->cmid = $cm->id;
792 $tmpactivity->name = $workshopname;
793 $tmpactivity->sectionnum = $cm->sectionnum;
794 $tmpactivity->timestamp = $submission->timemodified;
795 $tmpactivity->subtype = 'submission';
796 $tmpactivity->content = $submission;
797 if ($grader) {
798 $tmpactivity->grade = $grades->items[0]->grades[$submission->authorid]->str_long_grade;
799 }
800 if ($submission->authornamevisible and !empty($users[$submission->authorid])) {
801 $tmpactivity->user = $users[$submission->authorid];
802 }
803 $activities[$index++] = $tmpactivity;
804 }
805
806 foreach ($assessments as $assessment) {
807 $tmpactivity = new stdclass();
808 $tmpactivity->type = 'workshop';
809 $tmpactivity->cmid = $cm->id;
810 $tmpactivity->name = $workshopname;
811 $tmpactivity->sectionnum = $cm->sectionnum;
812 $tmpactivity->timestamp = $assessment->timemodified;
813 $tmpactivity->subtype = 'assessment';
814 $tmpactivity->content = $assessment;
815 if ($grader) {
816 $tmpactivity->grade = $grades->items[1]->grades[$assessment->reviewerid]->str_long_grade;
817 }
818 if ($assessment->reviewernamevisible and !empty($users[$assessment->reviewerid])) {
819 $tmpactivity->user = $users[$assessment->reviewerid];
820 }
821 $activities[$index++] = $tmpactivity;
822 }
823}
824
825/**
826 * Print single activity item prepared by {@see workshop_get_recent_mod_activity()}
827 */
828function workshop_print_recent_mod_activity($activity, $courseid, $detail, $modnames, $viewfullnames) {
829 global $CFG, $OUTPUT;
830
831 if (!empty($activity->user)) {
832 echo html_writer::tag('div', $OUTPUT->user_picture($activity->user, array('courseid'=>$courseid)),
833 array('style' => 'float: left; padding: 7px;'));
834 }
835
836 if ($activity->subtype == 'submission') {
837 echo html_writer::start_tag('div', array('class'=>'submission', 'style'=>'padding: 7px; float:left;'));
838
839 if ($detail) {
840 echo html_writer::start_tag('h4', array('class'=>'workshop'));
841 $url = new moodle_url('/mod/workshop/view.php', array('id'=>$activity->cmid));
842 $name = s($activity->name);
843 echo html_writer::empty_tag('img', array('src'=>$OUTPUT->pix_url('icon', $activity->type), 'class'=>'icon', 'alt'=>$name));
844 echo ' ' . $modnames[$activity->type];
845 echo html_writer::link($url, $name, array('class'=>'name', 'style'=>'margin-left: 5px'));
846 echo html_writer::end_tag('h4');
847 }
848
849 echo html_writer::start_tag('div', array('class'=>'title'));
54fe8470 850 $url = new moodle_url('/mod/workshop/submission.php', array('cmid'=>$activity->cmid, 'id'=>$activity->content->id));
7a5f4be0
DM
851 $name = s($activity->content->title);
852 echo html_writer::tag('strong', html_writer::link($url, $name));
853 echo html_writer::end_tag('div');
854
855 if (!empty($activity->user)) {
856 echo html_writer::start_tag('div', array('class'=>'user'));
857 $url = new moodle_url('/user/view.php', array('id'=>$activity->user->id, 'course'=>$courseid));
858 $name = fullname($activity->user);
859 $link = html_writer::link($url, $name);
860 echo get_string('submissionby', 'workshop', $link);
861 echo ' - '.userdate($activity->timestamp);
862 echo html_writer::end_tag('div');
863 } else {
864 echo html_writer::start_tag('div', array('class'=>'anonymous'));
865 echo get_string('submission', 'workshop');
866 echo ' - '.userdate($activity->timestamp);
867 echo html_writer::end_tag('div');
868 }
869
870 echo html_writer::end_tag('div');
871 }
872
873 if ($activity->subtype == 'assessment') {
874 echo html_writer::start_tag('div', array('class'=>'assessment', 'style'=>'padding: 7px; float:left;'));
875
876 if ($detail) {
877 echo html_writer::start_tag('h4', array('class'=>'workshop'));
878 $url = new moodle_url('/mod/workshop/view.php', array('id'=>$activity->cmid));
879 $name = s($activity->name);
880 echo html_writer::empty_tag('img', array('src'=>$OUTPUT->pix_url('icon', $activity->type), 'class'=>'icon', 'alt'=>$name));
881 echo ' ' . $modnames[$activity->type];
882 echo html_writer::link($url, $name, array('class'=>'name', 'style'=>'margin-left: 5px'));
883 echo html_writer::end_tag('h4');
884 }
885
886 echo html_writer::start_tag('div', array('class'=>'title'));
54fe8470 887 $url = new moodle_url('/mod/workshop/assessment.php', array('asid'=>$activity->content->id));
7a5f4be0
DM
888 $name = s($activity->content->submissiontitle);
889 echo html_writer::tag('em', html_writer::link($url, $name));
890 echo html_writer::end_tag('div');
891
892 if (!empty($activity->user)) {
893 echo html_writer::start_tag('div', array('class'=>'user'));
894 $url = new moodle_url('/user/view.php', array('id'=>$activity->user->id, 'course'=>$courseid));
895 $name = fullname($activity->user);
896 $link = html_writer::link($url, $name);
38504a44 897 echo get_string('assessmentbyfullname', 'workshop', $link);
7a5f4be0
DM
898 echo ' - '.userdate($activity->timestamp);
899 echo html_writer::end_tag('div');
900 } else {
901 echo html_writer::start_tag('div', array('class'=>'anonymous'));
38504a44 902 echo get_string('assessment', 'workshop');
7a5f4be0
DM
903 echo ' - '.userdate($activity->timestamp);
904 echo html_writer::end_tag('div');
905 }
906
907 echo html_writer::end_tag('div');
908 }
909
910 echo html_writer::empty_tag('br', array('style'=>'clear:both'));
4eab2e7f
DM
911}
912
4eab2e7f 913/**
f6bc60cb 914 * Regular jobs to execute via cron
4eab2e7f 915 *
f6bc60cb
DM
916 * @return boolean true on success, false otherwise
917 */
918function workshop_cron() {
9260bb3c
DM
919 global $CFG, $DB;
920
921 $now = time();
922
a80b7728 923 mtrace(' processing workshop subplugins ...');
f6bc60cb 924 cron_execute_plugin_type('workshopallocation', 'workshop allocation methods');
a80b7728 925
9260bb3c
DM
926 // now when the scheduled allocator had a chance to do its job, check if there
927 // are some workshops to switch into the assessment phase
928 $workshops = $DB->get_records_select("workshop",
929 "phase = 20 AND phaseswitchassessment = 1 AND submissionend > 0 AND submissionend < ?", array($now));
930
931 if (!empty($workshops)) {
932 mtrace('Processing automatic assessment phase switch in '.count($workshops).' workshop(s) ... ', '');
933 require_once($CFG->dirroot.'/mod/workshop/locallib.php');
934 foreach ($workshops as $workshop) {
935 $cm = get_coursemodule_from_instance('workshop', $workshop->id, $workshop->course, false, MUST_EXIST);
936 $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
937 $workshop = new workshop($workshop, $cm, $course);
938 $workshop->switch_phase(workshop::PHASE_ASSESSMENT);
939 $workshop->log('update switch phase', $workshop->view_url(), $workshop->phase);
940 // disable the automatic switching now so that it is not executed again by accident
941 // if the teacher changes the phase back to the submission one
942 $DB->set_field('workshop', 'phaseswitchassessment', 0, array('id' => $workshop->id));
943
944 // todo inform the teachers
945 }
946 mtrace('done');
947 }
948
4eab2e7f
DM
949 return true;
950}
951
4eab2e7f 952/**
7a2d0f61 953 * Is a given scale used by the instance of workshop?
4eab2e7f 954 *
7a2d0f61
DM
955 * The function asks all installed grading strategy subplugins. The workshop
956 * core itself does not use scales. Both grade for submission and grade for
957 * assessments do not use scales.
958 *
959 * @param int $workshopid id of workshop instance
960 * @param int $scaleid id of the scale to check
961 * @return bool
4eab2e7f
DM
962 */
963function workshop_scale_used($workshopid, $scaleid) {
7a2d0f61
DM
964 global $CFG; // other files included from here
965
966 $strategies = get_plugin_list('workshopform');
967 foreach ($strategies as $strategy => $strategypath) {
968 $strategylib = $strategypath . '/lib.php';
969 if (is_readable($strategylib)) {
970 require_once($strategylib);
971 } else {
972 throw new coding_exception('the grading forms subplugin must contain library ' . $strategylib);
973 }
974 $classname = 'workshop_' . $strategy . '_strategy';
975 if (method_exists($classname, 'scale_used')) {
976 if (call_user_func_array(array($classname, 'scale_used'), array($scaleid, $workshopid))) {
977 // no need to include any other files - scale is used
978 return true;
979 }
980 }
981 }
4eab2e7f 982
7a2d0f61 983 return false;
4eab2e7f
DM
984}
985
4eab2e7f 986/**
7a2d0f61
DM
987 * Is a given scale used by any instance of workshop?
988 *
989 * The function asks all installed grading strategy subplugins. The workshop
990 * core itself does not use scales. Both grade for submission and grade for
991 * assessments do not use scales.
4eab2e7f 992 *
7a2d0f61
DM
993 * @param int $scaleid id of the scale to check
994 * @return bool
4eab2e7f
DM
995 */
996function workshop_scale_used_anywhere($scaleid) {
7a2d0f61
DM
997 global $CFG; // other files included from here
998
999 $strategies = get_plugin_list('workshopform');
1000 foreach ($strategies as $strategy => $strategypath) {
1001 $strategylib = $strategypath . '/lib.php';
1002 if (is_readable($strategylib)) {
1003 require_once($strategylib);
1004 } else {
1005 throw new coding_exception('the grading forms subplugin must contain library ' . $strategylib);
1006 }
1007 $classname = 'workshop_' . $strategy . '_strategy';
1008 if (method_exists($classname, 'scale_used')) {
1009 if (call_user_func(array($classname, 'scale_used'), $scaleid)) {
1010 // no need to include any other files - scale is used
1011 return true;
1012 }
1013 }
4eab2e7f 1014 }
7a2d0f61
DM
1015
1016 return false;
4eab2e7f
DM
1017}
1018
0dc47fb9
DM
1019/**
1020 * Returns all other caps used in the module
1021 *
1022 * @return array
1023 */
1024function workshop_get_extra_capabilities() {
1025 return array('moodle/site:accessallgroups');
1026}
1027
365c2cc2
DM
1028////////////////////////////////////////////////////////////////////////////////
1029// Gradebook API //
1030////////////////////////////////////////////////////////////////////////////////
1031
1032/**
1033 * Creates or updates grade items for the give workshop instance
1034 *
1035 * Needed by grade_update_mod_grades() in lib/gradelib.php. Also used by
1036 * {@link workshop_update_grades()}.
1037 *
8fdecaa5 1038 * @param stdClass $workshop instance object with extra cmidnumber property
5924db72
PS
1039 * @param stdClass $submissiongrades data for the first grade item
1040 * @param stdClass $assessmentgrades data for the second grade item
365c2cc2
DM
1041 * @return void
1042 */
7a789aa8 1043function workshop_grade_item_update(stdclass $workshop, $submissiongrades=null, $assessmentgrades=null) {
365c2cc2
DM
1044 global $CFG;
1045 require_once($CFG->libdir.'/gradelib.php');
1046
7a789aa8 1047 $a = new stdclass();
365c2cc2
DM
1048 $a->workshopname = clean_param($workshop->name, PARAM_NOTAGS);
1049
1050 $item = array();
1051 $item['itemname'] = get_string('gradeitemsubmission', 'workshop', $a);
365c2cc2
DM
1052 $item['gradetype'] = GRADE_TYPE_VALUE;
1053 $item['grademax'] = $workshop->grade;
1054 $item['grademin'] = 0;
1055 grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 0, $submissiongrades , $item);
1056
1057 $item = array();
1058 $item['itemname'] = get_string('gradeitemassessment', 'workshop', $a);
365c2cc2
DM
1059 $item['gradetype'] = GRADE_TYPE_VALUE;
1060 $item['grademax'] = $workshop->gradinggrade;
1061 $item['grademin'] = 0;
1062 grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 1, $assessmentgrades, $item);
1063}
1064
1065/**
1066 * Update workshop grades in the gradebook
1067 *
1068 * Needed by grade_update_mod_grades() in lib/gradelib.php
1069 *
a153c9f2 1070 * @category grade
5924db72 1071 * @param stdClass $workshop instance object with extra cmidnumber and modname property
365c2cc2
DM
1072 * @param int $userid update grade of specific user only, 0 means all participants
1073 * @return void
1074 */
7a789aa8 1075function workshop_update_grades(stdclass $workshop, $userid=0) {
365c2cc2
DM
1076 global $CFG, $DB;
1077 require_once($CFG->libdir.'/gradelib.php');
1078
1079 $whereuser = $userid ? ' AND authorid = :userid' : '';
10bc4bce
DM
1080 $params = array('workshopid' => $workshop->id, 'userid' => $userid);
1081 $sql = 'SELECT authorid, grade, gradeover, gradeoverby, feedbackauthor, feedbackauthorformat, timemodified, timegraded
365c2cc2
DM
1082 FROM {workshop_submissions}
1083 WHERE workshopid = :workshopid AND example=0' . $whereuser;
10bc4bce
DM
1084 $records = $DB->get_records_sql($sql, $params);
1085 $submissiongrades = array();
1086 foreach ($records as $record) {
7a789aa8 1087 $grade = new stdclass();
10bc4bce
DM
1088 $grade->userid = $record->authorid;
1089 if (!is_null($record->gradeover)) {
1090 $grade->rawgrade = grade_floatval($workshop->grade * $record->gradeover / 100);
1091 $grade->usermodified = $record->gradeoverby;
1092 } else {
1093 $grade->rawgrade = grade_floatval($workshop->grade * $record->grade / 100);
1094 }
1095 $grade->feedback = $record->feedbackauthor;
1096 $grade->feedbackformat = $record->feedbackauthorformat;
1097 $grade->datesubmitted = $record->timemodified;
1098 $grade->dategraded = $record->timegraded;
1099 $submissiongrades[$record->authorid] = $grade;
1100 }
365c2cc2
DM
1101
1102 $whereuser = $userid ? ' AND userid = :userid' : '';
10bc4bce
DM
1103 $params = array('workshopid' => $workshop->id, 'userid' => $userid);
1104 $sql = 'SELECT userid, gradinggrade, timegraded
365c2cc2
DM
1105 FROM {workshop_aggregations}
1106 WHERE workshopid = :workshopid' . $whereuser;
10bc4bce
DM
1107 $records = $DB->get_records_sql($sql, $params);
1108 $assessmentgrades = array();
1109 foreach ($records as $record) {
7a789aa8 1110 $grade = new stdclass();
10bc4bce
DM
1111 $grade->userid = $record->userid;
1112 $grade->rawgrade = grade_floatval($workshop->gradinggrade * $record->gradinggrade / 100);
1113 $grade->dategraded = $record->timegraded;
1114 $assessmentgrades[$record->userid] = $grade;
1115 }
365c2cc2
DM
1116
1117 workshop_grade_item_update($workshop, $submissiongrades, $assessmentgrades);
1118}
1119
a74cadfa
DM
1120/**
1121 * Update the grade items categories if they are changed via mod_form.php
1122 *
1123 * We must do it manually here in the workshop module because modedit supports only
1124 * single grade item while we use two.
1125 *
1126 * @param stdClass $workshop An object from the form in mod_form.php
1127 */
1128function workshop_grade_item_category_update($workshop) {
1129
1130 $gradeitems = grade_item::fetch_all(array(
1131 'itemtype' => 'mod',
1132 'itemmodule' => 'workshop',
1133 'iteminstance' => $workshop->id,
1134 'courseid' => $workshop->course));
1135
1136 if (!empty($gradeitems)) {
1137 foreach ($gradeitems as $gradeitem) {
1138 if ($gradeitem->itemnumber == 0) {
1139 if ($gradeitem->categoryid != $workshop->gradecategory) {
1140 $gradeitem->set_parent($workshop->gradecategory);
1141 }
1142 } else if ($gradeitem->itemnumber == 1) {
1143 if ($gradeitem->categoryid != $workshop->gradinggradecategory) {
1144 $gradeitem->set_parent($workshop->gradinggradecategory);
1145 }
1146 }
1147 }
1148 }
1149}
1150
b8ead2e6
DM
1151////////////////////////////////////////////////////////////////////////////////
1152// File API //
1153////////////////////////////////////////////////////////////////////////////////
0dc47fb9 1154
a39d7d87
DM
1155/**
1156 * Returns the lists of all browsable file areas within the given module context
1157 *
1158 * The file area workshop_intro for the activity introduction field is added automatically
64f93798 1159 * by {@link file_browser::get_file_info_context_module()}
a39d7d87 1160 *
d2b7803e
DC
1161 * @package mod_workshop
1162 * @category files
1163 *
5924db72
PS
1164 * @param stdClass $course
1165 * @param stdClass $cm
1166 * @param stdClass $context
a39d7d87
DM
1167 * @return array of [(string)filearea] => (string)description
1168 */
1169function workshop_get_file_areas($course, $cm, $context) {
1170 $areas = array();
64f93798
PS
1171 $areas['instructauthors'] = get_string('areainstructauthors', 'workshop');
1172 $areas['instructreviewers'] = get_string('areainstructreviewers', 'workshop');
1173 $areas['submission_content'] = get_string('areasubmissioncontent', 'workshop');
1174 $areas['submission_attachment'] = get_string('areasubmissionattachment', 'workshop');
1175
a39d7d87
DM
1176 return $areas;
1177}
1178
0dc47fb9 1179/**
b8ead2e6 1180 * Serves the files from the workshop file areas
0dc47fb9 1181 *
b8ead2e6
DM
1182 * Apart from module intro (handled by pluginfile.php automatically), workshop files may be
1183 * media inserted into submission content (like images) and submission attachments. For these two,
7528e238
DM
1184 * the fileareas submission_content and submission_attachment are used.
1185 * Besides that, areas instructauthors and instructreviewers contain the media
6516b9e9 1186 * embedded using the mod_form.php.
0dc47fb9 1187 *
d2b7803e
DC
1188 * @package mod_workshop
1189 * @category files
1190 *
261cbbac
DM
1191 * @param stdClass $course the course object
1192 * @param stdClass $cm the course module object
1193 * @param stdClass $context the workshop's context
1194 * @param string $filearea the name of the file area
1195 * @param array $args extra arguments (itemid, path)
1196 * @param bool $forcedownload whether or not force download
1197 * @param array $options additional options affecting the file serving
1198 * @return bool false if the file not found, just send the file otherwise and do not return anything
0dc47fb9 1199 */
261cbbac 1200function workshop_pluginfile($course, $cm, $context, $filearea, array $args, $forcedownload, array $options=array()) {
7528e238 1201 global $DB, $CFG, $USER;
0dc47fb9 1202
64f93798
PS
1203 if ($context->contextlevel != CONTEXT_MODULE) {
1204 return false;
6516b9e9 1205 }
64f93798 1206
6516b9e9
DM
1207 require_login($course, true, $cm);
1208
64f93798 1209 if ($filearea === 'instructauthors') {
92039f1c 1210 array_shift($args); // itemid is ignored here
64f93798 1211 $relativepath = implode('/', $args);
261cbbac 1212 $fullpath = "/$context->id/mod_workshop/$filearea/0/$relativepath";
6516b9e9
DM
1213
1214 $fs = get_file_storage();
1215 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
15d12b54
DM
1216 send_file_not_found();
1217 }
1218
1219 $lifetime = isset($CFG->filelifetime) ? $CFG->filelifetime : 86400;
1220
1221 // finally send the file
261cbbac 1222 send_stored_file($file, $lifetime, 0, $forcedownload, $options);
15d12b54
DM
1223 }
1224
64f93798 1225 if ($filearea === 'instructreviewers') {
92039f1c 1226 array_shift($args); // itemid is ignored here
64f93798 1227 $relativepath = implode('/', $args);
7a5f4be0 1228 $fullpath = "/$context->id/mod_workshop/$filearea/0/$relativepath";
15d12b54
DM
1229
1230 $fs = get_file_storage();
1231 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
6516b9e9
DM
1232 send_file_not_found();
1233 }
1234
1235 $lifetime = isset($CFG->filelifetime) ? $CFG->filelifetime : 86400;
1236
1237 // finally send the file
261cbbac 1238 send_stored_file($file, $lifetime, 0, $forcedownload, $options);
0dc47fb9 1239
64f93798 1240 } else if ($filearea === 'submission_content' or $filearea === 'submission_attachment') {
18cbfe9b 1241 $itemid = (int)array_shift($args);
64f93798 1242 if (!$workshop = $DB->get_record('workshop', array('id' => $cm->instance))) {
18cbfe9b
DM
1243 return false;
1244 }
64f93798 1245 if (!$submission = $DB->get_record('workshop_submissions', array('id' => $itemid, 'workshopid' => $workshop->id))) {
18cbfe9b
DM
1246 return false;
1247 }
7528e238
DM
1248
1249 // make sure the user is allowed to see the file
1250 if (empty($submission->example)) {
1251 if ($USER->id != $submission->authorid) {
1252 if (!$DB->record_exists('workshop_assessments', array('submissionid' => $submission->id, 'reviewerid' => $USER->id))) {
1253 if (!has_capability('mod/workshop:viewallsubmissions', $context)) {
1254 send_file_not_found();
1255 } else {
1256 $gmode = groups_get_activity_groupmode($cm, $course);
1257 if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1258 // check there is at least one common group with both the $USER
1259 // and the submission author
1260 $sql = "SELECT 'x'
1261 FROM {workshop_submissions} s
1262 JOIN {user} a ON (a.id = s.authorid)
1263 JOIN {groups_members} agm ON (a.id = agm.userid)
1264 JOIN {user} u ON (u.id = ?)
1265 JOIN {groups_members} ugm ON (u.id = ugm.userid)
1266 WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1267 $params = array($USER->id, $workshop->id, $submission->id);
1268 if (!$DB->record_exists_sql($sql, $params)) {
1269 send_file_not_found();
1270 }
1271 }
1272 }
1273 }
1274 }
1275 }
1276
18cbfe9b 1277 $fs = get_file_storage();
64f93798
PS
1278 $relativepath = implode('/', $args);
1279 $fullpath = "/$context->id/mod_workshop/$filearea/$itemid/$relativepath";
18cbfe9b
DM
1280 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1281 return false;
1282 }
1283 // finally send the file
1284 // these files are uploaded by students - forcing download for security reasons
261cbbac 1285 send_stored_file($file, 0, 0, true, $options);
0dc47fb9 1286 }
18cbfe9b
DM
1287
1288 return false;
0dc47fb9
DM
1289}
1290
1291/**
1292 * File browsing support for workshop file areas
1293 *
d2b7803e
DC
1294 * @package mod_workshop
1295 * @category files
1296 *
1297 * @param file_browser $browser
1298 * @param array $areas
5924db72
PS
1299 * @param stdClass $course
1300 * @param stdClass $cm
1301 * @param stdClass $context
0dc47fb9
DM
1302 * @param string $filearea
1303 * @param int $itemid
1304 * @param string $filepath
1305 * @param string $filename
d2b7803e 1306 * @return file_info instance or null if not found
0dc47fb9
DM
1307 */
1308function workshop_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
b7a5e3d6
DM
1309 global $CFG, $DB, $USER;
1310 /** @var array internal cache for author names */
1311 static $submissionauthors = array();
0dc47fb9 1312
0dc47fb9 1313 $fs = get_file_storage();
a39d7d87 1314
f08c1568 1315 if ($filearea === 'submission_content' or $filearea === 'submission_attachment') {
a39d7d87 1316
b7a5e3d6
DM
1317 if (!has_capability('mod/workshop:viewallsubmissions', $context)) {
1318 return null;
1319 }
1320
a39d7d87 1321 if (is_null($itemid)) {
b7a5e3d6 1322 // no itemid (submissionid) passed, display the list of all submissions
a39d7d87
DM
1323 require_once($CFG->dirroot . '/mod/workshop/fileinfolib.php');
1324 return new workshop_file_info_submissions_container($browser, $course, $cm, $context, $areas, $filearea);
1325 }
1326
b7a5e3d6
DM
1327 // make sure the user can see the particular submission in separate groups mode
1328 $gmode = groups_get_activity_groupmode($cm, $course);
1329
1330 if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1331 // check there is at least one common group with both the $USER
1332 // and the submission author (this is not expected to be a frequent
1333 // usecase so we can live with pretty ineffective one query per submission here...)
1334 $sql = "SELECT 'x'
1335 FROM {workshop_submissions} s
1336 JOIN {user} a ON (a.id = s.authorid)
1337 JOIN {groups_members} agm ON (a.id = agm.userid)
1338 JOIN {user} u ON (u.id = ?)
1339 JOIN {groups_members} ugm ON (u.id = ugm.userid)
1340 WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1341 $params = array($USER->id, $cm->instance, $itemid);
1342 if (!$DB->record_exists_sql($sql, $params)) {
1343 return null;
1344 }
1345 }
1346
1347 // we are inside some particular submission container
a39d7d87
DM
1348
1349 $filepath = is_null($filepath) ? '/' : $filepath;
1350 $filename = is_null($filename) ? '.' : $filename;
1351
64f93798 1352 if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, $itemid, $filepath, $filename)) {
a39d7d87 1353 if ($filepath === '/' and $filename === '.') {
64f93798 1354 $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, $itemid);
a39d7d87
DM
1355 } else {
1356 // not found
1357 return null;
1358 }
1359 }
1360
35ca63c1
AG
1361 // Checks to see if the user can manage files or is the owner.
1362 // TODO MDL-33805 - Do not use userid here and move the capability check above.
1363 if (!has_capability('moodle/course:managefiles', $context) && $storedfile->get_userid() != $USER->id) {
1364 return null;
1365 }
1366
a39d7d87 1367 // let us display the author's name instead of itemid (submission id)
b7a5e3d6
DM
1368
1369 if (isset($submissionauthors[$itemid])) {
1370 $topvisiblename = $submissionauthors[$itemid];
1371
1372 } else {
1373
1374 $sql = "SELECT s.id, u.lastname, u.firstname
1375 FROM {workshop_submissions} s
1376 JOIN {user} u ON (s.authorid = u.id)
1377 WHERE s.example = 0 AND s.workshopid = ?";
1378 $params = array($cm->instance);
1379 $rs = $DB->get_recordset_sql($sql, $params);
1380
1381 foreach ($rs as $submissionauthor) {
1382 $title = s(fullname($submissionauthor)); // this is generally not unique...
1383 $submissionauthors[$submissionauthor->id] = $title;
1384 }
1385 $rs->close();
1386
1387 if (!isset($submissionauthors[$itemid])) {
1388 // should not happen
1389 return null;
1390 } else {
1391 $topvisiblename = $submissionauthors[$itemid];
1392 }
1393 }
1394
1395 $urlbase = $CFG->wwwroot . '/pluginfile.php';
a39d7d87
DM
1396 // do not allow manual modification of any files!
1397 return new file_info_stored($browser, $context, $storedfile, $urlbase, $topvisiblename, true, true, false, false);
0dc47fb9
DM
1398 }
1399
64f93798 1400 if ($filearea == 'instructauthors' or $filearea == 'instructreviewers') {
a39d7d87
DM
1401 // always only itemid 0
1402
1403 $filepath = is_null($filepath) ? '/' : $filepath;
1404 $filename = is_null($filename) ? '.' : $filename;
1405
1406 $urlbase = $CFG->wwwroot.'/pluginfile.php';
64f93798 1407 if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, 0, $filepath, $filename)) {
a39d7d87 1408 if ($filepath === '/' and $filename === '.') {
64f93798 1409 $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, 0);
a39d7d87
DM
1410 } else {
1411 // not found
1412 return null;
1413 }
1414 }
1415 return new file_info_stored($browser, $context, $storedfile, $urlbase, $areas[$filearea], false, true, true, false);
0dc47fb9 1416 }
0dc47fb9
DM
1417}
1418
6516b9e9
DM
1419////////////////////////////////////////////////////////////////////////////////
1420// Navigation API //
1421////////////////////////////////////////////////////////////////////////////////
1422
39861053
DM
1423/**
1424 * Extends the global navigation tree by adding workshop nodes if there is a relevant content
1425 *
b761e6d9
DM
1426 * This can be called by an AJAX request so do not rely on $PAGE as it might not be set up properly.
1427 *
39861053 1428 * @param navigation_node $navref An object representing the navigation tree node of the workshop module instance
5924db72
PS
1429 * @param stdClass $course
1430 * @param stdClass $module
2addb4ba 1431 * @param cm_info $cm
39861053 1432 */
2addb4ba 1433function workshop_extend_navigation(navigation_node $navref, stdclass $course, stdclass $module, cm_info $cm) {
39861053
DM
1434 global $CFG;
1435
4f0c2d00 1436 if (has_capability('mod/workshop:submit', get_context_instance(CONTEXT_MODULE, $cm->id))) {
a6855934 1437 $url = new moodle_url('/mod/workshop/submission.php', array('cmid' => $cm->id));
3406acde
SH
1438 $mysubmission = $navref->add(get_string('mysubmission', 'workshop'), $url);
1439 $mysubmission->mainnavonly = true;
39861053
DM
1440 }
1441}
1442
1443/**
1444 * Extends the settings navigation with the Workshop settings
1445
b761e6d9
DM
1446 * This function is called when the context for the page is a workshop module. This is not called by AJAX
1447 * so it is safe to rely on the $PAGE.
39861053
DM
1448 *
1449 * @param settings_navigation $settingsnav {@link settings_navigation}
0b29477b 1450 * @param navigation_node $workshopnode {@link navigation_node}
39861053 1451 */
0b29477b
SH
1452function workshop_extend_settings_navigation(settings_navigation $settingsnav, navigation_node $workshopnode=null) {
1453 global $PAGE;
39861053 1454
39861053
DM
1455 //$workshopobject = $DB->get_record("workshop", array("id" => $PAGE->cm->instance));
1456
b761e6d9 1457 if (has_capability('mod/workshop:editdimensions', $PAGE->cm->context)) {
a6855934 1458 $url = new moodle_url('/mod/workshop/editform.php', array('cmid' => $PAGE->cm->id));
b761e6d9 1459 $workshopnode->add(get_string('editassessmentform', 'workshop'), $url, settings_navigation::TYPE_SETTING);
39861053 1460 }
428a28e1 1461 if (has_capability('mod/workshop:allocate', $PAGE->cm->context)) {
a6855934 1462 $url = new moodle_url('/mod/workshop/allocation.php', array('cmid' => $PAGE->cm->id));
b761e6d9 1463 $workshopnode->add(get_string('allocate', 'workshop'), $url, settings_navigation::TYPE_SETTING);
39861053
DM
1464 }
1465}
b1627a92
DC
1466
1467/**
1468 * Return a list of page types
1469 * @param string $pagetype current page type
1470 * @param stdClass $parentcontext Block's parent context
1471 * @param stdClass $currentcontext Current context of block
1472 */
b38e2e28 1473function workshop_page_type_list($pagetype, $parentcontext, $currentcontext) {
b1627a92
DC
1474 $module_pagetype = array('mod-workshop-*'=>get_string('page-mod-workshop-x', 'workshop'));
1475 return $module_pagetype;
1476}
ac069aeb
DM
1477
1478////////////////////////////////////////////////////////////////////////////////
1479// Calendar API //
1480////////////////////////////////////////////////////////////////////////////////
1481
1482/**
1483 * Updates the calendar events associated to the given workshop
1484 *
1485 * @param stdClass $workshop the workshop instance record
1486 * @param int $cmid course module id
1487 */
1488function workshop_calendar_update(stdClass $workshop, $cmid) {
1489 global $DB;
1490
1491 // get the currently registered events so that we can re-use their ids
1492 $currentevents = $DB->get_records('event', array('modulename' => 'workshop', 'instance' => $workshop->id));
1493
1494 // the common properties for all events
1495 $base = new stdClass();
1496 $base->description = format_module_intro('workshop', $workshop, $cmid, false);
1497 $base->courseid = $workshop->course;
1498 $base->groupid = 0;
1499 $base->userid = 0;
1500 $base->modulename = 'workshop';
1501 $base->eventtype = 'pluginname';
1502 $base->instance = $workshop->id;
1503 $base->visible = instance_is_visible('workshop', $workshop);
1504 $base->timeduration = 0;
1505
1506 if ($workshop->submissionstart) {
1507 $event = clone($base);
1508 $event->name = get_string('submissionstartevent', 'mod_workshop', $workshop->name);
1509 $event->timestart = $workshop->submissionstart;
1510 if ($reusedevent = array_shift($currentevents)) {
1511 $event->id = $reusedevent->id;
1512 } else {
1513 // should not be set but just in case
1514 unset($event->id);
1515 }
de7daa83
DM
1516 // update() will reuse a db record if the id field is set
1517 $eventobj = new calendar_event($event);
1518 $eventobj->update($event, false);
ac069aeb
DM
1519 }
1520
1521 if ($workshop->submissionend) {
1522 $event = clone($base);
1523 $event->name = get_string('submissionendevent', 'mod_workshop', $workshop->name);
1524 $event->timestart = $workshop->submissionend;
1525 if ($reusedevent = array_shift($currentevents)) {
1526 $event->id = $reusedevent->id;
1527 } else {
1528 // should not be set but just in case
1529 unset($event->id);
1530 }
de7daa83
DM
1531 // update() will reuse a db record if the id field is set
1532 $eventobj = new calendar_event($event);
1533 $eventobj->update($event, false);
ac069aeb
DM
1534 }
1535
1536 if ($workshop->assessmentstart) {
1537 $event = clone($base);
1538 $event->name = get_string('assessmentstartevent', 'mod_workshop', $workshop->name);
1539 $event->timestart = $workshop->assessmentstart;
1540 if ($reusedevent = array_shift($currentevents)) {
1541 $event->id = $reusedevent->id;
1542 } else {
1543 // should not be set but just in case
1544 unset($event->id);
1545 }
de7daa83
DM
1546 // update() will reuse a db record if the id field is set
1547 $eventobj = new calendar_event($event);
1548 $eventobj->update($event, false);
ac069aeb
DM
1549 }
1550
1551 if ($workshop->assessmentend) {
1552 $event = clone($base);
1553 $event->name = get_string('assessmentendevent', 'mod_workshop', $workshop->name);
1554 $event->timestart = $workshop->assessmentend;
1555 if ($reusedevent = array_shift($currentevents)) {
1556 $event->id = $reusedevent->id;
1557 } else {
1558 // should not be set but just in case
1559 unset($event->id);
1560 }
de7daa83
DM
1561 // update() will reuse a db record if the id field is set
1562 $eventobj = new calendar_event($event);
1563 $eventobj->update($event, false);
ac069aeb
DM
1564 }
1565
1566 // delete any leftover events
1567 foreach ($currentevents as $oldevent) {
1568 $oldevent = calendar_event::load($oldevent);
1569 $oldevent->delete();
1570 }
1571}