MDL-59287 calendar_events: Update modules to create all events.
[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);
141     if (!empty($workshop->completionexpected)) {
142         \core_completion\api::update_completion_date_event($cmid, 'workshop', $workshop->id, $workshop->completionexpected);
143     }
145     return $workshop->id;
148 /**
149  * Given an object containing all the necessary data,
150  * (defined by the form in mod_form.php) this function
151  * will update an existing instance with new data.
152  *
153  * @param stdClass $workshop An object from the form in mod_form.php
154  * @return bool success
155  */
156 function workshop_update_instance(stdclass $workshop) {
157     global $CFG, $DB;
158     require_once(__DIR__ . '/locallib.php');
160     $workshop->timemodified          = time();
161     $workshop->id                    = $workshop->instance;
162     $workshop->useexamples           = (int)!empty($workshop->useexamples);
163     $workshop->usepeerassessment     = 1;
164     $workshop->useselfassessment     = (int)!empty($workshop->useselfassessment);
165     $workshop->latesubmissions       = (int)!empty($workshop->latesubmissions);
166     $workshop->phaseswitchassessment = (int)!empty($workshop->phaseswitchassessment);
168     if (isset($workshop->gradinggradepass)) {
169         $workshop->gradinggradepass = (float)unformat_float($workshop->gradinggradepass);
170     }
172     if (isset($workshop->submissiongradepass)) {
173         $workshop->submissiongradepass = (float)unformat_float($workshop->submissiongradepass);
174     }
176     if (isset($workshop->submissionfiletypes)) {
177         $workshop->submissionfiletypes = workshop::clean_file_extensions($workshop->submissionfiletypes);
178     }
180     if (isset($workshop->overallfeedbackfiletypes)) {
181         $workshop->overallfeedbackfiletypes = workshop::clean_file_extensions($workshop->overallfeedbackfiletypes);
182     }
184     // todo - if the grading strategy is being changed, we may want to replace all aggregated peer grades with nulls
186     $DB->update_record('workshop', $workshop);
187     $context = context_module::instance($workshop->coursemodule);
189     // process the custom wysiwyg editors
190     if ($draftitemid = $workshop->instructauthorseditor['itemid']) {
191         $workshop->instructauthors = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructauthors',
192                 0, workshop::instruction_editors_options($context), $workshop->instructauthorseditor['text']);
193         $workshop->instructauthorsformat = $workshop->instructauthorseditor['format'];
194     }
196     if ($draftitemid = $workshop->instructreviewerseditor['itemid']) {
197         $workshop->instructreviewers = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructreviewers',
198                 0, workshop::instruction_editors_options($context), $workshop->instructreviewerseditor['text']);
199         $workshop->instructreviewersformat = $workshop->instructreviewerseditor['format'];
200     }
202     if ($draftitemid = $workshop->conclusioneditor['itemid']) {
203         $workshop->conclusion = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'conclusion',
204                 0, workshop::instruction_editors_options($context), $workshop->conclusioneditor['text']);
205         $workshop->conclusionformat = $workshop->conclusioneditor['format'];
206     }
208     // re-save the record with the replaced URLs in editor fields
209     $DB->update_record('workshop', $workshop);
211     // update gradebook items
212     workshop_grade_item_update($workshop);
213     workshop_grade_item_category_update($workshop);
215     // update calendar events
216     workshop_calendar_update($workshop, $workshop->coursemodule);
217     $completionexpected = (!empty($workshop->completionexpected)) ? $workshop->completionexpected : null;
218     \core_completion\api::update_completion_date_event($workshop->coursemodule, 'workshop', $workshop->id, $completionexpected);
220     return true;
223 /**
224  * Given an ID of an instance of this module,
225  * this function will permanently delete the instance
226  * and any data that depends on it.
227  *
228  * @param int $id Id of the module instance
229  * @return boolean Success/Failure
230  */
231 function workshop_delete_instance($id) {
232     global $CFG, $DB;
233     require_once($CFG->libdir.'/gradelib.php');
235     if (! $workshop = $DB->get_record('workshop', array('id' => $id))) {
236         return false;
237     }
239     // delete all associated aggregations
240     $DB->delete_records('workshop_aggregations', array('workshopid' => $workshop->id));
242     // get the list of ids of all submissions
243     $submissions = $DB->get_records('workshop_submissions', array('workshopid' => $workshop->id), '', 'id');
245     // get the list of all allocated assessments
246     $assessments = $DB->get_records_list('workshop_assessments', 'submissionid', array_keys($submissions), '', 'id');
248     // delete the associated records from the workshop core tables
249     $DB->delete_records_list('workshop_grades', 'assessmentid', array_keys($assessments));
250     $DB->delete_records_list('workshop_assessments', 'id', array_keys($assessments));
251     $DB->delete_records_list('workshop_submissions', 'id', array_keys($submissions));
253     // call the static clean-up methods of all available subplugins
254     $strategies = core_component::get_plugin_list('workshopform');
255     foreach ($strategies as $strategy => $path) {
256         require_once($path.'/lib.php');
257         $classname = 'workshop_'.$strategy.'_strategy';
258         call_user_func($classname.'::delete_instance', $workshop->id);
259     }
261     $allocators = core_component::get_plugin_list('workshopallocation');
262     foreach ($allocators as $allocator => $path) {
263         require_once($path.'/lib.php');
264         $classname = 'workshop_'.$allocator.'_allocator';
265         call_user_func($classname.'::delete_instance', $workshop->id);
266     }
268     $evaluators = core_component::get_plugin_list('workshopeval');
269     foreach ($evaluators as $evaluator => $path) {
270         require_once($path.'/lib.php');
271         $classname = 'workshop_'.$evaluator.'_evaluation';
272         call_user_func($classname.'::delete_instance', $workshop->id);
273     }
275     // delete the calendar events
276     $events = $DB->get_records('event', array('modulename' => 'workshop', 'instance' => $workshop->id));
277     foreach ($events as $event) {
278         $event = calendar_event::load($event);
279         $event->delete();
280     }
282     // finally remove the workshop record itself
283     $DB->delete_records('workshop', array('id' => $workshop->id));
285     // gradebook cleanup
286     grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 0, null, array('deleted' => true));
287     grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 1, null, array('deleted' => true));
289     return true;
292 /**
293  * This standard function will check all instances of this module
294  * and make sure there are up-to-date events created for each of them.
295  * If courseid = 0, then every workshop event in the site is checked, else
296  * only workshop events belonging to the course specified are checked.
297  *
298  * @param  integer $courseid The Course ID.
299  * @param int|stdClass $instance workshop module instance or ID.
300  * @param int|stdClass $cm Course module object or ID.
301  * @return bool Returns true if the calendar events were successfully updated.
302  */
303 function workshop_refresh_events($courseid = 0, $instance = null, $cm = null) {
304     global $DB;
306     // If we have instance information then we can just update the one event instead of updating all events.
307     if (isset($instance)) {
308         if (!is_object($instance)) {
309             $instance = $DB->get_record('workshop', array('id' => $instance), '*', MUST_EXIST);
310         }
311         if (isset($cm)) {
312             if (!is_object($cm)) {
313                 $cm = (object)array('id' => $cm);
314             }
315         } else {
316             $cm = get_coursemodule_from_instance('workshop', $instance->id);
317         }
318         workshop_calendar_update($instance, $cm->id);
319         return true;
320     }
322     if ($courseid) {
323         // Make sure that the course id is numeric.
324         if (!is_numeric($courseid)) {
325             return false;
326         }
327         if (!$workshops = $DB->get_records('workshop', array('course' => $courseid))) {
328             return false;
329         }
330     } else {
331         if (!$workshops = $DB->get_records('workshop')) {
332             return false;
333         }
334     }
335     foreach ($workshops as $workshop) {
336         if (!$cm = get_coursemodule_from_instance('workshop', $workshop->id, $courseid, false)) {
337             continue;
338         }
339         workshop_calendar_update($workshop, $cm->id);
340     }
341     return true;
344 /**
345  * List the actions that correspond to a view of this module.
346  * This is used by the participation report.
347  *
348  * Note: This is not used by new logging system. Event with
349  *       crud = 'r' and edulevel = LEVEL_PARTICIPATING will
350  *       be considered as view action.
351  *
352  * @return array
353  */
354 function workshop_get_view_actions() {
355     return array('view', 'view all', 'view submission', 'view example');
358 /**
359  * List the actions that correspond to a post of this module.
360  * This is used by the participation report.
361  *
362  * Note: This is not used by new logging system. Event with
363  *       crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING
364  *       will be considered as post action.
365  *
366  * @return array
367  */
368 function workshop_get_post_actions() {
369     return array('add', 'add assessment', 'add example', 'add submission',
370                  'update', 'update assessment', 'update example', 'update submission');
373 /**
374  * Return a small object with summary information about what a
375  * user has done with a given particular instance of this module
376  * Used for user activity reports.
377  * $return->time = the time they did it
378  * $return->info = a short text description
379  *
380  * @param stdClass $course The course record.
381  * @param stdClass $user The user record.
382  * @param cm_info|stdClass $mod The course module info object or record.
383  * @param stdClass $workshop The workshop instance record.
384  * @return stdclass|null
385  */
386 function workshop_user_outline($course, $user, $mod, $workshop) {
387     global $CFG, $DB;
388     require_once($CFG->libdir.'/gradelib.php');
390     $grades = grade_get_grades($course->id, 'mod', 'workshop', $workshop->id, $user->id);
392     $submissiongrade = null;
393     $assessmentgrade = null;
395     $info = '';
396     $time = 0;
398     if (!empty($grades->items[0]->grades)) {
399         $submissiongrade = reset($grades->items[0]->grades);
400         $info .= get_string('submissiongrade', 'workshop') . ': ' . $submissiongrade->str_long_grade . html_writer::empty_tag('br');
401         $time = max($time, $submissiongrade->dategraded);
402     }
403     if (!empty($grades->items[1]->grades)) {
404         $assessmentgrade = reset($grades->items[1]->grades);
405         $info .= get_string('gradinggrade', 'workshop') . ': ' . $assessmentgrade->str_long_grade;
406         $time = max($time, $assessmentgrade->dategraded);
407     }
409     if (!empty($info) and !empty($time)) {
410         $return = new stdclass();
411         $return->time = $time;
412         $return->info = $info;
413         return $return;
414     }
416     return null;
419 /**
420  * Print a detailed representation of what a user has done with
421  * a given particular instance of this module, for user activity reports.
422  *
423  * @param stdClass $course The course record.
424  * @param stdClass $user The user record.
425  * @param cm_info|stdClass $mod The course module info object or record.
426  * @param stdClass $workshop The workshop instance record.
427  * @return string HTML
428  */
429 function workshop_user_complete($course, $user, $mod, $workshop) {
430     global $CFG, $DB, $OUTPUT;
431     require_once(__DIR__.'/locallib.php');
432     require_once($CFG->libdir.'/gradelib.php');
434     $workshop   = new workshop($workshop, $mod, $course);
435     $grades     = grade_get_grades($course->id, 'mod', 'workshop', $workshop->id, $user->id);
437     if (!empty($grades->items[0]->grades)) {
438         $submissiongrade = reset($grades->items[0]->grades);
439         $info = get_string('submissiongrade', 'workshop') . ': ' . $submissiongrade->str_long_grade;
440         echo html_writer::tag('li', $info, array('class'=>'submissiongrade'));
441     }
442     if (!empty($grades->items[1]->grades)) {
443         $assessmentgrade = reset($grades->items[1]->grades);
444         $info = get_string('gradinggrade', 'workshop') . ': ' . $assessmentgrade->str_long_grade;
445         echo html_writer::tag('li', $info, array('class'=>'gradinggrade'));
446     }
448     if (has_capability('mod/workshop:viewallsubmissions', $workshop->context)) {
449         $canviewsubmission = true;
450         if (groups_get_activity_groupmode($workshop->cm) == SEPARATEGROUPS) {
451             // user must have accessallgroups or share at least one group with the submission author
452             if (!has_capability('moodle/site:accessallgroups', $workshop->context)) {
453                 $usersgroups = groups_get_activity_allowed_groups($workshop->cm);
454                 $authorsgroups = groups_get_all_groups($workshop->course->id, $user->id, $workshop->cm->groupingid, 'g.id');
455                 $sharedgroups = array_intersect_key($usersgroups, $authorsgroups);
456                 if (empty($sharedgroups)) {
457                     $canviewsubmission = false;
458                 }
459             }
460         }
461         if ($canviewsubmission and $submission = $workshop->get_submission_by_author($user->id)) {
462             $title      = format_string($submission->title);
463             $url        = $workshop->submission_url($submission->id);
464             $link       = html_writer::link($url, $title);
465             $info       = get_string('submission', 'workshop').': '.$link;
466             echo html_writer::tag('li', $info, array('class'=>'submission'));
467         }
468     }
470     if (has_capability('mod/workshop:viewallassessments', $workshop->context)) {
471         if ($assessments = $workshop->get_assessments_by_reviewer($user->id)) {
472             foreach ($assessments as $assessment) {
473                 $a = new stdclass();
474                 $a->submissionurl = $workshop->submission_url($assessment->submissionid)->out();
475                 $a->assessmenturl = $workshop->assess_url($assessment->id)->out();
476                 $a->submissiontitle = s($assessment->submissiontitle);
477                 echo html_writer::tag('li', get_string('assessmentofsubmission', 'workshop', $a));
478             }
479         }
480     }
483 /**
484  * Given a course and a time, this module should find recent activity
485  * that has occurred in workshop activities and print it out.
486  * Return true if there was output, or false is there was none.
487  *
488  * @param stdClass $course
489  * @param bool $viewfullnames
490  * @param int $timestart
491  * @return boolean
492  */
493 function workshop_print_recent_activity($course, $viewfullnames, $timestart) {
494     global $CFG, $USER, $DB, $OUTPUT;
496     $authoramefields = get_all_user_name_fields(true, 'author', null, 'author');
497     $reviewerfields = get_all_user_name_fields(true, 'reviewer', null, 'reviewer');
499     $sql = "SELECT s.id AS submissionid, s.title AS submissiontitle, s.timemodified AS submissionmodified,
500                    author.id AS authorid, $authoramefields, a.id AS assessmentid, a.timemodified AS assessmentmodified,
501                    reviewer.id AS reviewerid, $reviewerfields, cm.id AS cmid
502               FROM {workshop} w
503         INNER JOIN {course_modules} cm ON cm.instance = w.id
504         INNER JOIN {modules} md ON md.id = cm.module
505         INNER JOIN {workshop_submissions} s ON s.workshopid = w.id
506         INNER JOIN {user} author ON s.authorid = author.id
507          LEFT JOIN {workshop_assessments} a ON a.submissionid = s.id
508          LEFT JOIN {user} reviewer ON a.reviewerid = reviewer.id
509              WHERE cm.course = ?
510                    AND md.name = 'workshop'
511                    AND s.example = 0
512                    AND (s.timemodified > ? OR a.timemodified > ?)
513           ORDER BY s.timemodified";
515     $rs = $DB->get_recordset_sql($sql, array($course->id, $timestart, $timestart));
517     $modinfo = get_fast_modinfo($course); // reference needed because we might load the groups
519     $submissions = array(); // recent submissions indexed by submission id
520     $assessments = array(); // recent assessments indexed by assessment id
521     $users       = array();
523     foreach ($rs as $activity) {
524         if (!array_key_exists($activity->cmid, $modinfo->cms)) {
525             // this should not happen but just in case
526             continue;
527         }
529         $cm = $modinfo->cms[$activity->cmid];
530         if (!$cm->uservisible) {
531             continue;
532         }
534         // remember all user names we can use later
535         if (empty($users[$activity->authorid])) {
536             $u = new stdclass();
537             $users[$activity->authorid] = username_load_fields_from_object($u, $activity, 'author');
538         }
539         if ($activity->reviewerid and empty($users[$activity->reviewerid])) {
540             $u = new stdclass();
541             $users[$activity->reviewerid] = username_load_fields_from_object($u, $activity, 'reviewer');
542         }
544         $context = context_module::instance($cm->id);
545         $groupmode = groups_get_activity_groupmode($cm, $course);
547         if ($activity->submissionmodified > $timestart and empty($submissions[$activity->submissionid])) {
548             $s = new stdclass();
549             $s->title = $activity->submissiontitle;
550             $s->authorid = $activity->authorid;
551             $s->timemodified = $activity->submissionmodified;
552             $s->cmid = $activity->cmid;
553             if ($activity->authorid == $USER->id || has_capability('mod/workshop:viewauthornames', $context)) {
554                 $s->authornamevisible = true;
555             } else {
556                 $s->authornamevisible = false;
557             }
559             // the following do-while wrapper allows to break from deeply nested if-statements
560             do {
561                 if ($s->authorid === $USER->id) {
562                     // own submissions always visible
563                     $submissions[$activity->submissionid] = $s;
564                     break;
565                 }
567                 if (has_capability('mod/workshop:viewallsubmissions', $context)) {
568                     if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
569                         if (isguestuser()) {
570                             // shortcut - guest user does not belong into any group
571                             break;
572                         }
574                         // this might be slow - show only submissions by users who share group with me in this cm
575                         if (!$modinfo->get_groups($cm->groupingid)) {
576                             break;
577                         }
578                         $authorsgroups = groups_get_all_groups($course->id, $s->authorid, $cm->groupingid);
579                         if (is_array($authorsgroups)) {
580                             $authorsgroups = array_keys($authorsgroups);
581                             $intersect = array_intersect($authorsgroups, $modinfo->get_groups($cm->groupingid));
582                             if (empty($intersect)) {
583                                 break;
584                             } else {
585                                 // can see all submissions and shares a group with the author
586                                 $submissions[$activity->submissionid] = $s;
587                                 break;
588                             }
589                         }
591                     } else {
592                         // can see all submissions from all groups
593                         $submissions[$activity->submissionid] = $s;
594                     }
595                 }
596             } while (0);
597         }
599         if ($activity->assessmentmodified > $timestart and empty($assessments[$activity->assessmentid])) {
600             $a = new stdclass();
601             $a->submissionid = $activity->submissionid;
602             $a->submissiontitle = $activity->submissiontitle;
603             $a->reviewerid = $activity->reviewerid;
604             $a->timemodified = $activity->assessmentmodified;
605             $a->cmid = $activity->cmid;
606             if ($activity->reviewerid == $USER->id || has_capability('mod/workshop:viewreviewernames', $context)) {
607                 $a->reviewernamevisible = true;
608             } else {
609                 $a->reviewernamevisible = false;
610             }
612             // the following do-while wrapper allows to break from deeply nested if-statements
613             do {
614                 if ($a->reviewerid === $USER->id) {
615                     // own assessments always visible
616                     $assessments[$activity->assessmentid] = $a;
617                     break;
618                 }
620                 if (has_capability('mod/workshop:viewallassessments', $context)) {
621                     if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
622                         if (isguestuser()) {
623                             // shortcut - guest user does not belong into any group
624                             break;
625                         }
627                         // this might be slow - show only submissions by users who share group with me in this cm
628                         if (!$modinfo->get_groups($cm->groupingid)) {
629                             break;
630                         }
631                         $reviewersgroups = groups_get_all_groups($course->id, $a->reviewerid, $cm->groupingid);
632                         if (is_array($reviewersgroups)) {
633                             $reviewersgroups = array_keys($reviewersgroups);
634                             $intersect = array_intersect($reviewersgroups, $modinfo->get_groups($cm->groupingid));
635                             if (empty($intersect)) {
636                                 break;
637                             } else {
638                                 // can see all assessments and shares a group with the reviewer
639                                 $assessments[$activity->assessmentid] = $a;
640                                 break;
641                             }
642                         }
644                     } else {
645                         // can see all assessments from all groups
646                         $assessments[$activity->assessmentid] = $a;
647                     }
648                 }
649             } while (0);
650         }
651     }
652     $rs->close();
654     $shown = false;
656     if (!empty($submissions)) {
657         $shown = true;
658         echo $OUTPUT->heading(get_string('recentsubmissions', 'workshop'), 3);
659         foreach ($submissions as $id => $submission) {
660             $link = new moodle_url('/mod/workshop/submission.php', array('id'=>$id, 'cmid'=>$submission->cmid));
661             if ($submission->authornamevisible) {
662                 $author = $users[$submission->authorid];
663             } else {
664                 $author = null;
665             }
666             print_recent_activity_note($submission->timemodified, $author, $submission->title, $link->out(), false, $viewfullnames);
667         }
668     }
670     if (!empty($assessments)) {
671         $shown = true;
672         echo $OUTPUT->heading(get_string('recentassessments', 'workshop'), 3);
673         core_collator::asort_objects_by_property($assessments, 'timemodified');
674         foreach ($assessments as $id => $assessment) {
675             $link = new moodle_url('/mod/workshop/assessment.php', array('asid' => $id));
676             if ($assessment->reviewernamevisible) {
677                 $reviewer = $users[$assessment->reviewerid];
678             } else {
679                 $reviewer = null;
680             }
681             print_recent_activity_note($assessment->timemodified, $reviewer, $assessment->submissiontitle, $link->out(), false, $viewfullnames);
682         }
683     }
685     if ($shown) {
686         return true;
687     }
689     return false;
692 /**
693  * Returns all activity in course workshops since a given time
694  *
695  * @param array $activities sequentially indexed array of objects
696  * @param int $index
697  * @param int $timestart
698  * @param int $courseid
699  * @param int $cmid
700  * @param int $userid defaults to 0
701  * @param int $groupid defaults to 0
702  * @return void adds items into $activities and increases $index
703  */
704 function workshop_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid=0, $groupid=0) {
705     global $CFG, $COURSE, $USER, $DB;
707     if ($COURSE->id == $courseid) {
708         $course = $COURSE;
709     } else {
710         $course = $DB->get_record('course', array('id'=>$courseid));
711     }
713     $modinfo = get_fast_modinfo($course);
715     $cm = $modinfo->cms[$cmid];
717     $params = array();
718     if ($userid) {
719         $userselect = "AND (author.id = :authorid OR reviewer.id = :reviewerid)";
720         $params['authorid'] = $userid;
721         $params['reviewerid'] = $userid;
722     } else {
723         $userselect = "";
724     }
726     if ($groupid) {
727         $groupselect = "AND (authorgroupmembership.groupid = :authorgroupid OR reviewergroupmembership.groupid = :reviewergroupid)";
728         $groupjoin   = "LEFT JOIN {groups_members} authorgroupmembership ON authorgroupmembership.userid = author.id
729                         LEFT JOIN {groups_members} reviewergroupmembership ON reviewergroupmembership.userid = reviewer.id";
730         $params['authorgroupid'] = $groupid;
731         $params['reviewergroupid'] = $groupid;
732     } else {
733         $groupselect = "";
734         $groupjoin   = "";
735     }
737     $params['cminstance'] = $cm->instance;
738     $params['submissionmodified'] = $timestart;
739     $params['assessmentmodified'] = $timestart;
741     $authornamefields = get_all_user_name_fields(true, 'author', null, 'author');
742     $reviewerfields = get_all_user_name_fields(true, 'reviewer', null, 'reviewer');
744     $sql = "SELECT s.id AS submissionid, s.title AS submissiontitle, s.timemodified AS submissionmodified,
745                    author.id AS authorid, $authornamefields, author.picture AS authorpicture, author.imagealt AS authorimagealt,
746                    author.email AS authoremail, a.id AS assessmentid, a.timemodified AS assessmentmodified,
747                    reviewer.id AS reviewerid, $reviewerfields, reviewer.picture AS reviewerpicture,
748                    reviewer.imagealt AS reviewerimagealt, reviewer.email AS revieweremail
749               FROM {workshop_submissions} s
750         INNER JOIN {workshop} w ON s.workshopid = w.id
751         INNER JOIN {user} author ON s.authorid = author.id
752          LEFT JOIN {workshop_assessments} a ON a.submissionid = s.id
753          LEFT JOIN {user} reviewer ON a.reviewerid = reviewer.id
754         $groupjoin
755              WHERE w.id = :cminstance
756                    AND s.example = 0
757                    $userselect $groupselect
758                    AND (s.timemodified > :submissionmodified OR a.timemodified > :assessmentmodified)
759           ORDER BY s.timemodified ASC, a.timemodified ASC";
761     $rs = $DB->get_recordset_sql($sql, $params);
763     $groupmode       = groups_get_activity_groupmode($cm, $course);
764     $context         = context_module::instance($cm->id);
765     $grader          = has_capability('moodle/grade:viewall', $context);
766     $accessallgroups = has_capability('moodle/site:accessallgroups', $context);
767     $viewauthors     = has_capability('mod/workshop:viewauthornames', $context);
768     $viewreviewers   = has_capability('mod/workshop:viewreviewernames', $context);
770     $submissions = array(); // recent submissions indexed by submission id
771     $assessments = array(); // recent assessments indexed by assessment id
772     $users       = array();
774     foreach ($rs as $activity) {
776         // remember all user names we can use later
777         if (empty($users[$activity->authorid])) {
778             $u = new stdclass();
779             $additionalfields = explode(',', user_picture::fields());
780             $u = username_load_fields_from_object($u, $activity, 'author', $additionalfields);
781             $users[$activity->authorid] = $u;
782         }
783         if ($activity->reviewerid and empty($users[$activity->reviewerid])) {
784             $u = new stdclass();
785             $additionalfields = explode(',', user_picture::fields());
786             $u = username_load_fields_from_object($u, $activity, 'reviewer', $additionalfields);
787             $users[$activity->reviewerid] = $u;
788         }
790         if ($activity->submissionmodified > $timestart and empty($submissions[$activity->submissionid])) {
791             $s = new stdclass();
792             $s->id = $activity->submissionid;
793             $s->title = $activity->submissiontitle;
794             $s->authorid = $activity->authorid;
795             $s->timemodified = $activity->submissionmodified;
796             if ($activity->authorid == $USER->id || has_capability('mod/workshop:viewauthornames', $context)) {
797                 $s->authornamevisible = true;
798             } else {
799                 $s->authornamevisible = false;
800             }
802             // the following do-while wrapper allows to break from deeply nested if-statements
803             do {
804                 if ($s->authorid === $USER->id) {
805                     // own submissions always visible
806                     $submissions[$activity->submissionid] = $s;
807                     break;
808                 }
810                 if (has_capability('mod/workshop:viewallsubmissions', $context)) {
811                     if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
812                         if (isguestuser()) {
813                             // shortcut - guest user does not belong into any group
814                             break;
815                         }
817                         // this might be slow - show only submissions by users who share group with me in this cm
818                         if (!$modinfo->get_groups($cm->groupingid)) {
819                             break;
820                         }
821                         $authorsgroups = groups_get_all_groups($course->id, $s->authorid, $cm->groupingid);
822                         if (is_array($authorsgroups)) {
823                             $authorsgroups = array_keys($authorsgroups);
824                             $intersect = array_intersect($authorsgroups, $modinfo->get_groups($cm->groupingid));
825                             if (empty($intersect)) {
826                                 break;
827                             } else {
828                                 // can see all submissions and shares a group with the author
829                                 $submissions[$activity->submissionid] = $s;
830                                 break;
831                             }
832                         }
834                     } else {
835                         // can see all submissions from all groups
836                         $submissions[$activity->submissionid] = $s;
837                     }
838                 }
839             } while (0);
840         }
842         if ($activity->assessmentmodified > $timestart and empty($assessments[$activity->assessmentid])) {
843             $a = new stdclass();
844             $a->id = $activity->assessmentid;
845             $a->submissionid = $activity->submissionid;
846             $a->submissiontitle = $activity->submissiontitle;
847             $a->reviewerid = $activity->reviewerid;
848             $a->timemodified = $activity->assessmentmodified;
849             if ($activity->reviewerid == $USER->id || has_capability('mod/workshop:viewreviewernames', $context)) {
850                 $a->reviewernamevisible = true;
851             } else {
852                 $a->reviewernamevisible = false;
853             }
855             // the following do-while wrapper allows to break from deeply nested if-statements
856             do {
857                 if ($a->reviewerid === $USER->id) {
858                     // own assessments always visible
859                     $assessments[$activity->assessmentid] = $a;
860                     break;
861                 }
863                 if (has_capability('mod/workshop:viewallassessments', $context)) {
864                     if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
865                         if (isguestuser()) {
866                             // shortcut - guest user does not belong into any group
867                             break;
868                         }
870                         // this might be slow - show only submissions by users who share group with me in this cm
871                         if (!$modinfo->get_groups($cm->groupingid)) {
872                             break;
873                         }
874                         $reviewersgroups = groups_get_all_groups($course->id, $a->reviewerid, $cm->groupingid);
875                         if (is_array($reviewersgroups)) {
876                             $reviewersgroups = array_keys($reviewersgroups);
877                             $intersect = array_intersect($reviewersgroups, $modinfo->get_groups($cm->groupingid));
878                             if (empty($intersect)) {
879                                 break;
880                             } else {
881                                 // can see all assessments and shares a group with the reviewer
882                                 $assessments[$activity->assessmentid] = $a;
883                                 break;
884                             }
885                         }
887                     } else {
888                         // can see all assessments from all groups
889                         $assessments[$activity->assessmentid] = $a;
890                     }
891                 }
892             } while (0);
893         }
894     }
895     $rs->close();
897     $workshopname = format_string($cm->name, true);
899     if ($grader) {
900         require_once($CFG->libdir.'/gradelib.php');
901         $grades = grade_get_grades($courseid, 'mod', 'workshop', $cm->instance, array_keys($users));
902     }
904     foreach ($submissions as $submission) {
905         $tmpactivity                = new stdclass();
906         $tmpactivity->type          = 'workshop';
907         $tmpactivity->cmid          = $cm->id;
908         $tmpactivity->name          = $workshopname;
909         $tmpactivity->sectionnum    = $cm->sectionnum;
910         $tmpactivity->timestamp     = $submission->timemodified;
911         $tmpactivity->subtype       = 'submission';
912         $tmpactivity->content       = $submission;
913         if ($grader) {
914             $tmpactivity->grade     = $grades->items[0]->grades[$submission->authorid]->str_long_grade;
915         }
916         if ($submission->authornamevisible and !empty($users[$submission->authorid])) {
917             $tmpactivity->user      = $users[$submission->authorid];
918         }
919         $activities[$index++]       = $tmpactivity;
920     }
922     foreach ($assessments as $assessment) {
923         $tmpactivity                = new stdclass();
924         $tmpactivity->type          = 'workshop';
925         $tmpactivity->cmid          = $cm->id;
926         $tmpactivity->name          = $workshopname;
927         $tmpactivity->sectionnum    = $cm->sectionnum;
928         $tmpactivity->timestamp     = $assessment->timemodified;
929         $tmpactivity->subtype       = 'assessment';
930         $tmpactivity->content       = $assessment;
931         if ($grader) {
932             $tmpactivity->grade     = $grades->items[1]->grades[$assessment->reviewerid]->str_long_grade;
933         }
934         if ($assessment->reviewernamevisible and !empty($users[$assessment->reviewerid])) {
935             $tmpactivity->user      = $users[$assessment->reviewerid];
936         }
937         $activities[$index++]       = $tmpactivity;
938     }
941 /**
942  * Print single activity item prepared by {@see workshop_get_recent_mod_activity()}
943  */
944 function workshop_print_recent_mod_activity($activity, $courseid, $detail, $modnames, $viewfullnames) {
945     global $CFG, $OUTPUT;
947     if (!empty($activity->user)) {
948         echo html_writer::tag('div', $OUTPUT->user_picture($activity->user, array('courseid'=>$courseid)),
949                 array('style' => 'float: left; padding: 7px;'));
950     }
952     if ($activity->subtype == 'submission') {
953         echo html_writer::start_tag('div', array('class'=>'submission', 'style'=>'padding: 7px; float:left;'));
955         if ($detail) {
956             echo html_writer::start_tag('h4', array('class'=>'workshop'));
957             $url = new moodle_url('/mod/workshop/view.php', array('id'=>$activity->cmid));
958             $name = s($activity->name);
959             echo $OUTPUT->image_icon('icon', $name, $activity->type);
960             echo ' ' . $modnames[$activity->type];
961             echo html_writer::link($url, $name, array('class'=>'name', 'style'=>'margin-left: 5px'));
962             echo html_writer::end_tag('h4');
963         }
965         echo html_writer::start_tag('div', array('class'=>'title'));
966         $url = new moodle_url('/mod/workshop/submission.php', array('cmid'=>$activity->cmid, 'id'=>$activity->content->id));
967         $name = s($activity->content->title);
968         echo html_writer::tag('strong', html_writer::link($url, $name));
969         echo html_writer::end_tag('div');
971         if (!empty($activity->user)) {
972             echo html_writer::start_tag('div', array('class'=>'user'));
973             $url = new moodle_url('/user/view.php', array('id'=>$activity->user->id, 'course'=>$courseid));
974             $name = fullname($activity->user);
975             $link = html_writer::link($url, $name);
976             echo get_string('submissionby', 'workshop', $link);
977             echo ' - '.userdate($activity->timestamp);
978             echo html_writer::end_tag('div');
979         } else {
980             echo html_writer::start_tag('div', array('class'=>'anonymous'));
981             echo get_string('submission', 'workshop');
982             echo ' - '.userdate($activity->timestamp);
983             echo html_writer::end_tag('div');
984         }
986         echo html_writer::end_tag('div');
987     }
989     if ($activity->subtype == 'assessment') {
990         echo html_writer::start_tag('div', array('class'=>'assessment', 'style'=>'padding: 7px; float:left;'));
992         if ($detail) {
993             echo html_writer::start_tag('h4', array('class'=>'workshop'));
994             $url = new moodle_url('/mod/workshop/view.php', array('id'=>$activity->cmid));
995             $name = s($activity->name);
996             echo $OUTPUT->image_icon('icon', $name, $activity->type);
997             echo ' ' . $modnames[$activity->type];
998             echo html_writer::link($url, $name, array('class'=>'name', 'style'=>'margin-left: 5px'));
999             echo html_writer::end_tag('h4');
1000         }
1002         echo html_writer::start_tag('div', array('class'=>'title'));
1003         $url = new moodle_url('/mod/workshop/assessment.php', array('asid'=>$activity->content->id));
1004         $name = s($activity->content->submissiontitle);
1005         echo html_writer::tag('em', html_writer::link($url, $name));
1006         echo html_writer::end_tag('div');
1008         if (!empty($activity->user)) {
1009             echo html_writer::start_tag('div', array('class'=>'user'));
1010             $url = new moodle_url('/user/view.php', array('id'=>$activity->user->id, 'course'=>$courseid));
1011             $name = fullname($activity->user);
1012             $link = html_writer::link($url, $name);
1013             echo get_string('assessmentbyfullname', 'workshop', $link);
1014             echo ' - '.userdate($activity->timestamp);
1015             echo html_writer::end_tag('div');
1016         } else {
1017             echo html_writer::start_tag('div', array('class'=>'anonymous'));
1018             echo get_string('assessment', 'workshop');
1019             echo ' - '.userdate($activity->timestamp);
1020             echo html_writer::end_tag('div');
1021         }
1023         echo html_writer::end_tag('div');
1024     }
1026     echo html_writer::empty_tag('br', array('style'=>'clear:both'));
1029 /**
1030  * Regular jobs to execute via cron
1031  *
1032  * @return boolean true on success, false otherwise
1033  */
1034 function workshop_cron() {
1035     global $CFG, $DB;
1037     $now = time();
1039     mtrace(' processing workshop subplugins ...');
1040     cron_execute_plugin_type('workshopallocation', 'workshop allocation methods');
1042     // now when the scheduled allocator had a chance to do its job, check if there
1043     // are some workshops to switch into the assessment phase
1044     $workshops = $DB->get_records_select("workshop",
1045         "phase = 20 AND phaseswitchassessment = 1 AND submissionend > 0 AND submissionend < ?", array($now));
1047     if (!empty($workshops)) {
1048         mtrace('Processing automatic assessment phase switch in '.count($workshops).' workshop(s) ... ', '');
1049         require_once($CFG->dirroot.'/mod/workshop/locallib.php');
1050         foreach ($workshops as $workshop) {
1051             $cm = get_coursemodule_from_instance('workshop', $workshop->id, $workshop->course, false, MUST_EXIST);
1052             $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
1053             $workshop = new workshop($workshop, $cm, $course);
1054             $workshop->switch_phase(workshop::PHASE_ASSESSMENT);
1056             $params = array(
1057                 'objectid' => $workshop->id,
1058                 'context' => $workshop->context,
1059                 'courseid' => $workshop->course->id,
1060                 'other' => array(
1061                     'workshopphase' => $workshop->phase
1062                 )
1063             );
1064             $event = \mod_workshop\event\phase_switched::create($params);
1065             $event->trigger();
1067             // disable the automatic switching now so that it is not executed again by accident
1068             // if the teacher changes the phase back to the submission one
1069             $DB->set_field('workshop', 'phaseswitchassessment', 0, array('id' => $workshop->id));
1071             // todo inform the teachers
1072         }
1073         mtrace('done');
1074     }
1076     return true;
1079 /**
1080  * Is a given scale used by the instance of workshop?
1081  *
1082  * The function asks all installed grading strategy subplugins. The workshop
1083  * core itself does not use scales. Both grade for submission and grade for
1084  * assessments do not use scales.
1085  *
1086  * @param int $workshopid id of workshop instance
1087  * @param int $scaleid id of the scale to check
1088  * @return bool
1089  */
1090 function workshop_scale_used($workshopid, $scaleid) {
1091     global $CFG; // other files included from here
1093     $strategies = core_component::get_plugin_list('workshopform');
1094     foreach ($strategies as $strategy => $strategypath) {
1095         $strategylib = $strategypath . '/lib.php';
1096         if (is_readable($strategylib)) {
1097             require_once($strategylib);
1098         } else {
1099             throw new coding_exception('the grading forms subplugin must contain library ' . $strategylib);
1100         }
1101         $classname = 'workshop_' . $strategy . '_strategy';
1102         if (method_exists($classname, 'scale_used')) {
1103             if (call_user_func_array(array($classname, 'scale_used'), array($scaleid, $workshopid))) {
1104                 // no need to include any other files - scale is used
1105                 return true;
1106             }
1107         }
1108     }
1110     return false;
1113 /**
1114  * Is a given scale used by any instance of workshop?
1115  *
1116  * The function asks all installed grading strategy subplugins. The workshop
1117  * core itself does not use scales. Both grade for submission and grade for
1118  * assessments do not use scales.
1119  *
1120  * @param int $scaleid id of the scale to check
1121  * @return bool
1122  */
1123 function workshop_scale_used_anywhere($scaleid) {
1124     global $CFG; // other files included from here
1126     $strategies = core_component::get_plugin_list('workshopform');
1127     foreach ($strategies as $strategy => $strategypath) {
1128         $strategylib = $strategypath . '/lib.php';
1129         if (is_readable($strategylib)) {
1130             require_once($strategylib);
1131         } else {
1132             throw new coding_exception('the grading forms subplugin must contain library ' . $strategylib);
1133         }
1134         $classname = 'workshop_' . $strategy . '_strategy';
1135         if (method_exists($classname, 'scale_used')) {
1136             if (call_user_func(array($classname, 'scale_used'), $scaleid)) {
1137                 // no need to include any other files - scale is used
1138                 return true;
1139             }
1140         }
1141     }
1143     return false;
1146 /**
1147  * Returns all other caps used in the module
1148  *
1149  * @return array
1150  */
1151 function workshop_get_extra_capabilities() {
1152     return array('moodle/site:accessallgroups');
1155 ////////////////////////////////////////////////////////////////////////////////
1156 // Gradebook API                                                              //
1157 ////////////////////////////////////////////////////////////////////////////////
1159 /**
1160  * Creates or updates grade items for the give workshop instance
1161  *
1162  * Needed by grade_update_mod_grades() in lib/gradelib.php. Also used by
1163  * {@link workshop_update_grades()}.
1164  *
1165  * @param stdClass $workshop instance object with extra cmidnumber property
1166  * @param stdClass $submissiongrades data for the first grade item
1167  * @param stdClass $assessmentgrades data for the second grade item
1168  * @return void
1169  */
1170 function workshop_grade_item_update(stdclass $workshop, $submissiongrades=null, $assessmentgrades=null) {
1171     global $CFG;
1172     require_once($CFG->libdir.'/gradelib.php');
1174     $a = new stdclass();
1175     $a->workshopname = clean_param($workshop->name, PARAM_NOTAGS);
1177     $item = array();
1178     $item['itemname'] = get_string('gradeitemsubmission', 'workshop', $a);
1179     $item['gradetype'] = GRADE_TYPE_VALUE;
1180     $item['grademax']  = $workshop->grade;
1181     $item['grademin']  = 0;
1182     grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 0, $submissiongrades , $item);
1184     $item = array();
1185     $item['itemname'] = get_string('gradeitemassessment', 'workshop', $a);
1186     $item['gradetype'] = GRADE_TYPE_VALUE;
1187     $item['grademax']  = $workshop->gradinggrade;
1188     $item['grademin']  = 0;
1189     grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 1, $assessmentgrades, $item);
1192 /**
1193  * Update workshop grades in the gradebook
1194  *
1195  * Needed by grade_update_mod_grades() in lib/gradelib.php
1196  *
1197  * @category grade
1198  * @param stdClass $workshop instance object with extra cmidnumber and modname property
1199  * @param int $userid        update grade of specific user only, 0 means all participants
1200  * @return void
1201  */
1202 function workshop_update_grades(stdclass $workshop, $userid=0) {
1203     global $CFG, $DB;
1204     require_once($CFG->libdir.'/gradelib.php');
1206     $whereuser = $userid ? ' AND authorid = :userid' : '';
1207     $params = array('workshopid' => $workshop->id, 'userid' => $userid);
1208     $sql = 'SELECT authorid, grade, gradeover, gradeoverby, feedbackauthor, feedbackauthorformat, timemodified, timegraded
1209               FROM {workshop_submissions}
1210              WHERE workshopid = :workshopid AND example=0' . $whereuser;
1211     $records = $DB->get_records_sql($sql, $params);
1212     $submissiongrades = array();
1213     foreach ($records as $record) {
1214         $grade = new stdclass();
1215         $grade->userid = $record->authorid;
1216         if (!is_null($record->gradeover)) {
1217             $grade->rawgrade = grade_floatval($workshop->grade * $record->gradeover / 100);
1218             $grade->usermodified = $record->gradeoverby;
1219         } else {
1220             $grade->rawgrade = grade_floatval($workshop->grade * $record->grade / 100);
1221         }
1222         $grade->feedback = $record->feedbackauthor;
1223         $grade->feedbackformat = $record->feedbackauthorformat;
1224         $grade->datesubmitted = $record->timemodified;
1225         $grade->dategraded = $record->timegraded;
1226         $submissiongrades[$record->authorid] = $grade;
1227     }
1229     $whereuser = $userid ? ' AND userid = :userid' : '';
1230     $params = array('workshopid' => $workshop->id, 'userid' => $userid);
1231     $sql = 'SELECT userid, gradinggrade, timegraded
1232               FROM {workshop_aggregations}
1233              WHERE workshopid = :workshopid' . $whereuser;
1234     $records = $DB->get_records_sql($sql, $params);
1235     $assessmentgrades = array();
1236     foreach ($records as $record) {
1237         $grade = new stdclass();
1238         $grade->userid = $record->userid;
1239         $grade->rawgrade = grade_floatval($workshop->gradinggrade * $record->gradinggrade / 100);
1240         $grade->dategraded = $record->timegraded;
1241         $assessmentgrades[$record->userid] = $grade;
1242     }
1244     workshop_grade_item_update($workshop, $submissiongrades, $assessmentgrades);
1247 /**
1248  * Update the grade items categories if they are changed via mod_form.php
1249  *
1250  * We must do it manually here in the workshop module because modedit supports only
1251  * single grade item while we use two.
1252  *
1253  * @param stdClass $workshop An object from the form in mod_form.php
1254  */
1255 function workshop_grade_item_category_update($workshop) {
1257     $gradeitems = grade_item::fetch_all(array(
1258         'itemtype'      => 'mod',
1259         'itemmodule'    => 'workshop',
1260         'iteminstance'  => $workshop->id,
1261         'courseid'      => $workshop->course));
1263     if (!empty($gradeitems)) {
1264         foreach ($gradeitems as $gradeitem) {
1265             if ($gradeitem->itemnumber == 0) {
1266                 if (isset($workshop->submissiongradepass) &&
1267                         $gradeitem->gradepass != $workshop->submissiongradepass) {
1268                     $gradeitem->gradepass = $workshop->submissiongradepass;
1269                     $gradeitem->update();
1270                 }
1271                 if ($gradeitem->categoryid != $workshop->gradecategory) {
1272                     $gradeitem->set_parent($workshop->gradecategory);
1273                 }
1274             } else if ($gradeitem->itemnumber == 1) {
1275                 if (isset($workshop->gradinggradepass) &&
1276                         $gradeitem->gradepass != $workshop->gradinggradepass) {
1277                     $gradeitem->gradepass = $workshop->gradinggradepass;
1278                     $gradeitem->update();
1279                 }
1280                 if ($gradeitem->categoryid != $workshop->gradinggradecategory) {
1281                     $gradeitem->set_parent($workshop->gradinggradecategory);
1282                 }
1283             }
1284         }
1285     }
1288 ////////////////////////////////////////////////////////////////////////////////
1289 // File API                                                                   //
1290 ////////////////////////////////////////////////////////////////////////////////
1292 /**
1293  * Returns the lists of all browsable file areas within the given module context
1294  *
1295  * The file area workshop_intro for the activity introduction field is added automatically
1296  * by {@link file_browser::get_file_info_context_module()}
1297  *
1298  * @package  mod_workshop
1299  * @category files
1300  *
1301  * @param stdClass $course
1302  * @param stdClass $cm
1303  * @param stdClass $context
1304  * @return array of [(string)filearea] => (string)description
1305  */
1306 function workshop_get_file_areas($course, $cm, $context) {
1307     $areas = array();
1308     $areas['instructauthors']          = get_string('areainstructauthors', 'workshop');
1309     $areas['instructreviewers']        = get_string('areainstructreviewers', 'workshop');
1310     $areas['submission_content']       = get_string('areasubmissioncontent', 'workshop');
1311     $areas['submission_attachment']    = get_string('areasubmissionattachment', 'workshop');
1312     $areas['conclusion']               = get_string('areaconclusion', 'workshop');
1313     $areas['overallfeedback_content']  = get_string('areaoverallfeedbackcontent', 'workshop');
1314     $areas['overallfeedback_attachment'] = get_string('areaoverallfeedbackattachment', 'workshop');
1316     return $areas;
1319 /**
1320  * Serves the files from the workshop file areas
1321  *
1322  * Apart from module intro (handled by pluginfile.php automatically), workshop files may be
1323  * media inserted into submission content (like images) and submission attachments. For these two,
1324  * the fileareas submission_content and submission_attachment are used.
1325  * Besides that, areas instructauthors, instructreviewers and conclusion contain the media
1326  * embedded using the mod_form.php.
1327  *
1328  * @package  mod_workshop
1329  * @category files
1330  *
1331  * @param stdClass $course the course object
1332  * @param stdClass $cm the course module object
1333  * @param stdClass $context the workshop's context
1334  * @param string $filearea the name of the file area
1335  * @param array $args extra arguments (itemid, path)
1336  * @param bool $forcedownload whether or not force download
1337  * @param array $options additional options affecting the file serving
1338  * @return bool false if the file not found, just send the file otherwise and do not return anything
1339  */
1340 function workshop_pluginfile($course, $cm, $context, $filearea, array $args, $forcedownload, array $options=array()) {
1341     global $DB, $CFG, $USER;
1343     if ($context->contextlevel != CONTEXT_MODULE) {
1344         return false;
1345     }
1347     require_login($course, true, $cm);
1349     if ($filearea === 'instructauthors' or $filearea === 'instructreviewers' or $filearea === 'conclusion') {
1350         // The $args are supposed to contain just the path, not the item id.
1351         $relativepath = implode('/', $args);
1352         $fullpath = "/$context->id/mod_workshop/$filearea/0/$relativepath";
1354         $fs = get_file_storage();
1355         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1356             send_file_not_found();
1357         }
1358         send_stored_file($file, null, 0, $forcedownload, $options);
1360     } else if ($filearea === 'submission_content' or $filearea === 'submission_attachment') {
1361         $itemid = (int)array_shift($args);
1362         if (!$workshop = $DB->get_record('workshop', array('id' => $cm->instance))) {
1363             return false;
1364         }
1365         if (!$submission = $DB->get_record('workshop_submissions', array('id' => $itemid, 'workshopid' => $workshop->id))) {
1366             return false;
1367         }
1369         // make sure the user is allowed to see the file
1370         if (empty($submission->example)) {
1371             if ($USER->id != $submission->authorid) {
1372                 if ($submission->published == 1 and $workshop->phase == 50
1373                         and has_capability('mod/workshop:viewpublishedsubmissions', $context)) {
1374                     // Published submission, we can go (workshop does not take the group mode
1375                     // into account in this case yet).
1376                 } else if (!$DB->record_exists('workshop_assessments', array('submissionid' => $submission->id, 'reviewerid' => $USER->id))) {
1377                     if (!has_capability('mod/workshop:viewallsubmissions', $context)) {
1378                         send_file_not_found();
1379                     } else {
1380                         $gmode = groups_get_activity_groupmode($cm, $course);
1381                         if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1382                             // check there is at least one common group with both the $USER
1383                             // and the submission author
1384                             $sql = "SELECT 'x'
1385                                       FROM {workshop_submissions} s
1386                                       JOIN {user} a ON (a.id = s.authorid)
1387                                       JOIN {groups_members} agm ON (a.id = agm.userid)
1388                                       JOIN {user} u ON (u.id = ?)
1389                                       JOIN {groups_members} ugm ON (u.id = ugm.userid)
1390                                      WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1391                             $params = array($USER->id, $workshop->id, $submission->id);
1392                             if (!$DB->record_exists_sql($sql, $params)) {
1393                                 send_file_not_found();
1394                             }
1395                         }
1396                     }
1397                 }
1398             }
1399         }
1401         $fs = get_file_storage();
1402         $relativepath = implode('/', $args);
1403         $fullpath = "/$context->id/mod_workshop/$filearea/$itemid/$relativepath";
1404         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1405             return false;
1406         }
1407         // finally send the file
1408         // these files are uploaded by students - forcing download for security reasons
1409         send_stored_file($file, 0, 0, true, $options);
1411     } else if ($filearea === 'overallfeedback_content' or $filearea === 'overallfeedback_attachment') {
1412         $itemid = (int)array_shift($args);
1413         if (!$workshop = $DB->get_record('workshop', array('id' => $cm->instance))) {
1414             return false;
1415         }
1416         if (!$assessment = $DB->get_record('workshop_assessments', array('id' => $itemid))) {
1417             return false;
1418         }
1419         if (!$submission = $DB->get_record('workshop_submissions', array('id' => $assessment->submissionid, 'workshopid' => $workshop->id))) {
1420             return false;
1421         }
1423         if ($USER->id == $assessment->reviewerid) {
1424             // Reviewers can always see their own files.
1425         } else if ($USER->id == $submission->authorid and $workshop->phase == 50) {
1426             // Authors can see the feedback once the workshop is closed.
1427         } else if (!empty($submission->example) and $assessment->weight == 1) {
1428             // Reference assessments of example submissions can be displayed.
1429         } else if (!has_capability('mod/workshop:viewallassessments', $context)) {
1430             send_file_not_found();
1431         } else {
1432             $gmode = groups_get_activity_groupmode($cm, $course);
1433             if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1434                 // Check there is at least one common group with both the $USER
1435                 // and the submission author.
1436                 $sql = "SELECT 'x'
1437                           FROM {workshop_submissions} s
1438                           JOIN {user} a ON (a.id = s.authorid)
1439                           JOIN {groups_members} agm ON (a.id = agm.userid)
1440                           JOIN {user} u ON (u.id = ?)
1441                           JOIN {groups_members} ugm ON (u.id = ugm.userid)
1442                          WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1443                 $params = array($USER->id, $workshop->id, $submission->id);
1444                 if (!$DB->record_exists_sql($sql, $params)) {
1445                     send_file_not_found();
1446                 }
1447             }
1448         }
1450         $fs = get_file_storage();
1451         $relativepath = implode('/', $args);
1452         $fullpath = "/$context->id/mod_workshop/$filearea/$itemid/$relativepath";
1453         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1454             return false;
1455         }
1456         // finally send the file
1457         // these files are uploaded by students - forcing download for security reasons
1458         send_stored_file($file, 0, 0, true, $options);
1459     }
1461     return false;
1464 /**
1465  * File browsing support for workshop file areas
1466  *
1467  * @package  mod_workshop
1468  * @category files
1469  *
1470  * @param file_browser $browser
1471  * @param array $areas
1472  * @param stdClass $course
1473  * @param stdClass $cm
1474  * @param stdClass $context
1475  * @param string $filearea
1476  * @param int $itemid
1477  * @param string $filepath
1478  * @param string $filename
1479  * @return file_info instance or null if not found
1480  */
1481 function workshop_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
1482     global $CFG, $DB, $USER;
1484     /** @var array internal cache for author names */
1485     static $submissionauthors = array();
1487     $fs = get_file_storage();
1489     if ($filearea === 'submission_content' or $filearea === 'submission_attachment') {
1491         if (!has_capability('mod/workshop:viewallsubmissions', $context)) {
1492             return null;
1493         }
1495         if (is_null($itemid)) {
1496             // no itemid (submissionid) passed, display the list of all submissions
1497             require_once($CFG->dirroot . '/mod/workshop/fileinfolib.php');
1498             return new workshop_file_info_submissions_container($browser, $course, $cm, $context, $areas, $filearea);
1499         }
1501         // make sure the user can see the particular submission in separate groups mode
1502         $gmode = groups_get_activity_groupmode($cm, $course);
1504         if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1505             // check there is at least one common group with both the $USER
1506             // and the submission author (this is not expected to be a frequent
1507             // usecase so we can live with pretty ineffective one query per submission here...)
1508             $sql = "SELECT 'x'
1509                       FROM {workshop_submissions} s
1510                       JOIN {user} a ON (a.id = s.authorid)
1511                       JOIN {groups_members} agm ON (a.id = agm.userid)
1512                       JOIN {user} u ON (u.id = ?)
1513                       JOIN {groups_members} ugm ON (u.id = ugm.userid)
1514                      WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1515             $params = array($USER->id, $cm->instance, $itemid);
1516             if (!$DB->record_exists_sql($sql, $params)) {
1517                 return null;
1518             }
1519         }
1521         // we are inside some particular submission container
1523         $filepath = is_null($filepath) ? '/' : $filepath;
1524         $filename = is_null($filename) ? '.' : $filename;
1526         if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, $itemid, $filepath, $filename)) {
1527             if ($filepath === '/' and $filename === '.') {
1528                 $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, $itemid);
1529             } else {
1530                 // not found
1531                 return null;
1532             }
1533         }
1535         // Checks to see if the user can manage files or is the owner.
1536         // TODO MDL-33805 - Do not use userid here and move the capability check above.
1537         if (!has_capability('moodle/course:managefiles', $context) && $storedfile->get_userid() != $USER->id) {
1538             return null;
1539         }
1541         // let us display the author's name instead of itemid (submission id)
1543         if (isset($submissionauthors[$itemid])) {
1544             $topvisiblename = $submissionauthors[$itemid];
1546         } else {
1548             $sql = "SELECT s.id, u.lastname, u.firstname
1549                       FROM {workshop_submissions} s
1550                       JOIN {user} u ON (s.authorid = u.id)
1551                      WHERE s.example = 0 AND s.workshopid = ?";
1552             $params = array($cm->instance);
1553             $rs = $DB->get_recordset_sql($sql, $params);
1555             foreach ($rs as $submissionauthor) {
1556                 $title = s(fullname($submissionauthor)); // this is generally not unique...
1557                 $submissionauthors[$submissionauthor->id] = $title;
1558             }
1559             $rs->close();
1561             if (!isset($submissionauthors[$itemid])) {
1562                 // should not happen
1563                 return null;
1564             } else {
1565                 $topvisiblename = $submissionauthors[$itemid];
1566             }
1567         }
1569         $urlbase = $CFG->wwwroot . '/pluginfile.php';
1570         // do not allow manual modification of any files!
1571         return new file_info_stored($browser, $context, $storedfile, $urlbase, $topvisiblename, true, true, false, false);
1572     }
1574     if ($filearea === 'overallfeedback_content' or $filearea === 'overallfeedback_attachment') {
1576         if (!has_capability('mod/workshop:viewallassessments', $context)) {
1577             return null;
1578         }
1580         if (is_null($itemid)) {
1581             // No itemid (assessmentid) passed, display the list of all assessments.
1582             require_once($CFG->dirroot . '/mod/workshop/fileinfolib.php');
1583             return new workshop_file_info_overallfeedback_container($browser, $course, $cm, $context, $areas, $filearea);
1584         }
1586         // Make sure the user can see the particular assessment in separate groups mode.
1587         $gmode = groups_get_activity_groupmode($cm, $course);
1588         if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1589             // Check there is at least one common group with both the $USER
1590             // and the submission author.
1591             $sql = "SELECT 'x'
1592                       FROM {workshop_submissions} s
1593                       JOIN {user} a ON (a.id = s.authorid)
1594                       JOIN {groups_members} agm ON (a.id = agm.userid)
1595                       JOIN {user} u ON (u.id = ?)
1596                       JOIN {groups_members} ugm ON (u.id = ugm.userid)
1597                      WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1598             $params = array($USER->id, $cm->instance, $itemid);
1599             if (!$DB->record_exists_sql($sql, $params)) {
1600                 return null;
1601             }
1602         }
1604         // We are inside a particular assessment container.
1605         $filepath = is_null($filepath) ? '/' : $filepath;
1606         $filename = is_null($filename) ? '.' : $filename;
1608         if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, $itemid, $filepath, $filename)) {
1609             if ($filepath === '/' and $filename === '.') {
1610                 $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, $itemid);
1611             } else {
1612                 // Not found
1613                 return null;
1614             }
1615         }
1617         // Check to see if the user can manage files or is the owner.
1618         if (!has_capability('moodle/course:managefiles', $context) and $storedfile->get_userid() != $USER->id) {
1619             return null;
1620         }
1622         $urlbase = $CFG->wwwroot . '/pluginfile.php';
1624         // Do not allow manual modification of any files.
1625         return new file_info_stored($browser, $context, $storedfile, $urlbase, $itemid, true, true, false, false);
1626     }
1628     if ($filearea == 'instructauthors' or $filearea == 'instructreviewers' or $filearea == 'conclusion') {
1629         // always only itemid 0
1631         $filepath = is_null($filepath) ? '/' : $filepath;
1632         $filename = is_null($filename) ? '.' : $filename;
1634         $urlbase = $CFG->wwwroot.'/pluginfile.php';
1635         if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, 0, $filepath, $filename)) {
1636             if ($filepath === '/' and $filename === '.') {
1637                 $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, 0);
1638             } else {
1639                 // not found
1640                 return null;
1641             }
1642         }
1643         return new file_info_stored($browser, $context, $storedfile, $urlbase, $areas[$filearea], false, true, true, false);
1644     }
1647 ////////////////////////////////////////////////////////////////////////////////
1648 // Navigation API                                                             //
1649 ////////////////////////////////////////////////////////////////////////////////
1651 /**
1652  * Extends the global navigation tree by adding workshop nodes if there is a relevant content
1653  *
1654  * This can be called by an AJAX request so do not rely on $PAGE as it might not be set up properly.
1655  *
1656  * @param navigation_node $navref An object representing the navigation tree node of the workshop module instance
1657  * @param stdClass $course
1658  * @param stdClass $module
1659  * @param cm_info $cm
1660  */
1661 function workshop_extend_navigation(navigation_node $navref, stdclass $course, stdclass $module, cm_info $cm) {
1662     global $CFG;
1664     if (has_capability('mod/workshop:submit', context_module::instance($cm->id))) {
1665         $url = new moodle_url('/mod/workshop/submission.php', array('cmid' => $cm->id));
1666         $mysubmission = $navref->add(get_string('mysubmission', 'workshop'), $url);
1667         $mysubmission->mainnavonly = true;
1668     }
1671 /**
1672  * Extends the settings navigation with the Workshop settings
1674  * This function is called when the context for the page is a workshop module. This is not called by AJAX
1675  * so it is safe to rely on the $PAGE.
1676  *
1677  * @param settings_navigation $settingsnav {@link settings_navigation}
1678  * @param navigation_node $workshopnode {@link navigation_node}
1679  */
1680 function workshop_extend_settings_navigation(settings_navigation $settingsnav, navigation_node $workshopnode=null) {
1681     global $PAGE;
1683     //$workshopobject = $DB->get_record("workshop", array("id" => $PAGE->cm->instance));
1685     if (has_capability('mod/workshop:editdimensions', $PAGE->cm->context)) {
1686         $url = new moodle_url('/mod/workshop/editform.php', array('cmid' => $PAGE->cm->id));
1687         $workshopnode->add(get_string('editassessmentform', 'workshop'), $url, settings_navigation::TYPE_SETTING);
1688     }
1689     if (has_capability('mod/workshop:allocate', $PAGE->cm->context)) {
1690         $url = new moodle_url('/mod/workshop/allocation.php', array('cmid' => $PAGE->cm->id));
1691         $workshopnode->add(get_string('allocate', 'workshop'), $url, settings_navigation::TYPE_SETTING);
1692     }
1695 /**
1696  * Return a list of page types
1697  * @param string $pagetype current page type
1698  * @param stdClass $parentcontext Block's parent context
1699  * @param stdClass $currentcontext Current context of block
1700  */
1701 function workshop_page_type_list($pagetype, $parentcontext, $currentcontext) {
1702     $module_pagetype = array('mod-workshop-*'=>get_string('page-mod-workshop-x', 'workshop'));
1703     return $module_pagetype;
1706 ////////////////////////////////////////////////////////////////////////////////
1707 // Calendar API                                                               //
1708 ////////////////////////////////////////////////////////////////////////////////
1710 /**
1711  * Updates the calendar events associated to the given workshop
1712  *
1713  * @param stdClass $workshop the workshop instance record
1714  * @param int $cmid course module id
1715  */
1716 function workshop_calendar_update(stdClass $workshop, $cmid) {
1717     global $DB;
1719     // get the currently registered events so that we can re-use their ids
1720     $currentevents = $DB->get_records('event', array('modulename' => 'workshop', 'instance' => $workshop->id));
1722     // the common properties for all events
1723     $base = new stdClass();
1724     $base->description  = format_module_intro('workshop', $workshop, $cmid, false);
1725     $base->courseid     = $workshop->course;
1726     $base->groupid      = 0;
1727     $base->userid       = 0;
1728     $base->modulename   = 'workshop';
1729     $base->instance     = $workshop->id;
1730     $base->visible      = instance_is_visible('workshop', $workshop);
1731     $base->timeduration = 0;
1733     if ($workshop->submissionstart) {
1734         $event = clone($base);
1735         $event->name = get_string('submissionstartevent', 'mod_workshop', $workshop->name);
1736         $event->eventtype = WORKSHOP_EVENT_TYPE_SUBMISSION_OPEN;
1737         $event->type = empty($workshop->submissionend) ? CALENDAR_EVENT_TYPE_ACTION : CALENDAR_EVENT_TYPE_STANDARD;
1738         $event->timestart = $workshop->submissionstart;
1739         $event->timesort  = $workshop->submissionstart;
1740         if ($reusedevent = array_shift($currentevents)) {
1741             $event->id = $reusedevent->id;
1742         } else {
1743             // should not be set but just in case
1744             unset($event->id);
1745         }
1746         // update() will reuse a db record if the id field is set
1747         $eventobj = new calendar_event($event);
1748         $eventobj->update($event, false);
1749     }
1751     if ($workshop->submissionend) {
1752         $event = clone($base);
1753         $event->name = get_string('submissionendevent', 'mod_workshop', $workshop->name);
1754         $event->eventtype = WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE;
1755         $event->type      = CALENDAR_EVENT_TYPE_ACTION;
1756         $event->timestart = $workshop->submissionend;
1757         $event->timesort  = $workshop->submissionend;
1758         if ($reusedevent = array_shift($currentevents)) {
1759             $event->id = $reusedevent->id;
1760         } else {
1761             // should not be set but just in case
1762             unset($event->id);
1763         }
1764         // update() will reuse a db record if the id field is set
1765         $eventobj = new calendar_event($event);
1766         $eventobj->update($event, false);
1767     }
1769     if ($workshop->assessmentstart) {
1770         $event = clone($base);
1771         $event->name = get_string('assessmentstartevent', 'mod_workshop', $workshop->name);
1772         $event->eventtype = WORKSHOP_EVENT_TYPE_ASSESSMENT_OPEN;
1773         $event->type      = empty($workshop->assessmentend) ? CALENDAR_EVENT_TYPE_ACTION : CALENDAR_EVENT_TYPE_STANDARD;
1774         $event->timestart = $workshop->assessmentstart;
1775         $event->timesort  = $workshop->assessmentstart;
1776         if ($reusedevent = array_shift($currentevents)) {
1777             $event->id = $reusedevent->id;
1778         } else {
1779             // should not be set but just in case
1780             unset($event->id);
1781         }
1782         // update() will reuse a db record if the id field is set
1783         $eventobj = new calendar_event($event);
1784         $eventobj->update($event, false);
1785     }
1787     if ($workshop->assessmentend) {
1788         $event = clone($base);
1789         $event->name = get_string('assessmentendevent', 'mod_workshop', $workshop->name);
1790         $event->eventtype = WORKSHOP_EVENT_TYPE_ASSESSMENT_CLOSE;
1791         $event->type      = CALENDAR_EVENT_TYPE_ACTION;
1792         $event->timestart = $workshop->assessmentend;
1793         $event->timesort  = $workshop->assessmentend;
1794         if ($reusedevent = array_shift($currentevents)) {
1795             $event->id = $reusedevent->id;
1796         } else {
1797             // should not be set but just in case
1798             unset($event->id);
1799         }
1800         // update() will reuse a db record if the id field is set
1801         $eventobj = new calendar_event($event);
1802         $eventobj->update($event, false);
1803     }
1805     // delete any leftover events
1806     foreach ($currentevents as $oldevent) {
1807         $oldevent = calendar_event::load($oldevent);
1808         $oldevent->delete();
1809     }
1812 /**
1813  * This function receives a calendar event and returns the action associated with it, or null if there is none.
1814  *
1815  * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event
1816  * is not displayed on the block.
1817  *
1818  * @param calendar_event $event
1819  * @param \core_calendar\action_factory $factory
1820  * @return \core_calendar\local\event\entities\action_interface|null
1821  */
1822 function mod_workshop_core_calendar_provide_event_action(calendar_event $event,
1823                                                          \core_calendar\action_factory $factory) {
1825     $cm = get_fast_modinfo($event->courseid)->instances['workshop'][$event->instance];
1827     return $factory->create_instance(
1828         get_string('viewworkshopsummary', 'workshop'),
1829         new \moodle_url('/mod/workshop/view.php', array('id' => $cm->id)),
1830         1,
1831         true
1832     );
1835 ////////////////////////////////////////////////////////////////////////////////
1836 // Course reset API                                                           //
1837 ////////////////////////////////////////////////////////////////////////////////
1839 /**
1840  * Extends the course reset form with workshop specific settings.
1841  *
1842  * @param MoodleQuickForm $mform
1843  */
1844 function workshop_reset_course_form_definition($mform) {
1846     $mform->addElement('header', 'workshopheader', get_string('modulenameplural', 'mod_workshop'));
1848     $mform->addElement('advcheckbox', 'reset_workshop_submissions', get_string('resetsubmissions', 'mod_workshop'));
1849     $mform->addHelpButton('reset_workshop_submissions', 'resetsubmissions', 'mod_workshop');
1851     $mform->addElement('advcheckbox', 'reset_workshop_assessments', get_string('resetassessments', 'mod_workshop'));
1852     $mform->addHelpButton('reset_workshop_assessments', 'resetassessments', 'mod_workshop');
1853     $mform->disabledIf('reset_workshop_assessments', 'reset_workshop_submissions', 'checked');
1855     $mform->addElement('advcheckbox', 'reset_workshop_phase', get_string('resetphase', 'mod_workshop'));
1856     $mform->addHelpButton('reset_workshop_phase', 'resetphase', 'mod_workshop');
1859 /**
1860  * Provides default values for the workshop settings in the course reset form.
1861  *
1862  * @param stdClass $course The course to be reset.
1863  */
1864 function workshop_reset_course_form_defaults(stdClass $course) {
1866     $defaults = array(
1867         'reset_workshop_submissions'    => 1,
1868         'reset_workshop_assessments'    => 1,
1869         'reset_workshop_phase'          => 1,
1870     );
1872     return $defaults;
1875 /**
1876  * Performs the reset of all workshop instances in the course.
1877  *
1878  * @param stdClass $data The actual course reset settings.
1879  * @return array List of results, each being array[(string)component, (string)item, (string)error]
1880  */
1881 function workshop_reset_userdata(stdClass $data) {
1882     global $CFG, $DB;
1884     if (empty($data->reset_workshop_submissions)
1885             and empty($data->reset_workshop_assessments)
1886             and empty($data->reset_workshop_phase) ) {
1887         // Nothing to do here.
1888         return array();
1889     }
1891     $workshoprecords = $DB->get_records('workshop', array('course' => $data->courseid));
1893     if (empty($workshoprecords)) {
1894         // What a boring course - no workshops here!
1895         return array();
1896     }
1898     require_once($CFG->dirroot . '/mod/workshop/locallib.php');
1900     $course = $DB->get_record('course', array('id' => $data->courseid), '*', MUST_EXIST);
1901     $status = array();
1903     foreach ($workshoprecords as $workshoprecord) {
1904         $cm = get_coursemodule_from_instance('workshop', $workshoprecord->id, $course->id, false, MUST_EXIST);
1905         $workshop = new workshop($workshoprecord, $cm, $course);
1906         $status = array_merge($status, $workshop->reset_userdata($data));
1907     }
1909     return $status;
1912 /**
1913  * Get icon mapping for font-awesome.
1914  */
1915 function mod_workshop_get_fontawesome_icon_map() {
1916     return [
1917         'mod_workshop:userplan/task-info' => 'fa-info text-info',
1918         'mod_workshop:userplan/task-todo' => 'fa-square-o',
1919         'mod_workshop:userplan/task-done' => 'fa-check text-success',
1920         'mod_workshop:userplan/task-fail' => 'fa-remove text-danger',
1921     ];