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