635774d02d3327af5867aa577bcd54b868a3a72d
[moodle.git] / mod / workshop / lib.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
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.
14 //
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/>.
18 /**
19  * Library of workshop module functions needed by Moodle core and other subsystems
20  *
21  * All the functions neeeded by Moodle core, gradebook, file subsystem etc
22  * are placed here.
23  *
24  * @package    mod_workshop
25  * @copyright  2009 David Mudrak <david.mudrak@gmail.com>
26  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27  */
29 defined('MOODLE_INTERNAL') || die();
31 require_once($CFG->dirroot . '/calendar/lib.php');
33 define('WORKSHOP_EVENT_TYPE_SUBMISSION_OPEN',   'opensubmission');
34 define('WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE',  'closesubmission');
35 define('WORKSHOP_EVENT_TYPE_ASSESSMENT_OPEN',   'openassessment');
36 define('WORKSHOP_EVENT_TYPE_ASSESSMENT_CLOSE',  'closeassessment');
38 ////////////////////////////////////////////////////////////////////////////////
39 // Moodle core API                                                            //
40 ////////////////////////////////////////////////////////////////////////////////
42 /**
43  * Returns the information if the module supports a feature
44  *
45  * @see plugin_supports() in lib/moodlelib.php
46  * @param string $feature FEATURE_xx constant for requested feature
47  * @return mixed true if the feature is supported, null if unknown
48  */
49 function workshop_supports($feature) {
50     switch($feature) {
51         case FEATURE_GRADE_HAS_GRADE:   return true;
52         case FEATURE_GROUPS:            return true;
53         case FEATURE_GROUPINGS:         return true;
54         case FEATURE_MOD_INTRO:         return true;
55         case FEATURE_BACKUP_MOODLE2:    return true;
56         case FEATURE_COMPLETION_TRACKS_VIEWS:
57             return true;
58         case FEATURE_SHOW_DESCRIPTION:  return true;
59         case FEATURE_PLAGIARISM:        return true;
60         default:                        return null;
61     }
62 }
64 /**
65  * Saves a new instance of the workshop into the database
66  *
67  * Given an object containing all the necessary data,
68  * (defined by the form in mod_form.php) this function
69  * will save a new instance and return the id number
70  * of the new instance.
71  *
72  * @param stdClass $workshop An object from the form in mod_form.php
73  * @return int The id of the newly inserted workshop record
74  */
75 function workshop_add_instance(stdclass $workshop) {
76     global $CFG, $DB;
77     require_once(__DIR__ . '/locallib.php');
79     $workshop->phase                 = workshop::PHASE_SETUP;
80     $workshop->timecreated           = time();
81     $workshop->timemodified          = $workshop->timecreated;
82     $workshop->useexamples           = (int)!empty($workshop->useexamples);
83     $workshop->usepeerassessment     = 1;
84     $workshop->useselfassessment     = (int)!empty($workshop->useselfassessment);
85     $workshop->latesubmissions       = (int)!empty($workshop->latesubmissions);
86     $workshop->phaseswitchassessment = (int)!empty($workshop->phaseswitchassessment);
87     $workshop->evaluation            = 'best';
89     if (isset($workshop->gradinggradepass)) {
90         $workshop->gradinggradepass = (float)unformat_float($workshop->gradinggradepass);
91     }
93     if (isset($workshop->submissiongradepass)) {
94         $workshop->submissiongradepass = (float)unformat_float($workshop->submissiongradepass);
95     }
97     if (isset($workshop->submissionfiletypes)) {
98         $workshop->submissionfiletypes = workshop::clean_file_extensions($workshop->submissionfiletypes);
99     }
101     if (isset($workshop->overallfeedbackfiletypes)) {
102         $workshop->overallfeedbackfiletypes = workshop::clean_file_extensions($workshop->overallfeedbackfiletypes);
103     }
105     // insert the new record so we get the id
106     $workshop->id = $DB->insert_record('workshop', $workshop);
108     // we need to use context now, so we need to make sure all needed info is already in db
109     $cmid = $workshop->coursemodule;
110     $DB->set_field('course_modules', 'instance', $workshop->id, array('id' => $cmid));
111     $context = context_module::instance($cmid);
113     // process the custom wysiwyg editors
114     if ($draftitemid = $workshop->instructauthorseditor['itemid']) {
115         $workshop->instructauthors = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructauthors',
116                 0, workshop::instruction_editors_options($context), $workshop->instructauthorseditor['text']);
117         $workshop->instructauthorsformat = $workshop->instructauthorseditor['format'];
118     }
120     if ($draftitemid = $workshop->instructreviewerseditor['itemid']) {
121         $workshop->instructreviewers = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructreviewers',
122                 0, workshop::instruction_editors_options($context), $workshop->instructreviewerseditor['text']);
123         $workshop->instructreviewersformat = $workshop->instructreviewerseditor['format'];
124     }
126     if ($draftitemid = $workshop->conclusioneditor['itemid']) {
127         $workshop->conclusion = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'conclusion',
128                 0, workshop::instruction_editors_options($context), $workshop->conclusioneditor['text']);
129         $workshop->conclusionformat = $workshop->conclusioneditor['format'];
130     }
132     // re-save the record with the replaced URLs in editor fields
133     $DB->update_record('workshop', $workshop);
135     // create gradebook items
136     workshop_grade_item_update($workshop);
137     workshop_grade_item_category_update($workshop);
139     // create calendar events
140     workshop_calendar_update($workshop, $workshop->coursemodule);
142     return $workshop->id;
145 /**
146  * Given an object containing all the necessary data,
147  * (defined by the form in mod_form.php) this function
148  * will update an existing instance with new data.
149  *
150  * @param stdClass $workshop An object from the form in mod_form.php
151  * @return bool success
152  */
153 function workshop_update_instance(stdclass $workshop) {
154     global $CFG, $DB;
155     require_once(__DIR__ . '/locallib.php');
157     $workshop->timemodified          = time();
158     $workshop->id                    = $workshop->instance;
159     $workshop->useexamples           = (int)!empty($workshop->useexamples);
160     $workshop->usepeerassessment     = 1;
161     $workshop->useselfassessment     = (int)!empty($workshop->useselfassessment);
162     $workshop->latesubmissions       = (int)!empty($workshop->latesubmissions);
163     $workshop->phaseswitchassessment = (int)!empty($workshop->phaseswitchassessment);
165     if (isset($workshop->gradinggradepass)) {
166         $workshop->gradinggradepass = (float)unformat_float($workshop->gradinggradepass);
167     }
169     if (isset($workshop->submissiongradepass)) {
170         $workshop->submissiongradepass = (float)unformat_float($workshop->submissiongradepass);
171     }
173     if (isset($workshop->submissionfiletypes)) {
174         $workshop->submissionfiletypes = workshop::clean_file_extensions($workshop->submissionfiletypes);
175     }
177     if (isset($workshop->overallfeedbackfiletypes)) {
178         $workshop->overallfeedbackfiletypes = workshop::clean_file_extensions($workshop->overallfeedbackfiletypes);
179     }
181     // todo - if the grading strategy is being changed, we may want to replace all aggregated peer grades with nulls
183     $DB->update_record('workshop', $workshop);
184     $context = context_module::instance($workshop->coursemodule);
186     // process the custom wysiwyg editors
187     if ($draftitemid = $workshop->instructauthorseditor['itemid']) {
188         $workshop->instructauthors = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructauthors',
189                 0, workshop::instruction_editors_options($context), $workshop->instructauthorseditor['text']);
190         $workshop->instructauthorsformat = $workshop->instructauthorseditor['format'];
191     }
193     if ($draftitemid = $workshop->instructreviewerseditor['itemid']) {
194         $workshop->instructreviewers = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructreviewers',
195                 0, workshop::instruction_editors_options($context), $workshop->instructreviewerseditor['text']);
196         $workshop->instructreviewersformat = $workshop->instructreviewerseditor['format'];
197     }
199     if ($draftitemid = $workshop->conclusioneditor['itemid']) {
200         $workshop->conclusion = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'conclusion',
201                 0, workshop::instruction_editors_options($context), $workshop->conclusioneditor['text']);
202         $workshop->conclusionformat = $workshop->conclusioneditor['format'];
203     }
205     // re-save the record with the replaced URLs in editor fields
206     $DB->update_record('workshop', $workshop);
208     // update gradebook items
209     workshop_grade_item_update($workshop);
210     workshop_grade_item_category_update($workshop);
212     // update calendar events
213     workshop_calendar_update($workshop, $workshop->coursemodule);
215     return true;
218 /**
219  * Given an ID of an instance of this module,
220  * this function will permanently delete the instance
221  * and any data that depends on it.
222  *
223  * @param int $id Id of the module instance
224  * @return boolean Success/Failure
225  */
226 function workshop_delete_instance($id) {
227     global $CFG, $DB;
228     require_once($CFG->libdir.'/gradelib.php');
230     if (! $workshop = $DB->get_record('workshop', array('id' => $id))) {
231         return false;
232     }
234     // delete all associated aggregations
235     $DB->delete_records('workshop_aggregations', array('workshopid' => $workshop->id));
237     // get the list of ids of all submissions
238     $submissions = $DB->get_records('workshop_submissions', array('workshopid' => $workshop->id), '', 'id');
240     // get the list of all allocated assessments
241     $assessments = $DB->get_records_list('workshop_assessments', 'submissionid', array_keys($submissions), '', 'id');
243     // delete the associated records from the workshop core tables
244     $DB->delete_records_list('workshop_grades', 'assessmentid', array_keys($assessments));
245     $DB->delete_records_list('workshop_assessments', 'id', array_keys($assessments));
246     $DB->delete_records_list('workshop_submissions', 'id', array_keys($submissions));
248     // call the static clean-up methods of all available subplugins
249     $strategies = core_component::get_plugin_list('workshopform');
250     foreach ($strategies as $strategy => $path) {
251         require_once($path.'/lib.php');
252         $classname = 'workshop_'.$strategy.'_strategy';
253         call_user_func($classname.'::delete_instance', $workshop->id);
254     }
256     $allocators = core_component::get_plugin_list('workshopallocation');
257     foreach ($allocators as $allocator => $path) {
258         require_once($path.'/lib.php');
259         $classname = 'workshop_'.$allocator.'_allocator';
260         call_user_func($classname.'::delete_instance', $workshop->id);
261     }
263     $evaluators = core_component::get_plugin_list('workshopeval');
264     foreach ($evaluators as $evaluator => $path) {
265         require_once($path.'/lib.php');
266         $classname = 'workshop_'.$evaluator.'_evaluation';
267         call_user_func($classname.'::delete_instance', $workshop->id);
268     }
270     // delete the calendar events
271     $events = $DB->get_records('event', array('modulename' => 'workshop', 'instance' => $workshop->id));
272     foreach ($events as $event) {
273         $event = calendar_event::load($event);
274         $event->delete();
275     }
277     // finally remove the workshop record itself
278     $DB->delete_records('workshop', array('id' => $workshop->id));
280     // gradebook cleanup
281     grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 0, null, array('deleted' => true));
282     grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 1, null, array('deleted' => true));
284     return true;
287 /**
288  * This standard function will check all instances of this module
289  * and make sure there are up-to-date events created for each of them.
290  * If courseid = 0, then every workshop event in the site is checked, else
291  * only workshop events belonging to the course specified are checked.
292  *
293  * @param  integer $courseid The Course ID.
294  * @return bool Returns true if the calendar events were successfully updated.
295  */
296 function workshop_refresh_events($courseid = 0) {
297     global $DB;
299     if ($courseid) {
300         // Make sure that the course id is numeric.
301         if (!is_numeric($courseid)) {
302             return false;
303         }
304         if (!$workshops = $DB->get_records('workshop', array('course' => $courseid))) {
305             return false;
306         }
307     } else {
308         if (!$workshops = $DB->get_records('workshop')) {
309             return false;
310         }
311     }
312     foreach ($workshops as $workshop) {
313         if (!$cm = get_coursemodule_from_instance('workshop', $workshop->id, $courseid, false)) {
314             continue;
315         }
316         workshop_calendar_update($workshop, $cm->id);
317     }
318     return true;
321 /**
322  * List the actions that correspond to a view of this module.
323  * This is used by the participation report.
324  *
325  * Note: This is not used by new logging system. Event with
326  *       crud = 'r' and edulevel = LEVEL_PARTICIPATING will
327  *       be considered as view action.
328  *
329  * @return array
330  */
331 function workshop_get_view_actions() {
332     return array('view', 'view all', 'view submission', 'view example');
335 /**
336  * List the actions that correspond to a post of this module.
337  * This is used by the participation report.
338  *
339  * Note: This is not used by new logging system. Event with
340  *       crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING
341  *       will be considered as post action.
342  *
343  * @return array
344  */
345 function workshop_get_post_actions() {
346     return array('add', 'add assessment', 'add example', 'add submission',
347                  'update', 'update assessment', 'update example', 'update submission');
350 /**
351  * Return a small object with summary information about what a
352  * user has done with a given particular instance of this module
353  * Used for user activity reports.
354  * $return->time = the time they did it
355  * $return->info = a short text description
356  *
357  * @param stdClass $course The course record.
358  * @param stdClass $user The user record.
359  * @param cm_info|stdClass $mod The course module info object or record.
360  * @param stdClass $workshop The workshop instance record.
361  * @return stdclass|null
362  */
363 function workshop_user_outline($course, $user, $mod, $workshop) {
364     global $CFG, $DB;
365     require_once($CFG->libdir.'/gradelib.php');
367     $grades = grade_get_grades($course->id, 'mod', 'workshop', $workshop->id, $user->id);
369     $submissiongrade = null;
370     $assessmentgrade = null;
372     $info = '';
373     $time = 0;
375     if (!empty($grades->items[0]->grades)) {
376         $submissiongrade = reset($grades->items[0]->grades);
377         $info .= get_string('submissiongrade', 'workshop') . ': ' . $submissiongrade->str_long_grade . html_writer::empty_tag('br');
378         $time = max($time, $submissiongrade->dategraded);
379     }
380     if (!empty($grades->items[1]->grades)) {
381         $assessmentgrade = reset($grades->items[1]->grades);
382         $info .= get_string('gradinggrade', 'workshop') . ': ' . $assessmentgrade->str_long_grade;
383         $time = max($time, $assessmentgrade->dategraded);
384     }
386     if (!empty($info) and !empty($time)) {
387         $return = new stdclass();
388         $return->time = $time;
389         $return->info = $info;
390         return $return;
391     }
393     return null;
396 /**
397  * Print a detailed representation of what a user has done with
398  * a given particular instance of this module, for user activity reports.
399  *
400  * @param stdClass $course The course record.
401  * @param stdClass $user The user record.
402  * @param cm_info|stdClass $mod The course module info object or record.
403  * @param stdClass $workshop The workshop instance record.
404  * @return string HTML
405  */
406 function workshop_user_complete($course, $user, $mod, $workshop) {
407     global $CFG, $DB, $OUTPUT;
408     require_once(__DIR__.'/locallib.php');
409     require_once($CFG->libdir.'/gradelib.php');
411     $workshop   = new workshop($workshop, $mod, $course);
412     $grades     = grade_get_grades($course->id, 'mod', 'workshop', $workshop->id, $user->id);
414     if (!empty($grades->items[0]->grades)) {
415         $submissiongrade = reset($grades->items[0]->grades);
416         $info = get_string('submissiongrade', 'workshop') . ': ' . $submissiongrade->str_long_grade;
417         echo html_writer::tag('li', $info, array('class'=>'submissiongrade'));
418     }
419     if (!empty($grades->items[1]->grades)) {
420         $assessmentgrade = reset($grades->items[1]->grades);
421         $info = get_string('gradinggrade', 'workshop') . ': ' . $assessmentgrade->str_long_grade;
422         echo html_writer::tag('li', $info, array('class'=>'gradinggrade'));
423     }
425     if (has_capability('mod/workshop:viewallsubmissions', $workshop->context)) {
426         $canviewsubmission = true;
427         if (groups_get_activity_groupmode($workshop->cm) == SEPARATEGROUPS) {
428             // user must have accessallgroups or share at least one group with the submission author
429             if (!has_capability('moodle/site:accessallgroups', $workshop->context)) {
430                 $usersgroups = groups_get_activity_allowed_groups($workshop->cm);
431                 $authorsgroups = groups_get_all_groups($workshop->course->id, $user->id, $workshop->cm->groupingid, 'g.id');
432                 $sharedgroups = array_intersect_key($usersgroups, $authorsgroups);
433                 if (empty($sharedgroups)) {
434                     $canviewsubmission = false;
435                 }
436             }
437         }
438         if ($canviewsubmission and $submission = $workshop->get_submission_by_author($user->id)) {
439             $title      = format_string($submission->title);
440             $url        = $workshop->submission_url($submission->id);
441             $link       = html_writer::link($url, $title);
442             $info       = get_string('submission', 'workshop').': '.$link;
443             echo html_writer::tag('li', $info, array('class'=>'submission'));
444         }
445     }
447     if (has_capability('mod/workshop:viewallassessments', $workshop->context)) {
448         if ($assessments = $workshop->get_assessments_by_reviewer($user->id)) {
449             foreach ($assessments as $assessment) {
450                 $a = new stdclass();
451                 $a->submissionurl = $workshop->submission_url($assessment->submissionid)->out();
452                 $a->assessmenturl = $workshop->assess_url($assessment->id)->out();
453                 $a->submissiontitle = s($assessment->submissiontitle);
454                 echo html_writer::tag('li', get_string('assessmentofsubmission', 'workshop', $a));
455             }
456         }
457     }
460 /**
461  * Given a course and a time, this module should find recent activity
462  * that has occurred in workshop activities and print it out.
463  * Return true if there was output, or false is there was none.
464  *
465  * @param stdClass $course
466  * @param bool $viewfullnames
467  * @param int $timestart
468  * @return boolean
469  */
470 function workshop_print_recent_activity($course, $viewfullnames, $timestart) {
471     global $CFG, $USER, $DB, $OUTPUT;
473     $authoramefields = get_all_user_name_fields(true, 'author', null, 'author');
474     $reviewerfields = get_all_user_name_fields(true, 'reviewer', null, 'reviewer');
476     $sql = "SELECT s.id AS submissionid, s.title AS submissiontitle, s.timemodified AS submissionmodified,
477                    author.id AS authorid, $authoramefields, a.id AS assessmentid, a.timemodified AS assessmentmodified,
478                    reviewer.id AS reviewerid, $reviewerfields, cm.id AS cmid
479               FROM {workshop} w
480         INNER JOIN {course_modules} cm ON cm.instance = w.id
481         INNER JOIN {modules} md ON md.id = cm.module
482         INNER JOIN {workshop_submissions} s ON s.workshopid = w.id
483         INNER JOIN {user} author ON s.authorid = author.id
484          LEFT JOIN {workshop_assessments} a ON a.submissionid = s.id
485          LEFT JOIN {user} reviewer ON a.reviewerid = reviewer.id
486              WHERE cm.course = ?
487                    AND md.name = 'workshop'
488                    AND s.example = 0
489                    AND (s.timemodified > ? OR a.timemodified > ?)
490           ORDER BY s.timemodified";
492     $rs = $DB->get_recordset_sql($sql, array($course->id, $timestart, $timestart));
494     $modinfo = get_fast_modinfo($course); // reference needed because we might load the groups
496     $submissions = array(); // recent submissions indexed by submission id
497     $assessments = array(); // recent assessments indexed by assessment id
498     $users       = array();
500     foreach ($rs as $activity) {
501         if (!array_key_exists($activity->cmid, $modinfo->cms)) {
502             // this should not happen but just in case
503             continue;
504         }
506         $cm = $modinfo->cms[$activity->cmid];
507         if (!$cm->uservisible) {
508             continue;
509         }
511         // remember all user names we can use later
512         if (empty($users[$activity->authorid])) {
513             $u = new stdclass();
514             $users[$activity->authorid] = username_load_fields_from_object($u, $activity, 'author');
515         }
516         if ($activity->reviewerid and empty($users[$activity->reviewerid])) {
517             $u = new stdclass();
518             $users[$activity->reviewerid] = username_load_fields_from_object($u, $activity, 'reviewer');
519         }
521         $context = context_module::instance($cm->id);
522         $groupmode = groups_get_activity_groupmode($cm, $course);
524         if ($activity->submissionmodified > $timestart and empty($submissions[$activity->submissionid])) {
525             $s = new stdclass();
526             $s->title = $activity->submissiontitle;
527             $s->authorid = $activity->authorid;
528             $s->timemodified = $activity->submissionmodified;
529             $s->cmid = $activity->cmid;
530             if ($activity->authorid == $USER->id || has_capability('mod/workshop:viewauthornames', $context)) {
531                 $s->authornamevisible = true;
532             } else {
533                 $s->authornamevisible = false;
534             }
536             // the following do-while wrapper allows to break from deeply nested if-statements
537             do {
538                 if ($s->authorid === $USER->id) {
539                     // own submissions always visible
540                     $submissions[$activity->submissionid] = $s;
541                     break;
542                 }
544                 if (has_capability('mod/workshop:viewallsubmissions', $context)) {
545                     if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
546                         if (isguestuser()) {
547                             // shortcut - guest user does not belong into any group
548                             break;
549                         }
551                         // this might be slow - show only submissions by users who share group with me in this cm
552                         if (!$modinfo->get_groups($cm->groupingid)) {
553                             break;
554                         }
555                         $authorsgroups = groups_get_all_groups($course->id, $s->authorid, $cm->groupingid);
556                         if (is_array($authorsgroups)) {
557                             $authorsgroups = array_keys($authorsgroups);
558                             $intersect = array_intersect($authorsgroups, $modinfo->get_groups($cm->groupingid));
559                             if (empty($intersect)) {
560                                 break;
561                             } else {
562                                 // can see all submissions and shares a group with the author
563                                 $submissions[$activity->submissionid] = $s;
564                                 break;
565                             }
566                         }
568                     } else {
569                         // can see all submissions from all groups
570                         $submissions[$activity->submissionid] = $s;
571                     }
572                 }
573             } while (0);
574         }
576         if ($activity->assessmentmodified > $timestart and empty($assessments[$activity->assessmentid])) {
577             $a = new stdclass();
578             $a->submissionid = $activity->submissionid;
579             $a->submissiontitle = $activity->submissiontitle;
580             $a->reviewerid = $activity->reviewerid;
581             $a->timemodified = $activity->assessmentmodified;
582             $a->cmid = $activity->cmid;
583             if ($activity->reviewerid == $USER->id || has_capability('mod/workshop:viewreviewernames', $context)) {
584                 $a->reviewernamevisible = true;
585             } else {
586                 $a->reviewernamevisible = false;
587             }
589             // the following do-while wrapper allows to break from deeply nested if-statements
590             do {
591                 if ($a->reviewerid === $USER->id) {
592                     // own assessments always visible
593                     $assessments[$activity->assessmentid] = $a;
594                     break;
595                 }
597                 if (has_capability('mod/workshop:viewallassessments', $context)) {
598                     if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
599                         if (isguestuser()) {
600                             // shortcut - guest user does not belong into any group
601                             break;
602                         }
604                         // this might be slow - show only submissions by users who share group with me in this cm
605                         if (!$modinfo->get_groups($cm->groupingid)) {
606                             break;
607                         }
608                         $reviewersgroups = groups_get_all_groups($course->id, $a->reviewerid, $cm->groupingid);
609                         if (is_array($reviewersgroups)) {
610                             $reviewersgroups = array_keys($reviewersgroups);
611                             $intersect = array_intersect($reviewersgroups, $modinfo->get_groups($cm->groupingid));
612                             if (empty($intersect)) {
613                                 break;
614                             } else {
615                                 // can see all assessments and shares a group with the reviewer
616                                 $assessments[$activity->assessmentid] = $a;
617                                 break;
618                             }
619                         }
621                     } else {
622                         // can see all assessments from all groups
623                         $assessments[$activity->assessmentid] = $a;
624                     }
625                 }
626             } while (0);
627         }
628     }
629     $rs->close();
631     $shown = false;
633     if (!empty($submissions)) {
634         $shown = true;
635         echo $OUTPUT->heading(get_string('recentsubmissions', 'workshop'), 3);
636         foreach ($submissions as $id => $submission) {
637             $link = new moodle_url('/mod/workshop/submission.php', array('id'=>$id, 'cmid'=>$submission->cmid));
638             if ($submission->authornamevisible) {
639                 $author = $users[$submission->authorid];
640             } else {
641                 $author = null;
642             }
643             print_recent_activity_note($submission->timemodified, $author, $submission->title, $link->out(), false, $viewfullnames);
644         }
645     }
647     if (!empty($assessments)) {
648         $shown = true;
649         echo $OUTPUT->heading(get_string('recentassessments', 'workshop'), 3);
650         core_collator::asort_objects_by_property($assessments, 'timemodified');
651         foreach ($assessments as $id => $assessment) {
652             $link = new moodle_url('/mod/workshop/assessment.php', array('asid' => $id));
653             if ($assessment->reviewernamevisible) {
654                 $reviewer = $users[$assessment->reviewerid];
655             } else {
656                 $reviewer = null;
657             }
658             print_recent_activity_note($assessment->timemodified, $reviewer, $assessment->submissiontitle, $link->out(), false, $viewfullnames);
659         }
660     }
662     if ($shown) {
663         return true;
664     }
666     return false;
669 /**
670  * Returns all activity in course workshops since a given time
671  *
672  * @param array $activities sequentially indexed array of objects
673  * @param int $index
674  * @param int $timestart
675  * @param int $courseid
676  * @param int $cmid
677  * @param int $userid defaults to 0
678  * @param int $groupid defaults to 0
679  * @return void adds items into $activities and increases $index
680  */
681 function workshop_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid=0, $groupid=0) {
682     global $CFG, $COURSE, $USER, $DB;
684     if ($COURSE->id == $courseid) {
685         $course = $COURSE;
686     } else {
687         $course = $DB->get_record('course', array('id'=>$courseid));
688     }
690     $modinfo = get_fast_modinfo($course);
692     $cm = $modinfo->cms[$cmid];
694     $params = array();
695     if ($userid) {
696         $userselect = "AND (author.id = :authorid OR reviewer.id = :reviewerid)";
697         $params['authorid'] = $userid;
698         $params['reviewerid'] = $userid;
699     } else {
700         $userselect = "";
701     }
703     if ($groupid) {
704         $groupselect = "AND (authorgroupmembership.groupid = :authorgroupid OR reviewergroupmembership.groupid = :reviewergroupid)";
705         $groupjoin   = "LEFT JOIN {groups_members} authorgroupmembership ON authorgroupmembership.userid = author.id
706                         LEFT JOIN {groups_members} reviewergroupmembership ON reviewergroupmembership.userid = reviewer.id";
707         $params['authorgroupid'] = $groupid;
708         $params['reviewergroupid'] = $groupid;
709     } else {
710         $groupselect = "";
711         $groupjoin   = "";
712     }
714     $params['cminstance'] = $cm->instance;
715     $params['submissionmodified'] = $timestart;
716     $params['assessmentmodified'] = $timestart;
718     $authornamefields = get_all_user_name_fields(true, 'author', null, 'author');
719     $reviewerfields = get_all_user_name_fields(true, 'reviewer', null, 'reviewer');
721     $sql = "SELECT s.id AS submissionid, s.title AS submissiontitle, s.timemodified AS submissionmodified,
722                    author.id AS authorid, $authornamefields, author.picture AS authorpicture, author.imagealt AS authorimagealt,
723                    author.email AS authoremail, a.id AS assessmentid, a.timemodified AS assessmentmodified,
724                    reviewer.id AS reviewerid, $reviewerfields, reviewer.picture AS reviewerpicture,
725                    reviewer.imagealt AS reviewerimagealt, reviewer.email AS revieweremail
726               FROM {workshop_submissions} s
727         INNER JOIN {workshop} w ON s.workshopid = w.id
728         INNER JOIN {user} author ON s.authorid = author.id
729          LEFT JOIN {workshop_assessments} a ON a.submissionid = s.id
730          LEFT JOIN {user} reviewer ON a.reviewerid = reviewer.id
731         $groupjoin
732              WHERE w.id = :cminstance
733                    AND s.example = 0
734                    $userselect $groupselect
735                    AND (s.timemodified > :submissionmodified OR a.timemodified > :assessmentmodified)
736           ORDER BY s.timemodified ASC, a.timemodified ASC";
738     $rs = $DB->get_recordset_sql($sql, $params);
740     $groupmode       = groups_get_activity_groupmode($cm, $course);
741     $context         = context_module::instance($cm->id);
742     $grader          = has_capability('moodle/grade:viewall', $context);
743     $accessallgroups = has_capability('moodle/site:accessallgroups', $context);
744     $viewauthors     = has_capability('mod/workshop:viewauthornames', $context);
745     $viewreviewers   = has_capability('mod/workshop:viewreviewernames', $context);
747     $submissions = array(); // recent submissions indexed by submission id
748     $assessments = array(); // recent assessments indexed by assessment id
749     $users       = array();
751     foreach ($rs as $activity) {
753         // remember all user names we can use later
754         if (empty($users[$activity->authorid])) {
755             $u = new stdclass();
756             $additionalfields = explode(',', user_picture::fields());
757             $u = username_load_fields_from_object($u, $activity, 'author', $additionalfields);
758             $users[$activity->authorid] = $u;
759         }
760         if ($activity->reviewerid and empty($users[$activity->reviewerid])) {
761             $u = new stdclass();
762             $additionalfields = explode(',', user_picture::fields());
763             $u = username_load_fields_from_object($u, $activity, 'reviewer', $additionalfields);
764             $users[$activity->reviewerid] = $u;
765         }
767         if ($activity->submissionmodified > $timestart and empty($submissions[$activity->submissionid])) {
768             $s = new stdclass();
769             $s->id = $activity->submissionid;
770             $s->title = $activity->submissiontitle;
771             $s->authorid = $activity->authorid;
772             $s->timemodified = $activity->submissionmodified;
773             if ($activity->authorid == $USER->id || has_capability('mod/workshop:viewauthornames', $context)) {
774                 $s->authornamevisible = true;
775             } else {
776                 $s->authornamevisible = false;
777             }
779             // the following do-while wrapper allows to break from deeply nested if-statements
780             do {
781                 if ($s->authorid === $USER->id) {
782                     // own submissions always visible
783                     $submissions[$activity->submissionid] = $s;
784                     break;
785                 }
787                 if (has_capability('mod/workshop:viewallsubmissions', $context)) {
788                     if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
789                         if (isguestuser()) {
790                             // shortcut - guest user does not belong into any group
791                             break;
792                         }
794                         // this might be slow - show only submissions by users who share group with me in this cm
795                         if (!$modinfo->get_groups($cm->groupingid)) {
796                             break;
797                         }
798                         $authorsgroups = groups_get_all_groups($course->id, $s->authorid, $cm->groupingid);
799                         if (is_array($authorsgroups)) {
800                             $authorsgroups = array_keys($authorsgroups);
801                             $intersect = array_intersect($authorsgroups, $modinfo->get_groups($cm->groupingid));
802                             if (empty($intersect)) {
803                                 break;
804                             } else {
805                                 // can see all submissions and shares a group with the author
806                                 $submissions[$activity->submissionid] = $s;
807                                 break;
808                             }
809                         }
811                     } else {
812                         // can see all submissions from all groups
813                         $submissions[$activity->submissionid] = $s;
814                     }
815                 }
816             } while (0);
817         }
819         if ($activity->assessmentmodified > $timestart and empty($assessments[$activity->assessmentid])) {
820             $a = new stdclass();
821             $a->id = $activity->assessmentid;
822             $a->submissionid = $activity->submissionid;
823             $a->submissiontitle = $activity->submissiontitle;
824             $a->reviewerid = $activity->reviewerid;
825             $a->timemodified = $activity->assessmentmodified;
826             if ($activity->reviewerid == $USER->id || has_capability('mod/workshop:viewreviewernames', $context)) {
827                 $a->reviewernamevisible = true;
828             } else {
829                 $a->reviewernamevisible = false;
830             }
832             // the following do-while wrapper allows to break from deeply nested if-statements
833             do {
834                 if ($a->reviewerid === $USER->id) {
835                     // own assessments always visible
836                     $assessments[$activity->assessmentid] = $a;
837                     break;
838                 }
840                 if (has_capability('mod/workshop:viewallassessments', $context)) {
841                     if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
842                         if (isguestuser()) {
843                             // shortcut - guest user does not belong into any group
844                             break;
845                         }
847                         // this might be slow - show only submissions by users who share group with me in this cm
848                         if (!$modinfo->get_groups($cm->groupingid)) {
849                             break;
850                         }
851                         $reviewersgroups = groups_get_all_groups($course->id, $a->reviewerid, $cm->groupingid);
852                         if (is_array($reviewersgroups)) {
853                             $reviewersgroups = array_keys($reviewersgroups);
854                             $intersect = array_intersect($reviewersgroups, $modinfo->get_groups($cm->groupingid));
855                             if (empty($intersect)) {
856                                 break;
857                             } else {
858                                 // can see all assessments and shares a group with the reviewer
859                                 $assessments[$activity->assessmentid] = $a;
860                                 break;
861                             }
862                         }
864                     } else {
865                         // can see all assessments from all groups
866                         $assessments[$activity->assessmentid] = $a;
867                     }
868                 }
869             } while (0);
870         }
871     }
872     $rs->close();
874     $workshopname = format_string($cm->name, true);
876     if ($grader) {
877         require_once($CFG->libdir.'/gradelib.php');
878         $grades = grade_get_grades($courseid, 'mod', 'workshop', $cm->instance, array_keys($users));
879     }
881     foreach ($submissions as $submission) {
882         $tmpactivity                = new stdclass();
883         $tmpactivity->type          = 'workshop';
884         $tmpactivity->cmid          = $cm->id;
885         $tmpactivity->name          = $workshopname;
886         $tmpactivity->sectionnum    = $cm->sectionnum;
887         $tmpactivity->timestamp     = $submission->timemodified;
888         $tmpactivity->subtype       = 'submission';
889         $tmpactivity->content       = $submission;
890         if ($grader) {
891             $tmpactivity->grade     = $grades->items[0]->grades[$submission->authorid]->str_long_grade;
892         }
893         if ($submission->authornamevisible and !empty($users[$submission->authorid])) {
894             $tmpactivity->user      = $users[$submission->authorid];
895         }
896         $activities[$index++]       = $tmpactivity;
897     }
899     foreach ($assessments as $assessment) {
900         $tmpactivity                = new stdclass();
901         $tmpactivity->type          = 'workshop';
902         $tmpactivity->cmid          = $cm->id;
903         $tmpactivity->name          = $workshopname;
904         $tmpactivity->sectionnum    = $cm->sectionnum;
905         $tmpactivity->timestamp     = $assessment->timemodified;
906         $tmpactivity->subtype       = 'assessment';
907         $tmpactivity->content       = $assessment;
908         if ($grader) {
909             $tmpactivity->grade     = $grades->items[1]->grades[$assessment->reviewerid]->str_long_grade;
910         }
911         if ($assessment->reviewernamevisible and !empty($users[$assessment->reviewerid])) {
912             $tmpactivity->user      = $users[$assessment->reviewerid];
913         }
914         $activities[$index++]       = $tmpactivity;
915     }
918 /**
919  * Print single activity item prepared by {@see workshop_get_recent_mod_activity()}
920  */
921 function workshop_print_recent_mod_activity($activity, $courseid, $detail, $modnames, $viewfullnames) {
922     global $CFG, $OUTPUT;
924     if (!empty($activity->user)) {
925         echo html_writer::tag('div', $OUTPUT->user_picture($activity->user, array('courseid'=>$courseid)),
926                 array('style' => 'float: left; padding: 7px;'));
927     }
929     if ($activity->subtype == 'submission') {
930         echo html_writer::start_tag('div', array('class'=>'submission', 'style'=>'padding: 7px; float:left;'));
932         if ($detail) {
933             echo html_writer::start_tag('h4', array('class'=>'workshop'));
934             $url = new moodle_url('/mod/workshop/view.php', array('id'=>$activity->cmid));
935             $name = s($activity->name);
936             echo $OUTPUT->image_icon('icon', $name, $activity->type);
937             echo ' ' . $modnames[$activity->type];
938             echo html_writer::link($url, $name, array('class'=>'name', 'style'=>'margin-left: 5px'));
939             echo html_writer::end_tag('h4');
940         }
942         echo html_writer::start_tag('div', array('class'=>'title'));
943         $url = new moodle_url('/mod/workshop/submission.php', array('cmid'=>$activity->cmid, 'id'=>$activity->content->id));
944         $name = s($activity->content->title);
945         echo html_writer::tag('strong', html_writer::link($url, $name));
946         echo html_writer::end_tag('div');
948         if (!empty($activity->user)) {
949             echo html_writer::start_tag('div', array('class'=>'user'));
950             $url = new moodle_url('/user/view.php', array('id'=>$activity->user->id, 'course'=>$courseid));
951             $name = fullname($activity->user);
952             $link = html_writer::link($url, $name);
953             echo get_string('submissionby', 'workshop', $link);
954             echo ' - '.userdate($activity->timestamp);
955             echo html_writer::end_tag('div');
956         } else {
957             echo html_writer::start_tag('div', array('class'=>'anonymous'));
958             echo get_string('submission', 'workshop');
959             echo ' - '.userdate($activity->timestamp);
960             echo html_writer::end_tag('div');
961         }
963         echo html_writer::end_tag('div');
964     }
966     if ($activity->subtype == 'assessment') {
967         echo html_writer::start_tag('div', array('class'=>'assessment', 'style'=>'padding: 7px; float:left;'));
969         if ($detail) {
970             echo html_writer::start_tag('h4', array('class'=>'workshop'));
971             $url = new moodle_url('/mod/workshop/view.php', array('id'=>$activity->cmid));
972             $name = s($activity->name);
973             echo $OUTPUT->image_icon('icon', $name, $activity->type);
974             echo ' ' . $modnames[$activity->type];
975             echo html_writer::link($url, $name, array('class'=>'name', 'style'=>'margin-left: 5px'));
976             echo html_writer::end_tag('h4');
977         }
979         echo html_writer::start_tag('div', array('class'=>'title'));
980         $url = new moodle_url('/mod/workshop/assessment.php', array('asid'=>$activity->content->id));
981         $name = s($activity->content->submissiontitle);
982         echo html_writer::tag('em', html_writer::link($url, $name));
983         echo html_writer::end_tag('div');
985         if (!empty($activity->user)) {
986             echo html_writer::start_tag('div', array('class'=>'user'));
987             $url = new moodle_url('/user/view.php', array('id'=>$activity->user->id, 'course'=>$courseid));
988             $name = fullname($activity->user);
989             $link = html_writer::link($url, $name);
990             echo get_string('assessmentbyfullname', 'workshop', $link);
991             echo ' - '.userdate($activity->timestamp);
992             echo html_writer::end_tag('div');
993         } else {
994             echo html_writer::start_tag('div', array('class'=>'anonymous'));
995             echo get_string('assessment', 'workshop');
996             echo ' - '.userdate($activity->timestamp);
997             echo html_writer::end_tag('div');
998         }
1000         echo html_writer::end_tag('div');
1001     }
1003     echo html_writer::empty_tag('br', array('style'=>'clear:both'));
1006 /**
1007  * Regular jobs to execute via cron
1008  *
1009  * @return boolean true on success, false otherwise
1010  */
1011 function workshop_cron() {
1012     global $CFG, $DB;
1014     $now = time();
1016     mtrace(' processing workshop subplugins ...');
1017     cron_execute_plugin_type('workshopallocation', 'workshop allocation methods');
1019     // now when the scheduled allocator had a chance to do its job, check if there
1020     // are some workshops to switch into the assessment phase
1021     $workshops = $DB->get_records_select("workshop",
1022         "phase = 20 AND phaseswitchassessment = 1 AND submissionend > 0 AND submissionend < ?", array($now));
1024     if (!empty($workshops)) {
1025         mtrace('Processing automatic assessment phase switch in '.count($workshops).' workshop(s) ... ', '');
1026         require_once($CFG->dirroot.'/mod/workshop/locallib.php');
1027         foreach ($workshops as $workshop) {
1028             $cm = get_coursemodule_from_instance('workshop', $workshop->id, $workshop->course, false, MUST_EXIST);
1029             $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
1030             $workshop = new workshop($workshop, $cm, $course);
1031             $workshop->switch_phase(workshop::PHASE_ASSESSMENT);
1033             $params = array(
1034                 'objectid' => $workshop->id,
1035                 'context' => $workshop->context,
1036                 'courseid' => $workshop->course->id,
1037                 'other' => array(
1038                     'workshopphase' => $workshop->phase
1039                 )
1040             );
1041             $event = \mod_workshop\event\phase_switched::create($params);
1042             $event->trigger();
1044             // disable the automatic switching now so that it is not executed again by accident
1045             // if the teacher changes the phase back to the submission one
1046             $DB->set_field('workshop', 'phaseswitchassessment', 0, array('id' => $workshop->id));
1048             // todo inform the teachers
1049         }
1050         mtrace('done');
1051     }
1053     return true;
1056 /**
1057  * Is a given scale used by the instance of workshop?
1058  *
1059  * The function asks all installed grading strategy subplugins. The workshop
1060  * core itself does not use scales. Both grade for submission and grade for
1061  * assessments do not use scales.
1062  *
1063  * @param int $workshopid id of workshop instance
1064  * @param int $scaleid id of the scale to check
1065  * @return bool
1066  */
1067 function workshop_scale_used($workshopid, $scaleid) {
1068     global $CFG; // other files included from here
1070     $strategies = core_component::get_plugin_list('workshopform');
1071     foreach ($strategies as $strategy => $strategypath) {
1072         $strategylib = $strategypath . '/lib.php';
1073         if (is_readable($strategylib)) {
1074             require_once($strategylib);
1075         } else {
1076             throw new coding_exception('the grading forms subplugin must contain library ' . $strategylib);
1077         }
1078         $classname = 'workshop_' . $strategy . '_strategy';
1079         if (method_exists($classname, 'scale_used')) {
1080             if (call_user_func_array(array($classname, 'scale_used'), array($scaleid, $workshopid))) {
1081                 // no need to include any other files - scale is used
1082                 return true;
1083             }
1084         }
1085     }
1087     return false;
1090 /**
1091  * Is a given scale used by any instance of workshop?
1092  *
1093  * The function asks all installed grading strategy subplugins. The workshop
1094  * core itself does not use scales. Both grade for submission and grade for
1095  * assessments do not use scales.
1096  *
1097  * @param int $scaleid id of the scale to check
1098  * @return bool
1099  */
1100 function workshop_scale_used_anywhere($scaleid) {
1101     global $CFG; // other files included from here
1103     $strategies = core_component::get_plugin_list('workshopform');
1104     foreach ($strategies as $strategy => $strategypath) {
1105         $strategylib = $strategypath . '/lib.php';
1106         if (is_readable($strategylib)) {
1107             require_once($strategylib);
1108         } else {
1109             throw new coding_exception('the grading forms subplugin must contain library ' . $strategylib);
1110         }
1111         $classname = 'workshop_' . $strategy . '_strategy';
1112         if (method_exists($classname, 'scale_used')) {
1113             if (call_user_func(array($classname, 'scale_used'), $scaleid)) {
1114                 // no need to include any other files - scale is used
1115                 return true;
1116             }
1117         }
1118     }
1120     return false;
1123 /**
1124  * Returns all other caps used in the module
1125  *
1126  * @return array
1127  */
1128 function workshop_get_extra_capabilities() {
1129     return array('moodle/site:accessallgroups');
1132 ////////////////////////////////////////////////////////////////////////////////
1133 // Gradebook API                                                              //
1134 ////////////////////////////////////////////////////////////////////////////////
1136 /**
1137  * Creates or updates grade items for the give workshop instance
1138  *
1139  * Needed by grade_update_mod_grades() in lib/gradelib.php. Also used by
1140  * {@link workshop_update_grades()}.
1141  *
1142  * @param stdClass $workshop instance object with extra cmidnumber property
1143  * @param stdClass $submissiongrades data for the first grade item
1144  * @param stdClass $assessmentgrades data for the second grade item
1145  * @return void
1146  */
1147 function workshop_grade_item_update(stdclass $workshop, $submissiongrades=null, $assessmentgrades=null) {
1148     global $CFG;
1149     require_once($CFG->libdir.'/gradelib.php');
1151     $a = new stdclass();
1152     $a->workshopname = clean_param($workshop->name, PARAM_NOTAGS);
1154     $item = array();
1155     $item['itemname'] = get_string('gradeitemsubmission', 'workshop', $a);
1156     $item['gradetype'] = GRADE_TYPE_VALUE;
1157     $item['grademax']  = $workshop->grade;
1158     $item['grademin']  = 0;
1159     grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 0, $submissiongrades , $item);
1161     $item = array();
1162     $item['itemname'] = get_string('gradeitemassessment', 'workshop', $a);
1163     $item['gradetype'] = GRADE_TYPE_VALUE;
1164     $item['grademax']  = $workshop->gradinggrade;
1165     $item['grademin']  = 0;
1166     grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 1, $assessmentgrades, $item);
1169 /**
1170  * Update workshop grades in the gradebook
1171  *
1172  * Needed by grade_update_mod_grades() in lib/gradelib.php
1173  *
1174  * @category grade
1175  * @param stdClass $workshop instance object with extra cmidnumber and modname property
1176  * @param int $userid        update grade of specific user only, 0 means all participants
1177  * @return void
1178  */
1179 function workshop_update_grades(stdclass $workshop, $userid=0) {
1180     global $CFG, $DB;
1181     require_once($CFG->libdir.'/gradelib.php');
1183     $whereuser = $userid ? ' AND authorid = :userid' : '';
1184     $params = array('workshopid' => $workshop->id, 'userid' => $userid);
1185     $sql = 'SELECT authorid, grade, gradeover, gradeoverby, feedbackauthor, feedbackauthorformat, timemodified, timegraded
1186               FROM {workshop_submissions}
1187              WHERE workshopid = :workshopid AND example=0' . $whereuser;
1188     $records = $DB->get_records_sql($sql, $params);
1189     $submissiongrades = array();
1190     foreach ($records as $record) {
1191         $grade = new stdclass();
1192         $grade->userid = $record->authorid;
1193         if (!is_null($record->gradeover)) {
1194             $grade->rawgrade = grade_floatval($workshop->grade * $record->gradeover / 100);
1195             $grade->usermodified = $record->gradeoverby;
1196         } else {
1197             $grade->rawgrade = grade_floatval($workshop->grade * $record->grade / 100);
1198         }
1199         $grade->feedback = $record->feedbackauthor;
1200         $grade->feedbackformat = $record->feedbackauthorformat;
1201         $grade->datesubmitted = $record->timemodified;
1202         $grade->dategraded = $record->timegraded;
1203         $submissiongrades[$record->authorid] = $grade;
1204     }
1206     $whereuser = $userid ? ' AND userid = :userid' : '';
1207     $params = array('workshopid' => $workshop->id, 'userid' => $userid);
1208     $sql = 'SELECT userid, gradinggrade, timegraded
1209               FROM {workshop_aggregations}
1210              WHERE workshopid = :workshopid' . $whereuser;
1211     $records = $DB->get_records_sql($sql, $params);
1212     $assessmentgrades = array();
1213     foreach ($records as $record) {
1214         $grade = new stdclass();
1215         $grade->userid = $record->userid;
1216         $grade->rawgrade = grade_floatval($workshop->gradinggrade * $record->gradinggrade / 100);
1217         $grade->dategraded = $record->timegraded;
1218         $assessmentgrades[$record->userid] = $grade;
1219     }
1221     workshop_grade_item_update($workshop, $submissiongrades, $assessmentgrades);
1224 /**
1225  * Update the grade items categories if they are changed via mod_form.php
1226  *
1227  * We must do it manually here in the workshop module because modedit supports only
1228  * single grade item while we use two.
1229  *
1230  * @param stdClass $workshop An object from the form in mod_form.php
1231  */
1232 function workshop_grade_item_category_update($workshop) {
1234     $gradeitems = grade_item::fetch_all(array(
1235         'itemtype'      => 'mod',
1236         'itemmodule'    => 'workshop',
1237         'iteminstance'  => $workshop->id,
1238         'courseid'      => $workshop->course));
1240     if (!empty($gradeitems)) {
1241         foreach ($gradeitems as $gradeitem) {
1242             if ($gradeitem->itemnumber == 0) {
1243                 if (isset($workshop->submissiongradepass) &&
1244                         $gradeitem->gradepass != $workshop->submissiongradepass) {
1245                     $gradeitem->gradepass = $workshop->submissiongradepass;
1246                     $gradeitem->update();
1247                 }
1248                 if ($gradeitem->categoryid != $workshop->gradecategory) {
1249                     $gradeitem->set_parent($workshop->gradecategory);
1250                 }
1251             } else if ($gradeitem->itemnumber == 1) {
1252                 if (isset($workshop->gradinggradepass) &&
1253                         $gradeitem->gradepass != $workshop->gradinggradepass) {
1254                     $gradeitem->gradepass = $workshop->gradinggradepass;
1255                     $gradeitem->update();
1256                 }
1257                 if ($gradeitem->categoryid != $workshop->gradinggradecategory) {
1258                     $gradeitem->set_parent($workshop->gradinggradecategory);
1259                 }
1260             }
1261         }
1262     }
1265 ////////////////////////////////////////////////////////////////////////////////
1266 // File API                                                                   //
1267 ////////////////////////////////////////////////////////////////////////////////
1269 /**
1270  * Returns the lists of all browsable file areas within the given module context
1271  *
1272  * The file area workshop_intro for the activity introduction field is added automatically
1273  * by {@link file_browser::get_file_info_context_module()}
1274  *
1275  * @package  mod_workshop
1276  * @category files
1277  *
1278  * @param stdClass $course
1279  * @param stdClass $cm
1280  * @param stdClass $context
1281  * @return array of [(string)filearea] => (string)description
1282  */
1283 function workshop_get_file_areas($course, $cm, $context) {
1284     $areas = array();
1285     $areas['instructauthors']          = get_string('areainstructauthors', 'workshop');
1286     $areas['instructreviewers']        = get_string('areainstructreviewers', 'workshop');
1287     $areas['submission_content']       = get_string('areasubmissioncontent', 'workshop');
1288     $areas['submission_attachment']    = get_string('areasubmissionattachment', 'workshop');
1289     $areas['conclusion']               = get_string('areaconclusion', 'workshop');
1290     $areas['overallfeedback_content']  = get_string('areaoverallfeedbackcontent', 'workshop');
1291     $areas['overallfeedback_attachment'] = get_string('areaoverallfeedbackattachment', 'workshop');
1293     return $areas;
1296 /**
1297  * Serves the files from the workshop file areas
1298  *
1299  * Apart from module intro (handled by pluginfile.php automatically), workshop files may be
1300  * media inserted into submission content (like images) and submission attachments. For these two,
1301  * the fileareas submission_content and submission_attachment are used.
1302  * Besides that, areas instructauthors, instructreviewers and conclusion contain the media
1303  * embedded using the mod_form.php.
1304  *
1305  * @package  mod_workshop
1306  * @category files
1307  *
1308  * @param stdClass $course the course object
1309  * @param stdClass $cm the course module object
1310  * @param stdClass $context the workshop's context
1311  * @param string $filearea the name of the file area
1312  * @param array $args extra arguments (itemid, path)
1313  * @param bool $forcedownload whether or not force download
1314  * @param array $options additional options affecting the file serving
1315  * @return bool false if the file not found, just send the file otherwise and do not return anything
1316  */
1317 function workshop_pluginfile($course, $cm, $context, $filearea, array $args, $forcedownload, array $options=array()) {
1318     global $DB, $CFG, $USER;
1320     if ($context->contextlevel != CONTEXT_MODULE) {
1321         return false;
1322     }
1324     require_login($course, true, $cm);
1326     if ($filearea === 'instructauthors' or $filearea === 'instructreviewers' or $filearea === 'conclusion') {
1327         // The $args are supposed to contain just the path, not the item id.
1328         $relativepath = implode('/', $args);
1329         $fullpath = "/$context->id/mod_workshop/$filearea/0/$relativepath";
1331         $fs = get_file_storage();
1332         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1333             send_file_not_found();
1334         }
1335         send_stored_file($file, null, 0, $forcedownload, $options);
1337     } else if ($filearea === 'submission_content' or $filearea === 'submission_attachment') {
1338         $itemid = (int)array_shift($args);
1339         if (!$workshop = $DB->get_record('workshop', array('id' => $cm->instance))) {
1340             return false;
1341         }
1342         if (!$submission = $DB->get_record('workshop_submissions', array('id' => $itemid, 'workshopid' => $workshop->id))) {
1343             return false;
1344         }
1346         // make sure the user is allowed to see the file
1347         if (empty($submission->example)) {
1348             if ($USER->id != $submission->authorid) {
1349                 if ($submission->published == 1 and $workshop->phase == 50
1350                         and has_capability('mod/workshop:viewpublishedsubmissions', $context)) {
1351                     // Published submission, we can go (workshop does not take the group mode
1352                     // into account in this case yet).
1353                 } else if (!$DB->record_exists('workshop_assessments', array('submissionid' => $submission->id, 'reviewerid' => $USER->id))) {
1354                     if (!has_capability('mod/workshop:viewallsubmissions', $context)) {
1355                         send_file_not_found();
1356                     } else {
1357                         $gmode = groups_get_activity_groupmode($cm, $course);
1358                         if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1359                             // check there is at least one common group with both the $USER
1360                             // and the submission author
1361                             $sql = "SELECT 'x'
1362                                       FROM {workshop_submissions} s
1363                                       JOIN {user} a ON (a.id = s.authorid)
1364                                       JOIN {groups_members} agm ON (a.id = agm.userid)
1365                                       JOIN {user} u ON (u.id = ?)
1366                                       JOIN {groups_members} ugm ON (u.id = ugm.userid)
1367                                      WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1368                             $params = array($USER->id, $workshop->id, $submission->id);
1369                             if (!$DB->record_exists_sql($sql, $params)) {
1370                                 send_file_not_found();
1371                             }
1372                         }
1373                     }
1374                 }
1375             }
1376         }
1378         $fs = get_file_storage();
1379         $relativepath = implode('/', $args);
1380         $fullpath = "/$context->id/mod_workshop/$filearea/$itemid/$relativepath";
1381         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1382             return false;
1383         }
1384         // finally send the file
1385         // these files are uploaded by students - forcing download for security reasons
1386         send_stored_file($file, 0, 0, true, $options);
1388     } else if ($filearea === 'overallfeedback_content' or $filearea === 'overallfeedback_attachment') {
1389         $itemid = (int)array_shift($args);
1390         if (!$workshop = $DB->get_record('workshop', array('id' => $cm->instance))) {
1391             return false;
1392         }
1393         if (!$assessment = $DB->get_record('workshop_assessments', array('id' => $itemid))) {
1394             return false;
1395         }
1396         if (!$submission = $DB->get_record('workshop_submissions', array('id' => $assessment->submissionid, 'workshopid' => $workshop->id))) {
1397             return false;
1398         }
1400         if ($USER->id == $assessment->reviewerid) {
1401             // Reviewers can always see their own files.
1402         } else if ($USER->id == $submission->authorid and $workshop->phase == 50) {
1403             // Authors can see the feedback once the workshop is closed.
1404         } else if (!empty($submission->example) and $assessment->weight == 1) {
1405             // Reference assessments of example submissions can be displayed.
1406         } else if (!has_capability('mod/workshop:viewallassessments', $context)) {
1407             send_file_not_found();
1408         } else {
1409             $gmode = groups_get_activity_groupmode($cm, $course);
1410             if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1411                 // Check there is at least one common group with both the $USER
1412                 // and the submission author.
1413                 $sql = "SELECT 'x'
1414                           FROM {workshop_submissions} s
1415                           JOIN {user} a ON (a.id = s.authorid)
1416                           JOIN {groups_members} agm ON (a.id = agm.userid)
1417                           JOIN {user} u ON (u.id = ?)
1418                           JOIN {groups_members} ugm ON (u.id = ugm.userid)
1419                          WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1420                 $params = array($USER->id, $workshop->id, $submission->id);
1421                 if (!$DB->record_exists_sql($sql, $params)) {
1422                     send_file_not_found();
1423                 }
1424             }
1425         }
1427         $fs = get_file_storage();
1428         $relativepath = implode('/', $args);
1429         $fullpath = "/$context->id/mod_workshop/$filearea/$itemid/$relativepath";
1430         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1431             return false;
1432         }
1433         // finally send the file
1434         // these files are uploaded by students - forcing download for security reasons
1435         send_stored_file($file, 0, 0, true, $options);
1436     }
1438     return false;
1441 /**
1442  * File browsing support for workshop file areas
1443  *
1444  * @package  mod_workshop
1445  * @category files
1446  *
1447  * @param file_browser $browser
1448  * @param array $areas
1449  * @param stdClass $course
1450  * @param stdClass $cm
1451  * @param stdClass $context
1452  * @param string $filearea
1453  * @param int $itemid
1454  * @param string $filepath
1455  * @param string $filename
1456  * @return file_info instance or null if not found
1457  */
1458 function workshop_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
1459     global $CFG, $DB, $USER;
1461     /** @var array internal cache for author names */
1462     static $submissionauthors = array();
1464     $fs = get_file_storage();
1466     if ($filearea === 'submission_content' or $filearea === 'submission_attachment') {
1468         if (!has_capability('mod/workshop:viewallsubmissions', $context)) {
1469             return null;
1470         }
1472         if (is_null($itemid)) {
1473             // no itemid (submissionid) passed, display the list of all submissions
1474             require_once($CFG->dirroot . '/mod/workshop/fileinfolib.php');
1475             return new workshop_file_info_submissions_container($browser, $course, $cm, $context, $areas, $filearea);
1476         }
1478         // make sure the user can see the particular submission in separate groups mode
1479         $gmode = groups_get_activity_groupmode($cm, $course);
1481         if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1482             // check there is at least one common group with both the $USER
1483             // and the submission author (this is not expected to be a frequent
1484             // usecase so we can live with pretty ineffective one query per submission here...)
1485             $sql = "SELECT 'x'
1486                       FROM {workshop_submissions} s
1487                       JOIN {user} a ON (a.id = s.authorid)
1488                       JOIN {groups_members} agm ON (a.id = agm.userid)
1489                       JOIN {user} u ON (u.id = ?)
1490                       JOIN {groups_members} ugm ON (u.id = ugm.userid)
1491                      WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1492             $params = array($USER->id, $cm->instance, $itemid);
1493             if (!$DB->record_exists_sql($sql, $params)) {
1494                 return null;
1495             }
1496         }
1498         // we are inside some particular submission container
1500         $filepath = is_null($filepath) ? '/' : $filepath;
1501         $filename = is_null($filename) ? '.' : $filename;
1503         if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, $itemid, $filepath, $filename)) {
1504             if ($filepath === '/' and $filename === '.') {
1505                 $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, $itemid);
1506             } else {
1507                 // not found
1508                 return null;
1509             }
1510         }
1512         // Checks to see if the user can manage files or is the owner.
1513         // TODO MDL-33805 - Do not use userid here and move the capability check above.
1514         if (!has_capability('moodle/course:managefiles', $context) && $storedfile->get_userid() != $USER->id) {
1515             return null;
1516         }
1518         // let us display the author's name instead of itemid (submission id)
1520         if (isset($submissionauthors[$itemid])) {
1521             $topvisiblename = $submissionauthors[$itemid];
1523         } else {
1525             $sql = "SELECT s.id, u.lastname, u.firstname
1526                       FROM {workshop_submissions} s
1527                       JOIN {user} u ON (s.authorid = u.id)
1528                      WHERE s.example = 0 AND s.workshopid = ?";
1529             $params = array($cm->instance);
1530             $rs = $DB->get_recordset_sql($sql, $params);
1532             foreach ($rs as $submissionauthor) {
1533                 $title = s(fullname($submissionauthor)); // this is generally not unique...
1534                 $submissionauthors[$submissionauthor->id] = $title;
1535             }
1536             $rs->close();
1538             if (!isset($submissionauthors[$itemid])) {
1539                 // should not happen
1540                 return null;
1541             } else {
1542                 $topvisiblename = $submissionauthors[$itemid];
1543             }
1544         }
1546         $urlbase = $CFG->wwwroot . '/pluginfile.php';
1547         // do not allow manual modification of any files!
1548         return new file_info_stored($browser, $context, $storedfile, $urlbase, $topvisiblename, true, true, false, false);
1549     }
1551     if ($filearea === 'overallfeedback_content' or $filearea === 'overallfeedback_attachment') {
1553         if (!has_capability('mod/workshop:viewallassessments', $context)) {
1554             return null;
1555         }
1557         if (is_null($itemid)) {
1558             // No itemid (assessmentid) passed, display the list of all assessments.
1559             require_once($CFG->dirroot . '/mod/workshop/fileinfolib.php');
1560             return new workshop_file_info_overallfeedback_container($browser, $course, $cm, $context, $areas, $filearea);
1561         }
1563         // Make sure the user can see the particular assessment in separate groups mode.
1564         $gmode = groups_get_activity_groupmode($cm, $course);
1565         if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1566             // Check there is at least one common group with both the $USER
1567             // and the submission author.
1568             $sql = "SELECT 'x'
1569                       FROM {workshop_submissions} s
1570                       JOIN {user} a ON (a.id = s.authorid)
1571                       JOIN {groups_members} agm ON (a.id = agm.userid)
1572                       JOIN {user} u ON (u.id = ?)
1573                       JOIN {groups_members} ugm ON (u.id = ugm.userid)
1574                      WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1575             $params = array($USER->id, $cm->instance, $itemid);
1576             if (!$DB->record_exists_sql($sql, $params)) {
1577                 return null;
1578             }
1579         }
1581         // We are inside a particular assessment container.
1582         $filepath = is_null($filepath) ? '/' : $filepath;
1583         $filename = is_null($filename) ? '.' : $filename;
1585         if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, $itemid, $filepath, $filename)) {
1586             if ($filepath === '/' and $filename === '.') {
1587                 $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, $itemid);
1588             } else {
1589                 // Not found
1590                 return null;
1591             }
1592         }
1594         // Check to see if the user can manage files or is the owner.
1595         if (!has_capability('moodle/course:managefiles', $context) and $storedfile->get_userid() != $USER->id) {
1596             return null;
1597         }
1599         $urlbase = $CFG->wwwroot . '/pluginfile.php';
1601         // Do not allow manual modification of any files.
1602         return new file_info_stored($browser, $context, $storedfile, $urlbase, $itemid, true, true, false, false);
1603     }
1605     if ($filearea == 'instructauthors' or $filearea == 'instructreviewers' or $filearea == 'conclusion') {
1606         // always only itemid 0
1608         $filepath = is_null($filepath) ? '/' : $filepath;
1609         $filename = is_null($filename) ? '.' : $filename;
1611         $urlbase = $CFG->wwwroot.'/pluginfile.php';
1612         if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, 0, $filepath, $filename)) {
1613             if ($filepath === '/' and $filename === '.') {
1614                 $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, 0);
1615             } else {
1616                 // not found
1617                 return null;
1618             }
1619         }
1620         return new file_info_stored($browser, $context, $storedfile, $urlbase, $areas[$filearea], false, true, true, false);
1621     }
1624 ////////////////////////////////////////////////////////////////////////////////
1625 // Navigation API                                                             //
1626 ////////////////////////////////////////////////////////////////////////////////
1628 /**
1629  * Extends the global navigation tree by adding workshop nodes if there is a relevant content
1630  *
1631  * This can be called by an AJAX request so do not rely on $PAGE as it might not be set up properly.
1632  *
1633  * @param navigation_node $navref An object representing the navigation tree node of the workshop module instance
1634  * @param stdClass $course
1635  * @param stdClass $module
1636  * @param cm_info $cm
1637  */
1638 function workshop_extend_navigation(navigation_node $navref, stdclass $course, stdclass $module, cm_info $cm) {
1639     global $CFG;
1641     if (has_capability('mod/workshop:submit', context_module::instance($cm->id))) {
1642         $url = new moodle_url('/mod/workshop/submission.php', array('cmid' => $cm->id));
1643         $mysubmission = $navref->add(get_string('mysubmission', 'workshop'), $url);
1644         $mysubmission->mainnavonly = true;
1645     }
1648 /**
1649  * Extends the settings navigation with the Workshop settings
1651  * This function is called when the context for the page is a workshop module. This is not called by AJAX
1652  * so it is safe to rely on the $PAGE.
1653  *
1654  * @param settings_navigation $settingsnav {@link settings_navigation}
1655  * @param navigation_node $workshopnode {@link navigation_node}
1656  */
1657 function workshop_extend_settings_navigation(settings_navigation $settingsnav, navigation_node $workshopnode=null) {
1658     global $PAGE;
1660     //$workshopobject = $DB->get_record("workshop", array("id" => $PAGE->cm->instance));
1662     if (has_capability('mod/workshop:editdimensions', $PAGE->cm->context)) {
1663         $url = new moodle_url('/mod/workshop/editform.php', array('cmid' => $PAGE->cm->id));
1664         $workshopnode->add(get_string('editassessmentform', 'workshop'), $url, settings_navigation::TYPE_SETTING);
1665     }
1666     if (has_capability('mod/workshop:allocate', $PAGE->cm->context)) {
1667         $url = new moodle_url('/mod/workshop/allocation.php', array('cmid' => $PAGE->cm->id));
1668         $workshopnode->add(get_string('allocate', 'workshop'), $url, settings_navigation::TYPE_SETTING);
1669     }
1672 /**
1673  * Return a list of page types
1674  * @param string $pagetype current page type
1675  * @param stdClass $parentcontext Block's parent context
1676  * @param stdClass $currentcontext Current context of block
1677  */
1678 function workshop_page_type_list($pagetype, $parentcontext, $currentcontext) {
1679     $module_pagetype = array('mod-workshop-*'=>get_string('page-mod-workshop-x', 'workshop'));
1680     return $module_pagetype;
1683 ////////////////////////////////////////////////////////////////////////////////
1684 // Calendar API                                                               //
1685 ////////////////////////////////////////////////////////////////////////////////
1687 /**
1688  * Updates the calendar events associated to the given workshop
1689  *
1690  * @param stdClass $workshop the workshop instance record
1691  * @param int $cmid course module id
1692  */
1693 function workshop_calendar_update(stdClass $workshop, $cmid) {
1694     global $DB;
1696     // get the currently registered events so that we can re-use their ids
1697     $currentevents = $DB->get_records('event', array('modulename' => 'workshop', 'instance' => $workshop->id));
1699     // the common properties for all events
1700     $base = new stdClass();
1701     $base->description  = format_module_intro('workshop', $workshop, $cmid, false);
1702     $base->courseid     = $workshop->course;
1703     $base->groupid      = 0;
1704     $base->userid       = 0;
1705     $base->modulename   = 'workshop';
1706     $base->instance     = $workshop->id;
1707     $base->visible      = instance_is_visible('workshop', $workshop);
1708     $base->timeduration = 0;
1710     if ($workshop->submissionstart) {
1711         $event = clone($base);
1712         $event->name = get_string('submissionstartevent', 'mod_workshop', $workshop->name);
1713         $event->eventtype = WORKSHOP_EVENT_TYPE_SUBMISSION_OPEN;
1714         $event->type = empty($workshop->submissionend) ? CALENDAR_EVENT_TYPE_ACTION : CALENDAR_EVENT_TYPE_STANDARD;
1715         $event->timestart = $workshop->submissionstart;
1716         $event->timesort  = $workshop->submissionstart;
1717         if ($reusedevent = array_shift($currentevents)) {
1718             $event->id = $reusedevent->id;
1719         } else {
1720             // should not be set but just in case
1721             unset($event->id);
1722         }
1723         // update() will reuse a db record if the id field is set
1724         $eventobj = new calendar_event($event);
1725         $eventobj->update($event, false);
1726     }
1728     if ($workshop->submissionend) {
1729         $event = clone($base);
1730         $event->name = get_string('submissionendevent', 'mod_workshop', $workshop->name);
1731         $event->eventtype = WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE;
1732         $event->type      = CALENDAR_EVENT_TYPE_ACTION;
1733         $event->timestart = $workshop->submissionend;
1734         $event->timesort  = $workshop->submissionend;
1735         if ($reusedevent = array_shift($currentevents)) {
1736             $event->id = $reusedevent->id;
1737         } else {
1738             // should not be set but just in case
1739             unset($event->id);
1740         }
1741         // update() will reuse a db record if the id field is set
1742         $eventobj = new calendar_event($event);
1743         $eventobj->update($event, false);
1744     }
1746     if ($workshop->assessmentstart) {
1747         $event = clone($base);
1748         $event->name = get_string('assessmentstartevent', 'mod_workshop', $workshop->name);
1749         $event->eventtype = WORKSHOP_EVENT_TYPE_ASSESSMENT_OPEN;
1750         $event->type      = empty($workshop->assessmentend) ? CALENDAR_EVENT_TYPE_ACTION : CALENDAR_EVENT_TYPE_STANDARD;
1751         $event->timestart = $workshop->assessmentstart;
1752         $event->timesort  = $workshop->assessmentstart;
1753         if ($reusedevent = array_shift($currentevents)) {
1754             $event->id = $reusedevent->id;
1755         } else {
1756             // should not be set but just in case
1757             unset($event->id);
1758         }
1759         // update() will reuse a db record if the id field is set
1760         $eventobj = new calendar_event($event);
1761         $eventobj->update($event, false);
1762     }
1764     if ($workshop->assessmentend) {
1765         $event = clone($base);
1766         $event->name = get_string('assessmentendevent', 'mod_workshop', $workshop->name);
1767         $event->eventtype = WORKSHOP_EVENT_TYPE_ASSESSMENT_CLOSE;
1768         $event->type      = CALENDAR_EVENT_TYPE_ACTION;
1769         $event->timestart = $workshop->assessmentend;
1770         $event->timesort  = $workshop->assessmentend;
1771         if ($reusedevent = array_shift($currentevents)) {
1772             $event->id = $reusedevent->id;
1773         } else {
1774             // should not be set but just in case
1775             unset($event->id);
1776         }
1777         // update() will reuse a db record if the id field is set
1778         $eventobj = new calendar_event($event);
1779         $eventobj->update($event, false);
1780     }
1782     // delete any leftover events
1783     foreach ($currentevents as $oldevent) {
1784         $oldevent = calendar_event::load($oldevent);
1785         $oldevent->delete();
1786     }
1789 /**
1790  * This function receives a calendar event and returns the action associated with it, or null if there is none.
1791  *
1792  * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event
1793  * is not displayed on the block.
1794  *
1795  * @param calendar_event $event
1796  * @param \core_calendar\action_factory $factory
1797  * @return \core_calendar\local\event\entities\action_interface|null
1798  */
1799 function mod_workshop_core_calendar_provide_event_action(calendar_event $event,
1800                                                          \core_calendar\action_factory $factory) {
1802     $cm = get_fast_modinfo($event->courseid)->instances['workshop'][$event->instance];
1804     return $factory->create_instance(
1805         get_string('viewworkshopsummary', 'workshop'),
1806         new \moodle_url('/mod/workshop/view.php', array('id' => $cm->id)),
1807         1,
1808         true
1809     );
1812 ////////////////////////////////////////////////////////////////////////////////
1813 // Course reset API                                                           //
1814 ////////////////////////////////////////////////////////////////////////////////
1816 /**
1817  * Extends the course reset form with workshop specific settings.
1818  *
1819  * @param MoodleQuickForm $mform
1820  */
1821 function workshop_reset_course_form_definition($mform) {
1823     $mform->addElement('header', 'workshopheader', get_string('modulenameplural', 'mod_workshop'));
1825     $mform->addElement('advcheckbox', 'reset_workshop_submissions', get_string('resetsubmissions', 'mod_workshop'));
1826     $mform->addHelpButton('reset_workshop_submissions', 'resetsubmissions', 'mod_workshop');
1828     $mform->addElement('advcheckbox', 'reset_workshop_assessments', get_string('resetassessments', 'mod_workshop'));
1829     $mform->addHelpButton('reset_workshop_assessments', 'resetassessments', 'mod_workshop');
1830     $mform->disabledIf('reset_workshop_assessments', 'reset_workshop_submissions', 'checked');
1832     $mform->addElement('advcheckbox', 'reset_workshop_phase', get_string('resetphase', 'mod_workshop'));
1833     $mform->addHelpButton('reset_workshop_phase', 'resetphase', 'mod_workshop');
1836 /**
1837  * Provides default values for the workshop settings in the course reset form.
1838  *
1839  * @param stdClass $course The course to be reset.
1840  */
1841 function workshop_reset_course_form_defaults(stdClass $course) {
1843     $defaults = array(
1844         'reset_workshop_submissions'    => 1,
1845         'reset_workshop_assessments'    => 1,
1846         'reset_workshop_phase'          => 1,
1847     );
1849     return $defaults;
1852 /**
1853  * Performs the reset of all workshop instances in the course.
1854  *
1855  * @param stdClass $data The actual course reset settings.
1856  * @return array List of results, each being array[(string)component, (string)item, (string)error]
1857  */
1858 function workshop_reset_userdata(stdClass $data) {
1859     global $CFG, $DB;
1861     if (empty($data->reset_workshop_submissions)
1862             and empty($data->reset_workshop_assessments)
1863             and empty($data->reset_workshop_phase) ) {
1864         // Nothing to do here.
1865         return array();
1866     }
1868     $workshoprecords = $DB->get_records('workshop', array('course' => $data->courseid));
1870     if (empty($workshoprecords)) {
1871         // What a boring course - no workshops here!
1872         return array();
1873     }
1875     require_once($CFG->dirroot . '/mod/workshop/locallib.php');
1877     $course = $DB->get_record('course', array('id' => $data->courseid), '*', MUST_EXIST);
1878     $status = array();
1880     foreach ($workshoprecords as $workshoprecord) {
1881         $cm = get_coursemodule_from_instance('workshop', $workshoprecord->id, $course->id, false, MUST_EXIST);
1882         $workshop = new workshop($workshoprecord, $cm, $course);
1883         $status = array_merge($status, $workshop->reset_userdata($data));
1884     }
1886     return $status;
1889 /**
1890  * Get icon mapping for font-awesome.
1891  */
1892 function mod_workshop_get_fontawesome_icon_map() {
1893     return [
1894         'mod_workshop:userplan/task-info' => 'fa-info text-info',
1895         'mod_workshop:userplan/task-todo' => 'fa-square-o',
1896         'mod_workshop:userplan/task-done' => 'fa-check text-success',
1897         'mod_workshop:userplan/task-fail' => 'fa-remove text-danger',
1898     ];