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