1<?php
2
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/>.
17
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 */
28
29defined('MOODLE_INTERNAL') || die();
30
31require_once($CFG->dirroot . '/calendar/lib.php');
32
33define('WORKSHOP_EVENT_TYPE_SUBMISSION_OPEN',   'opensubmission');
34define('WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE',  'closesubmission');
35define('WORKSHOP_EVENT_TYPE_ASSESSMENT_OPEN',   'openassessment');
36define('WORKSHOP_EVENT_TYPE_ASSESSMENT_CLOSE',  'closeassessment');
37define('WORKSHOP_SUBMISSION_TYPE_DISABLED', 0);
38define('WORKSHOP_SUBMISSION_TYPE_AVAILABLE', 1);
39define('WORKSHOP_SUBMISSION_TYPE_REQUIRED', 2);
40
41////////////////////////////////////////////////////////////////////////////////
42// Moodle core API                                                            //
43////////////////////////////////////////////////////////////////////////////////
44
45/**
46 * Returns the information if the module supports a feature
47 *
48 * @see plugin_supports() in lib/moodlelib.php
49 * @param string $feature FEATURE_xx constant for requested feature
50 * @return mixed true if the feature is supported, null if unknown
51 */
52function workshop_supports($feature) {
53    switch($feature) {
54        case FEATURE_GRADE_HAS_GRADE:   return true;
55        case FEATURE_GROUPS:            return true;
56        case FEATURE_GROUPINGS:         return true;
57        case FEATURE_MOD_INTRO:         return true;
58        case FEATURE_BACKUP_MOODLE2:    return true;
59        case FEATURE_COMPLETION_TRACKS_VIEWS:
60            return true;
61        case FEATURE_SHOW_DESCRIPTION:  return true;
62        case FEATURE_PLAGIARISM:        return true;
63        default:                        return null;
64    }
65}
66
67/**
68 * Saves a new instance of the workshop into the database
69 *
70 * Given an object containing all the necessary data,
71 * (defined by the form in mod_form.php) this function
72 * will save a new instance and return the id number
73 * of the new instance.
74 *
75 * @param stdClass $workshop An object from the form in mod_form.php
76 * @return int The id of the newly inserted workshop record
77 */
78function workshop_add_instance(stdclass $workshop) {
79    global $CFG, $DB;
80    require_once(__DIR__ . '/locallib.php');
81
82    $workshop->phase                 = workshop::PHASE_SETUP;
83    $workshop->timecreated           = time();
84    $workshop->timemodified          = $workshop->timecreated;
85    $workshop->useexamples           = (int)!empty($workshop->useexamples);
86    $workshop->usepeerassessment     = 1;
87    $workshop->useselfassessment     = (int)!empty($workshop->useselfassessment);
88    $workshop->latesubmissions       = (int)!empty($workshop->latesubmissions);
89    $workshop->phaseswitchassessment = (int)!empty($workshop->phaseswitchassessment);
90    $workshop->evaluation            = 'best';
91
92    if (isset($workshop->gradinggradepass)) {
93        $workshop->gradinggradepass = (float)unformat_float($workshop->gradinggradepass);
94    }
95
96    if (isset($workshop->submissiongradepass)) {
97        $workshop->submissiongradepass = (float)unformat_float($workshop->submissiongradepass);
98    }
99
100    if (isset($workshop->submissionfiletypes)) {
101        $filetypesutil = new \core_form\filetypes_util();
102        $submissionfiletypes = $filetypesutil->normalize_file_types($workshop->submissionfiletypes);
103        $workshop->submissionfiletypes = implode(' ', $submissionfiletypes);
104    }
105
106    if (isset($workshop->overallfeedbackfiletypes)) {
107        $filetypesutil = new \core_form\filetypes_util();
108        $overallfeedbackfiletypes = $filetypesutil->normalize_file_types($workshop->overallfeedbackfiletypes);
109        $workshop->overallfeedbackfiletypes = implode(' ', $overallfeedbackfiletypes);
110    }
111
112    // insert the new record so we get the id
113    $workshop->id = $DB->insert_record('workshop', $workshop);
114
115    // we need to use context now, so we need to make sure all needed info is already in db
116    $cmid = $workshop->coursemodule;
117    $DB->set_field('course_modules', 'instance', $workshop->id, array('id' => $cmid));
118    $context = context_module::instance($cmid);
119
120    // process the custom wysiwyg editors
121    if ($draftitemid = $workshop->instructauthorseditor['itemid']) {
122        $workshop->instructauthors = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructauthors',
123                0, workshop::instruction_editors_options($context), $workshop->instructauthorseditor['text']);
124        $workshop->instructauthorsformat = $workshop->instructauthorseditor['format'];
125    }
126
127    if ($draftitemid = $workshop->instructreviewerseditor['itemid']) {
128        $workshop->instructreviewers = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructreviewers',
129                0, workshop::instruction_editors_options($context), $workshop->instructreviewerseditor['text']);
130        $workshop->instructreviewersformat = $workshop->instructreviewerseditor['format'];
131    }
132
133    if ($draftitemid = $workshop->conclusioneditor['itemid']) {
134        $workshop->conclusion = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'conclusion',
135                0, workshop::instruction_editors_options($context), $workshop->conclusioneditor['text']);
136        $workshop->conclusionformat = $workshop->conclusioneditor['format'];
137    }
138
139    // re-save the record with the replaced URLs in editor fields
140    $DB->update_record('workshop', $workshop);
141
142    // create gradebook items
143    workshop_grade_item_update($workshop);
144    workshop_grade_item_category_update($workshop);
145
146    // create calendar events
147    workshop_calendar_update($workshop, $workshop->coursemodule);
148    if (!empty($workshop->completionexpected)) {
149        \core_completion\api::update_completion_date_event($cmid, 'workshop', $workshop->id, $workshop->completionexpected);
150    }
151
152    return $workshop->id;
153}
154
155/**
156 * Given an object containing all the necessary data,
157 * (defined by the form in mod_form.php) this function
158 * will update an existing instance with new data.
159 *
160 * @param stdClass $workshop An object from the form in mod_form.php
161 * @return bool success
162 */
163function workshop_update_instance(stdclass $workshop) {
164    global $CFG, $DB;
165    require_once(__DIR__ . '/locallib.php');
166
167    $workshop->timemodified          = time();
168    $workshop->id                    = $workshop->instance;
169    $workshop->useexamples           = (int)!empty($workshop->useexamples);
170    $workshop->usepeerassessment     = 1;
171    $workshop->useselfassessment     = (int)!empty($workshop->useselfassessment);
172    $workshop->latesubmissions       = (int)!empty($workshop->latesubmissions);
173    $workshop->phaseswitchassessment = (int)!empty($workshop->phaseswitchassessment);
174
175    if (isset($workshop->gradinggradepass)) {
176        $workshop->gradinggradepass = (float)unformat_float($workshop->gradinggradepass);
177    }
178
179    if (isset($workshop->submissiongradepass)) {
180        $workshop->submissiongradepass = (float)unformat_float($workshop->submissiongradepass);
181    }
182
183    if (isset($workshop->submissionfiletypes)) {
184        $filetypesutil = new \core_form\filetypes_util();
185        $submissionfiletypes = $filetypesutil->normalize_file_types($workshop->submissionfiletypes);
186        $workshop->submissionfiletypes = implode(' ', $submissionfiletypes);
187    }
188
189    if (isset($workshop->overallfeedbackfiletypes)) {
190        $filetypesutil = new \core_form\filetypes_util();
191        $overallfeedbackfiletypes = $filetypesutil->normalize_file_types($workshop->overallfeedbackfiletypes);
192        $workshop->overallfeedbackfiletypes = implode(' ', $overallfeedbackfiletypes);
193    }
194
195    // todo - if the grading strategy is being changed, we may want to replace all aggregated peer grades with nulls
196
197    $DB->update_record('workshop', $workshop);
198    $context = context_module::instance($workshop->coursemodule);
199
200    // process the custom wysiwyg editors
201    if ($draftitemid = $workshop->instructauthorseditor['itemid']) {
202        $workshop->instructauthors = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructauthors',
203                0, workshop::instruction_editors_options($context), $workshop->instructauthorseditor['text']);
204        $workshop->instructauthorsformat = $workshop->instructauthorseditor['format'];
205    }
206
207    if ($draftitemid = $workshop->instructreviewerseditor['itemid']) {
208        $workshop->instructreviewers = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructreviewers',
209                0, workshop::instruction_editors_options($context), $workshop->instructreviewerseditor['text']);
210        $workshop->instructreviewersformat = $workshop->instructreviewerseditor['format'];
211    }
212
213    if ($draftitemid = $workshop->conclusioneditor['itemid']) {
214        $workshop->conclusion = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'conclusion',
215                0, workshop::instruction_editors_options($context), $workshop->conclusioneditor['text']);
216        $workshop->conclusionformat = $workshop->conclusioneditor['format'];
217    }
218
219    // re-save the record with the replaced URLs in editor fields
220    $DB->update_record('workshop', $workshop);
221
222    // update gradebook items
223    workshop_grade_item_update($workshop);
224    workshop_grade_item_category_update($workshop);
225
226    // update calendar events
227    workshop_calendar_update($workshop, $workshop->coursemodule);
228    $completionexpected = (!empty($workshop->completionexpected)) ? $workshop->completionexpected : null;
229    \core_completion\api::update_completion_date_event($workshop->coursemodule, 'workshop', $workshop->id, $completionexpected);
230
231    return true;
232}
233
234/**
235 * Given an ID of an instance of this module,
236 * this function will permanently delete the instance
237 * and any data that depends on it.
238 *
239 * @param int $id Id of the module instance
240 * @return boolean Success/Failure
241 */
242function workshop_delete_instance($id) {
243    global $CFG, $DB;
244    require_once($CFG->libdir.'/gradelib.php');
245
246    if (! $workshop = $DB->get_record('workshop', array('id' => $id))) {
247        return false;
248    }
249
250    // delete all associated aggregations
251    $DB->delete_records('workshop_aggregations', array('workshopid' => $workshop->id));
252
253    // get the list of ids of all submissions
254    $submissions = $DB->get_records('workshop_submissions', array('workshopid' => $workshop->id), '', 'id');
255
256    // get the list of all allocated assessments
257    $assessments = $DB->get_records_list('workshop_assessments', 'submissionid', array_keys($submissions), '', 'id');
258
259    // delete the associated records from the workshop core tables
260    $DB->delete_records_list('workshop_grades', 'assessmentid', array_keys($assessments));
261    $DB->delete_records_list('workshop_assessments', 'id', array_keys($assessments));
262    $DB->delete_records_list('workshop_submissions', 'id', array_keys($submissions));
263
264    // call the static clean-up methods of all available subplugins
265    $strategies = core_component::get_plugin_list('workshopform');
266    foreach ($strategies as $strategy => $path) {
267        require_once($path.'/lib.php');
268        $classname = 'workshop_'.$strategy.'_strategy';
269        call_user_func($classname.'::delete_instance', $workshop->id);
270    }
271
272    $allocators = core_component::get_plugin_list('workshopallocation');
273    foreach ($allocators as $allocator => $path) {
274        require_once($path.'/lib.php');
275        $classname = 'workshop_'.$allocator.'_allocator';
276        call_user_func($classname.'::delete_instance', $workshop->id);
277    }
278
279    $evaluators = core_component::get_plugin_list('workshopeval');
280    foreach ($evaluators as $evaluator => $path) {
281        require_once($path.'/lib.php');
282        $classname = 'workshop_'.$evaluator.'_evaluation';
283        call_user_func($classname.'::delete_instance', $workshop->id);
284    }
285
286    // delete the calendar events
287    $events = $DB->get_records('event', array('modulename' => 'workshop', 'instance' => $workshop->id));
288    foreach ($events as $event) {
289        $event = calendar_event::load($event);
290        $event->delete();
291    }
292
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));
296
297    // finally remove the workshop record itself
298    // We must delete the module record after we delete the grade item.
299    $DB->delete_records('workshop', array('id' => $workshop->id));
300
301    return true;
302}
303
304/**
305 * This standard function will check all instances of this module
306 * and make sure there are up-to-date events created for each of them.
307 * If courseid = 0, then every workshop event in the site is checked, else
308 * only workshop events belonging to the course specified are checked.
309 *
310 * @param  integer $courseid The Course ID.
311 * @param int|stdClass $instance workshop module instance or ID.
312 * @param int|stdClass $cm Course module object or ID.
313 * @return bool Returns true if the calendar events were successfully updated.
314 */
315function workshop_refresh_events($courseid = 0, $instance = null, $cm = null) {
316    global $DB;
317
318    // If we have instance information then we can just update the one event instead of updating all events.
319    if (isset($instance)) {
320        if (!is_object($instance)) {
321            $instance = $DB->get_record('workshop', array('id' => $instance), '*', MUST_EXIST);
322        }
323        if (isset($cm)) {
324            if (!is_object($cm)) {
325                $cm = (object)array('id' => $cm);
326            }
327        } else {
328            $cm = get_coursemodule_from_instance('workshop', $instance->id);
329        }
330        workshop_calendar_update($instance, $cm->id);
331        return true;
332    }
333
334    if ($courseid) {
335        // Make sure that the course id is numeric.
336        if (!is_numeric($courseid)) {
337            return false;
338        }
339        if (!$workshops = $DB->get_records('workshop', array('course' => $courseid))) {
340            return false;
341        }
342    } else {
343        if (!$workshops = $DB->get_records('workshop')) {
344            return false;
345        }
346    }
347    foreach ($workshops as $workshop) {
348        if (!$cm = get_coursemodule_from_instance('workshop', $workshop->id, $courseid, false)) {
349            continue;
350        }
351        workshop_calendar_update($workshop, $cm->id);
352    }
353    return true;
354}
355
356/**
357 * List the actions that correspond to a view of this module.
358 * This is used by the participation report.
359 *
360 * Note: This is not used by new logging system. Event with
361 *       crud = 'r' and edulevel = LEVEL_PARTICIPATING will
362 *       be considered as view action.
363 *
364 * @return array
365 */
366function workshop_get_view_actions() {
367    return array('view', 'view all', 'view submission', 'view example');
368}
369
370/**
371 * List the actions that correspond to a post of this module.
372 * This is used by the participation report.
373 *
374 * Note: This is not used by new logging system. Event with
375 *       crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING
376 *       will be considered as post action.
377 *
378 * @return array
379 */
380function workshop_get_post_actions() {
381    return array('add', 'add assessment', 'add example', 'add submission',
382                 'update', 'update assessment', 'update example', 'update submission');
383}
384
385/**
386 * Return a small object with summary information about what a
387 * user has done with a given particular instance of this module
388 * Used for user activity reports.
389 * $return->time = the time they did it
390 * $return->info = a short text description
391 *
392 * @param stdClass $course The course record.
393 * @param stdClass $user The user record.
394 * @param cm_info|stdClass $mod The course module info object or record.
395 * @param stdClass $workshop The workshop instance record.
396 * @return stdclass|null
397 */
398function workshop_user_outline($course, $user, $mod, $workshop) {
399    global $CFG, $DB;
400    require_once($CFG->libdir.'/gradelib.php');
401
402    $grades = grade_get_grades($course->id, 'mod', 'workshop', $workshop->id, $user->id);
403
404    $submissiongrade = null;
405    $assessmentgrade = null;
406
407    $info = '';
408    $time = 0;
409
410    if (!empty($grades->items[0]->grades)) {
411        $submissiongrade = reset($grades->items[0]->grades);
412        $time = max($time, $submissiongrade->dategraded);
413        if (!$submissiongrade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
414            $info .= get_string('submissiongrade', 'workshop') . ': ' . $submissiongrade->str_long_grade
415                . html_writer::empty_tag('br');
416        } else {
417            $info .= get_string('submissiongrade', 'workshop') . ': ' . get_string('hidden', 'grades')
418                . html_writer::empty_tag('br');
419        }
420    }
421    if (!empty($grades->items[1]->grades)) {
422        $assessmentgrade = reset($grades->items[1]->grades);
423        $time = max($time, $assessmentgrade->dategraded);
424        if (!$assessmentgrade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
425            $info .= get_string('gradinggrade', 'workshop') . ': ' . $assessmentgrade->str_long_grade;
426        } else {
427            $info .= get_string('gradinggrade', 'workshop') . ': ' . get_string('hidden', 'grades');
428        }
429    }
430
431    if (!empty($info) and !empty($time)) {
432        $return = new stdclass();
433        $return->time = $time;
434        $return->info = $info;
435        return $return;
436    }
437
438    return null;
439}
440
441/**
442 * Print a detailed representation of what a user has done with
443 * a given particular instance of this module, for user activity reports.
444 *
445 * @param stdClass $course The course record.
446 * @param stdClass $user The user record.
447 * @param cm_info|stdClass $mod The course module info object or record.
448 * @param stdClass $workshop The workshop instance record.
449 * @return string HTML
450 */
451function workshop_user_complete($course, $user, $mod, $workshop) {
452    global $CFG, $DB, $OUTPUT;
453    require_once(__DIR__.'/locallib.php');
454    require_once($CFG->libdir.'/gradelib.php');
455
456    $workshop   = new workshop($workshop, $mod, $course);
457    $grades     = grade_get_grades($course->id, 'mod', 'workshop', $workshop->id, $user->id);
458
459    if (!empty($grades->items[0]->grades)) {
460        $submissiongrade = reset($grades->items[0]->grades);
461        if (!$submissiongrade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
462            $info = get_string('submissiongrade', 'workshop') . ': ' . $submissiongrade->str_long_grade;
463        } else {
464            $info = get_string('submissiongrade', 'workshop') . ': ' . get_string('hidden', 'grades');
465        }
466        echo html_writer::tag('li', $info, array('class'=>'submissiongrade'));
467    }
468    if (!empty($grades->items[1]->grades)) {
469        $assessmentgrade = reset($grades->items[1]->grades);
470        if (!$assessmentgrade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
471            $info = get_string('gradinggrade', 'workshop') . ': ' . $assessmentgrade->str_long_grade;
472        } else {
473            $info = get_string('gradinggrade', 'workshop') . ': ' . get_string('hidden', 'grades');
474        }
475        echo html_writer::tag('li', $info, array('class'=>'gradinggrade'));
476    }
477
478    if (has_capability('mod/workshop:viewallsubmissions', $workshop->context)) {
479        $canviewsubmission = true;
480        if (groups_get_activity_groupmode($workshop->cm) == SEPARATEGROUPS) {
481            // user must have accessallgroups or share at least one group with the submission author
482            if (!has_capability('moodle/site:accessallgroups', $workshop->context)) {
483                $usersgroups = groups_get_activity_allowed_groups($workshop->cm);
484                $authorsgroups = groups_get_all_groups($workshop->course->id, $user->id, $workshop->cm->groupingid, 'g.id');
485                $sharedgroups = array_intersect_key($usersgroups, $authorsgroups);
486                if (empty($sharedgroups)) {
487                    $canviewsubmission = false;
488                }
489            }
490        }
491        if ($canviewsubmission and $submission = $workshop->get_submission_by_author($user->id)) {
492            $title      = format_string($submission->title);
493            $url        = $workshop->submission_url($submission->id);
494            $link       = html_writer::link($url, $title);
495            $info       = get_string('submission', 'workshop').': '.$link;
496            echo html_writer::tag('li', $info, array('class'=>'submission'));
497        }
498    }
499
500    if (has_capability('mod/workshop:viewallassessments', $workshop->context)) {
501        if ($assessments = $workshop->get_assessments_by_reviewer($user->id)) {
502            foreach ($assessments as $assessment) {
503                $a = new stdclass();
504                $a->submissionurl = $workshop->submission_url($assessment->submissionid)->out();
505                $a->assessmenturl = $workshop->assess_url($assessment->id)->out();
506                $a->submissiontitle = s($assessment->submissiontitle);
507                echo html_writer::tag('li', get_string('assessmentofsubmission', 'workshop', $a));
508            }
509        }
510    }
511}
512
513/**
514 * Given a course and a time, this module should find recent activity
515 * that has occurred in workshop activities and print it out.
516 * Return true if there was output, or false is there was none.
517 *
518 * @param stdClass $course
519 * @param bool $viewfullnames
520 * @param int $timestart
521 * @return boolean
522 */
523function workshop_print_recent_activity($course, $viewfullnames, $timestart) {
524    global $CFG, $USER, $DB, $OUTPUT;
525
526    $authoramefields = get_all_user_name_fields(true, 'author', null, 'author');
527    $reviewerfields = get_all_user_name_fields(true, 'reviewer', null, 'reviewer');
528
529    $sql = "SELECT s.id AS submissionid, s.title AS submissiontitle, s.timemodified AS submissionmodified,
530                   author.id AS authorid, $authoramefields, a.id AS assessmentid, a.timemodified AS assessmentmodified,
531                   reviewer.id AS reviewerid, $reviewerfields, cm.id AS cmid
532              FROM {workshop} w
533        INNER JOIN {course_modules} cm ON cm.instance = w.id
534        INNER JOIN {modules} md ON md.id = cm.module
535        INNER JOIN {workshop_submissions} s ON s.workshopid = w.id
536        INNER JOIN {user} author ON s.authorid = author.id
537         LEFT JOIN {workshop_assessments} a ON a.submissionid = s.id
538         LEFT JOIN {user} reviewer ON a.reviewerid = reviewer.id
539             WHERE cm.course = ?
540                   AND md.name = 'workshop'
541                   AND s.example = 0
542                   AND (s.timemodified > ? OR a.timemodified > ?)
543          ORDER BY s.timemodified";
544
545    $rs = $DB->get_recordset_sql($sql, array($course->id, $timestart, $timestart));
546
547    $modinfo = get_fast_modinfo($course); // reference needed because we might load the groups
548
549    $submissions = array(); // recent submissions indexed by submission id
550    $assessments = array(); // recent assessments indexed by assessment id
551    $users       = array();
552
553    foreach ($rs as $activity) {
554        if (!array_key_exists($activity->cmid, $modinfo->cms)) {
555            // this should not happen but just in case
556            continue;
557        }
558
559        $cm = $modinfo->cms[$activity->cmid];
560        if (!$cm->uservisible) {
561            continue;
562        }
563
564        // remember all user names we can use later
565        if (empty($users[$activity->authorid])) {
566            $u = new stdclass();
567            $users[$activity->authorid] = username_load_fields_from_object($u, $activity, 'author');
568        }
569        if ($activity->reviewerid and empty($users[$activity->reviewerid])) {
570            $u = new stdclass();
571            $users[$activity->reviewerid] = username_load_fields_from_object($u, $activity, 'reviewer');
572        }
573
574        $context = context_module::instance($cm->id);
575        $groupmode = groups_get_activity_groupmode($cm, $course);
576
577        if ($activity->submissionmodified > $timestart and empty($submissions[$activity->submissionid])) {
578            $s = new stdclass();
579            $s->title = $activity->submissiontitle;
580            $s->authorid = $activity->authorid;
581            $s->timemodified = $activity->submissionmodified;
582            $s->cmid = $activity->cmid;
583            if ($activity->authorid == $USER->id || has_capability('mod/workshop:viewauthornames', $context)) {
584                $s->authornamevisible = true;
585            } else {
586                $s->authornamevisible = false;
587            }
588
589            // the following do-while wrapper allows to break from deeply nested if-statements
590            do {
591                if ($s->authorid === $USER->id) {
592                    // own submissions always visible
593                    $submissions[$activity->submissionid] = $s;
594                    break;
595                }
596
597                if (has_capability('mod/workshop:viewallsubmissions', $context)) {
598                    if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
599                        if (isguestuser()) {
600                            // shortcut - guest user does not belong into any group
601                            break;
602                        }
603
604                        // this might be slow - show only submissions by users who share group with me in this cm
605                        if (!$modinfo->get_groups($cm->groupingid)) {
606                            break;
607                        }
608                        $authorsgroups = groups_get_all_groups($course->id, $s->authorid, $cm->groupingid);
609                        if (is_array($authorsgroups)) {
610                            $authorsgroups = array_keys($authorsgroups);
611                            $intersect = array_intersect($authorsgroups, $modinfo->get_groups($cm->groupingid));
612                            if (empty($intersect)) {
613                                break;
614                            } else {
615                                // can see all submissions and shares a group with the author
616                                $submissions[$activity->submissionid] = $s;
617                                break;
618                            }
619                        }
620
621                    } else {
622                        // can see all submissions from all groups
623                        $submissions[$activity->submissionid] = $s;
624                    }
625                }
626            } while (0);
627        }
628
629        if ($activity->assessmentmodified > $timestart and empty($assessments[$activity->assessmentid])) {
630            $a = new stdclass();
631            $a->submissionid = $activity->submissionid;
632            $a->submissiontitle = $activity->submissiontitle;
633            $a->reviewerid = $activity->reviewerid;
634            $a->timemodified = $activity->assessmentmodified;
635            $a->cmid = $activity->cmid;
636            if ($activity->reviewerid == $USER->id || has_capability('mod/workshop:viewreviewernames', $context)) {
637                $a->reviewernamevisible = true;
638            } else {
639                $a->reviewernamevisible = false;
640            }
641
642            // the following do-while wrapper allows to break from deeply nested if-statements
643            do {
644                if ($a->reviewerid === $USER->id) {
645                    // own assessments always visible
646                    $assessments[$activity->assessmentid] = $a;
647                    break;
648                }
649
650                if (has_capability('mod/workshop:viewallassessments', $context)) {
651                    if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
652                        if (isguestuser()) {
653                            // shortcut - guest user does not belong into any group
654                            break;
655                        }
656
657                        // this might be slow - show only submissions by users who share group with me in this cm
658                        if (!$modinfo->get_groups($cm->groupingid)) {
659                            break;
660                        }
661                        $reviewersgroups = groups_get_all_groups($course->id, $a->reviewerid, $cm->groupingid);
662                        if (is_array($reviewersgroups)) {
663                            $reviewersgroups = array_keys($reviewersgroups);
664                            $intersect = array_intersect($reviewersgroups, $modinfo->get_groups($cm->groupingid));
665                            if (empty($intersect)) {
666                                break;
667                            } else {
668                                // can see all assessments and shares a group with the reviewer
669                                $assessments[$activity->assessmentid] = $a;
670                                break;
671                            }
672                        }
673
674                    } else {
675                        // can see all assessments from all groups
676                        $assessments[$activity->assessmentid] = $a;
677                    }
678                }
679            } while (0);
680        }
681    }
682    $rs->close();
683
684    $shown = false;
685
686    if (!empty($submissions)) {
687        $shown = true;
688        echo $OUTPUT->heading(get_string('recentsubmissions', 'workshop') . ':', 6);
689        foreach ($submissions as $id => $submission) {
690            $link = new moodle_url('/mod/workshop/submission.php', array('id'=>$id, 'cmid'=>$submission->cmid));
691            if ($submission->authornamevisible) {
692                $author = $users[$submission->authorid];
693            } else {
694                $author = null;
695            }
696            print_recent_activity_note($submission->timemodified, $author, $submission->title, $link->out(), false, $viewfullnames);
697        }
698    }
699
700    if (!empty($assessments)) {
701        $shown = true;
702        echo $OUTPUT->heading(get_string('recentassessments', 'workshop') . ':', 6);
703        core_collator::asort_objects_by_property($assessments, 'timemodified');
704        foreach ($assessments as $id => $assessment) {
705            $link = new moodle_url('/mod/workshop/assessment.php', array('asid' => $id));
706            if ($assessment->reviewernamevisible) {
707                $reviewer = $users[$assessment->reviewerid];
708            } else {
709                $reviewer = null;
710            }
711            print_recent_activity_note($assessment->timemodified, $reviewer, $assessment->submissiontitle, $link->out(), false, $viewfullnames);
712        }
713    }
714
715    if ($shown) {
716        return true;
717    }
718
719    return false;
720}
721
722/**
723 * Returns all activity in course workshops since a given time
724 *
725 * @param array $activities sequentially indexed array of objects
726 * @param int $index
727 * @param int $timestart
728 * @param int $courseid
729 * @param int $cmid
730 * @param int $userid defaults to 0
731 * @param int $groupid defaults to 0
732 * @return void adds items into $activities and increases $index
733 */
734function workshop_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid=0, $groupid=0) {
735    global $CFG, $COURSE, $USER, $DB;
736
737    if ($COURSE->id == $courseid) {
738        $course = $COURSE;
739    } else {
740        $course = $DB->get_record('course', array('id'=>$courseid));
741    }
742
743    $modinfo = get_fast_modinfo($course);
744
745    $cm = $modinfo->cms[$cmid];
746
747    $params = array();
748    if ($userid) {
749        $userselect = "AND (author.id = :authorid OR reviewer.id = :reviewerid)";
750        $params['authorid'] = $userid;
751        $params['reviewerid'] = $userid;
752    } else {
753        $userselect = "";
754    }
755
756    if ($groupid) {
757        $groupselect = "AND (authorgroupmembership.groupid = :authorgroupid OR reviewergroupmembership.groupid = :reviewergroupid)";
758        $groupjoin   = "LEFT JOIN {groups_members} authorgroupmembership ON authorgroupmembership.userid = author.id
759                        LEFT JOIN {groups_members} reviewergroupmembership ON reviewergroupmembership.userid = reviewer.id";
760        $params['authorgroupid'] = $groupid;
761        $params['reviewergroupid'] = $groupid;
762    } else {
763        $groupselect = "";
764        $groupjoin   = "";
765    }
766
767    $params['cminstance'] = $cm->instance;
768    $params['submissionmodified'] = $timestart;
769    $params['assessmentmodified'] = $timestart;
770
771    $authornamefields = get_all_user_name_fields(true, 'author', null, 'author');
772    $reviewerfields = get_all_user_name_fields(true, 'reviewer', null, 'reviewer');
773
774    $sql = "SELECT s.id AS submissionid, s.title AS submissiontitle, s.timemodified AS submissionmodified,
775                   author.id AS authorid, $authornamefields, author.picture AS authorpicture, author.imagealt AS authorimagealt,
776                   author.email AS authoremail, a.id AS assessmentid, a.timemodified AS assessmentmodified,
777                   reviewer.id AS reviewerid, $reviewerfields, reviewer.picture AS reviewerpicture,
778                   reviewer.imagealt AS reviewerimagealt, reviewer.email AS revieweremail
779              FROM {workshop_submissions} s
780        INNER JOIN {workshop} w ON s.workshopid = w.id
781        INNER JOIN {user} author ON s.authorid = author.id
782         LEFT JOIN {workshop_assessments} a ON a.submissionid = s.id
783         LEFT JOIN {user} reviewer ON a.reviewerid = reviewer.id
784        $groupjoin
785             WHERE w.id = :cminstance
786                   AND s.example = 0
787                   $userselect $groupselect
788                   AND (s.timemodified > :submissionmodified OR a.timemodified > :assessmentmodified)
789          ORDER BY s.timemodified ASC, a.timemodified ASC";
790
791    $rs = $DB->get_recordset_sql($sql, $params);
792
793    $groupmode       = groups_get_activity_groupmode($cm, $course);
794    $context         = context_module::instance($cm->id);
795    $grader          = has_capability('moodle/grade:viewall', $context);
796    $accessallgroups = has_capability('moodle/site:accessallgroups', $context);
797    $viewauthors     = has_capability('mod/workshop:viewauthornames', $context);
798    $viewreviewers   = has_capability('mod/workshop:viewreviewernames', $context);
799
800    $submissions = array(); // recent submissions indexed by submission id
801    $assessments = array(); // recent assessments indexed by assessment id
802    $users       = array();
803
804    foreach ($rs as $activity) {
805
806        // remember all user names we can use later
807        if (empty($users[$activity->authorid])) {
808            $u = new stdclass();
809            $additionalfields = explode(',', user_picture::fields());
810            $u = username_load_fields_from_object($u, $activity, 'author', $additionalfields);
811            $users[$activity->authorid] = $u;
812        }
813        if ($activity->reviewerid and empty($users[$activity->reviewerid])) {
814            $u = new stdclass();
815            $additionalfields = explode(',', user_picture::fields());
816            $u = username_load_fields_from_object($u, $activity, 'reviewer', $additionalfields);
817            $users[$activity->reviewerid] = $u;
818        }
819
820        if ($activity->submissionmodified > $timestart and empty($submissions[$activity->submissionid])) {
821            $s = new stdclass();
822            $s->id = $activity->submissionid;
823            $s->title = $activity->submissiontitle;
824            $s->authorid = $activity->authorid;
825            $s->timemodified = $activity->submissionmodified;
826            if ($activity->authorid == $USER->id || has_capability('mod/workshop:viewauthornames', $context)) {
827                $s->authornamevisible = true;
828            } else {
829                $s->authornamevisible = false;
830            }
831
832            // the following do-while wrapper allows to break from deeply nested if-statements
833            do {
834                if ($s->authorid === $USER->id) {
835                    // own submissions always visible
836                    $submissions[$activity->submissionid] = $s;
837                    break;
838                }
839
840                if (has_capability('mod/workshop:viewallsubmissions', $context)) {
841                    if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
842                        if (isguestuser()) {
843                            // shortcut - guest user does not belong into any group
844                            break;
845                        }
846
847                        // this might be slow - show only submissions by users who share group with me in this cm
848                        if (!$modinfo->get_groups($cm->groupingid)) {
849                            break;
850                        }
851                        $authorsgroups = groups_get_all_groups($course->id, $s->authorid, $cm->groupingid);
852                        if (is_array($authorsgroups)) {
853                            $authorsgroups = array_keys($authorsgroups);
854                            $intersect = array_intersect($authorsgroups, $modinfo->get_groups($cm->groupingid));
855                            if (empty($intersect)) {
856                                break;
857                            } else {
858                                // can see all submissions and shares a group with the author
859                                $submissions[$activity->submissionid] = $s;
860                                break;
861                            }
862                        }
863
864                    } else {
865                        // can see all submissions from all groups
866                        $submissions[$activity->submissionid] = $s;
867                    }
868                }
869            } while (0);
870        }
871
872        if ($activity->assessmentmodified > $timestart and empty($assessments[$activity->assessmentid])) {
873            $a = new stdclass();
874            $a->id = $activity->assessmentid;
875            $a->submissionid = $activity->submissionid;
876            $a->submissiontitle = $activity->submissiontitle;
877            $a->reviewerid = $activity->reviewerid;
878            $a->timemodified = $activity->assessmentmodified;
879            if ($activity->reviewerid == $USER->id || has_capability('mod/workshop:viewreviewernames', $context)) {
880                $a->reviewernamevisible = true;
881            } else {
882                $a->reviewernamevisible = false;
883            }
884
885            // the following do-while wrapper allows to break from deeply nested if-statements
886            do {
887                if ($a->reviewerid === $USER->id) {
888                    // own assessments always visible
889                    $assessments[$activity->assessmentid] = $a;
890                    break;
891                }
892
893                if (has_capability('mod/workshop:viewallassessments', $context)) {
894                    if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
895                        if (isguestuser()) {
896                            // shortcut - guest user does not belong into any group
897                            break;
898                        }
899
900                        // this might be slow - show only submissions by users who share group with me in this cm
901                        if (!$modinfo->get_groups($cm->groupingid)) {
902                            break;
903                        }
904                        $reviewersgroups = groups_get_all_groups($course->id, $a->reviewerid, $cm->groupingid);
905                        if (is_array($reviewersgroups)) {
906                            $reviewersgroups = array_keys($reviewersgroups);
907                            $intersect = array_intersect($reviewersgroups, $modinfo->get_groups($cm->groupingid));
908                            if (empty($intersect)) {
909                                break;
910                            } else {
911                                // can see all assessments and shares a group with the reviewer
912                                $assessments[$activity->assessmentid] = $a;
913                                break;
914                            }
915                        }
916
917                    } else {
918                        // can see all assessments from all groups
919                        $assessments[$activity->assessmentid] = $a;
920                    }
921                }
922            } while (0);
923        }
924    }
925    $rs->close();
926
927    $workshopname = format_string($cm->name, true);
928
929    if ($grader) {
930        require_once($CFG->libdir.'/gradelib.php');
931        $grades = grade_get_grades($courseid, 'mod', 'workshop', $cm->instance, array_keys($users));
932    }
933
934    foreach ($submissions as $submission) {
935        $tmpactivity                = new stdclass();
936        $tmpactivity->type          = 'workshop';
937        $tmpactivity->cmid          = $cm->id;
938        $tmpactivity->name          = $workshopname;
939        $tmpactivity->sectionnum    = $cm->sectionnum;
940        $tmpactivity->timestamp     = $submission->timemodified;
941        $tmpactivity->subtype       = 'submission';
942        $tmpactivity->content       = $submission;
943        if ($grader) {
944            $tmpactivity->grade     = $grades->items[0]->grades[$submission->authorid]->str_long_grade;
945        }
946        if ($submission->authornamevisible and !empty($users[$submission->authorid])) {
947            $tmpactivity->user      = $users[$submission->authorid];
948        }
949        $activities[$index++]       = $tmpactivity;
950    }
951
952    foreach ($assessments as $assessment) {
953        $tmpactivity                = new stdclass();
954        $tmpactivity->type          = 'workshop';
955        $tmpactivity->cmid          = $cm->id;
956        $tmpactivity->name          = $workshopname;
957        $tmpactivity->sectionnum    = $cm->sectionnum;
958        $tmpactivity->timestamp     = $assessment->timemodified;
959        $tmpactivity->subtype       = 'assessment';
960        $tmpactivity->content       = $assessment;
961        if ($grader) {
962            $tmpactivity->grade     = $grades->items[1]->grades[$assessment->reviewerid]->str_long_grade;
963        }
964        if ($assessment->reviewernamevisible and !empty($users[$assessment->reviewerid])) {
965            $tmpactivity->user      = $users[$assessment->reviewerid];
966        }
967        $activities[$index++]       = $tmpactivity;
968    }
969}
970
971/**
972 * Print single activity item prepared by {@see workshop_get_recent_mod_activity()}
973 */
974function workshop_print_recent_mod_activity($activity, $courseid, $detail, $modnames, $viewfullnames) {
975    global $CFG, $OUTPUT;
976
977    if (!empty($activity->user)) {
978        echo html_writer::tag('div', $OUTPUT->user_picture($activity->user, array('courseid'=>$courseid)),
979                array('style' => 'float: left; padding: 7px;'));
980    }
981
982    if ($activity->subtype == 'submission') {
983        echo html_writer::start_tag('div', array('class'=>'submission', 'style'=>'padding: 7px; float:left;'));
984
985        if ($detail) {
986            echo html_writer::start_tag('h4', array('class'=>'workshop'));
987            $url = new moodle_url('/mod/workshop/view.php', array('id'=>$activity->cmid));
988            $name = s($activity->name);
989            echo $OUTPUT->image_icon('icon', $name, $activity->type);
990            echo ' ' . $modnames[$activity->type];
991            echo html_writer::link($url, $name, array('class'=>'name', 'style'=>'margin-left: 5px'));
992            echo html_writer::end_tag('h4');
993        }
994
995        echo html_writer::start_tag('div', array('class'=>'title'));
996        $url = new moodle_url('/mod/workshop/submission.php', array('cmid'=>$activity->cmid, 'id'=>$activity->content->id));
997        $name = s($activity->content->title);
998        echo html_writer::tag('strong', html_writer::link($url, $name));
999        echo html_writer::end_tag('div');
1000
1001        if (!empty($activity->user)) {
1002            echo html_writer::start_tag('div', array('class'=>'user'));
1003            $url = new moodle_url('/user/view.php', array('id'=>$activity->user->id, 'course'=>$courseid));
1004            $name = fullname($activity->user);
1005            $link = html_writer::link($url, $name);
1006            echo get_string('submissionby', 'workshop', $link);
1007            echo ' - '.userdate($activity->timestamp);
1008            echo html_writer::end_tag('div');
1009        } else {
1010            echo html_writer::start_tag('div', array('class'=>'anonymous'));
1011            echo get_string('submission', 'workshop');
1012            echo ' - '.userdate($activity->timestamp);
1013            echo html_writer::end_tag('div');
1014        }
1015
1016        echo html_writer::end_tag('div');
1017    }
1018
1019    if ($activity->subtype == 'assessment') {
1020        echo html_writer::start_tag('div', array('class'=>'assessment', 'style'=>'padding: 7px; float:left;'));
1021
1022        if ($detail) {
1023            echo html_writer::start_tag('h4', array('class'=>'workshop'));
1024            $url = new moodle_url('/mod/workshop/view.php', array('id'=>$activity->cmid));
1025            $name = s($activity->name);
1026            echo $OUTPUT->image_icon('icon', $name, $activity->type);
1027            echo ' ' . $modnames[$activity->type];
1028            echo html_writer::link($url, $name, array('class'=>'name', 'style'=>'margin-left: 5px'));
1029            echo html_writer::end_tag('h4');
1030        }
1031
1032        echo html_writer::start_tag('div', array('class'=>'title'));
1033        $url = new moodle_url('/mod/workshop/assessment.php', array('asid'=>$activity->content->id));
1034        $name = s($activity->content->submissiontitle);
1035        echo html_writer::tag('em', html_writer::link($url, $name));
1036        echo html_writer::end_tag('div');
1037
1038        if (!empty($activity->user)) {
1039            echo html_writer::start_tag('div', array('class'=>'user'));
1040            $url = new moodle_url('/user/view.php', array('id'=>$activity->user->id, 'course'=>$courseid));
1041            $name = fullname($activity->user);
1042            $link = html_writer::link($url, $name);
1043            echo get_string('assessmentbyfullname', 'workshop', $link);
1044            echo ' - '.userdate($activity->timestamp);
1045            echo html_writer::end_tag('div');
1046        } else {
1047            echo html_writer::start_tag('div', array('class'=>'anonymous'));
1048            echo get_string('assessment', 'workshop');
1049            echo ' - '.userdate($activity->timestamp);
1050            echo html_writer::end_tag('div');
1051        }
1052
1053        echo html_writer::end_tag('div');
1054    }
1055
1056    echo html_writer::empty_tag('br', array('style'=>'clear:both'));
1057}
1058
1059/**
1060 * @deprecated since Moodle 3.8
1061 */
1062function workshop_scale_used() {
1063    throw new coding_exception('workshop_scale_used() can not be used anymore. Plugins can implement ' .
1064        '<modname>_scale_used_anywhere, all implementations of <modname>_scale_used are now ignored');
1065}
1066
1067/**
1068 * Is a given scale used by any instance of workshop?
1069 *
1070 * The function asks all installed grading strategy subplugins. The workshop
1071 * core itself does not use scales. Both grade for submission and grade for
1072 * assessments do not use scales.
1073 *
1074 * @param int $scaleid id of the scale to check
1075 * @return bool
1076 */
1077function workshop_scale_used_anywhere($scaleid) {
1078    global $CFG; // other files included from here
1079
1080    $strategies = core_component::get_plugin_list('workshopform');
1081    foreach ($strategies as $strategy => $strategypath) {
1082        $strategylib = $strategypath . '/lib.php';
1083        if (is_readable($strategylib)) {
1084            require_once($strategylib);
1085        } else {
1086            throw new coding_exception('the grading forms subplugin must contain library ' . $strategylib);
1087        }
1088        $classname = 'workshop_' . $strategy . '_strategy';
1089        if (method_exists($classname, 'scale_used')) {
1090            if (call_user_func(array($classname, 'scale_used'), $scaleid)) {
1091                // no need to include any other files - scale is used
1092                return true;
1093            }
1094        }
1095    }
1096
1097    return false;
1098}
1099
1100////////////////////////////////////////////////////////////////////////////////
1101// Gradebook API                                                              //
1102////////////////////////////////////////////////////////////////////////////////
1103
1104/**
1105 * Creates or updates grade items for the give workshop instance
1106 *
1107 * Needed by grade_update_mod_grades() in lib/gradelib.php. Also used by
1108 * {@link workshop_update_grades()}.
1109 *
1110 * @param stdClass $workshop instance object with extra cmidnumber property
1111 * @param stdClass $submissiongrades data for the first grade item
1112 * @param stdClass $assessmentgrades data for the second grade item
1113 * @return void
1114 */
1115function workshop_grade_item_update(stdclass $workshop, $submissiongrades=null, $assessmentgrades=null) {
1116    global $CFG;
1117    require_once($CFG->libdir.'/gradelib.php');
1118
1119    $a = new stdclass();
1120    $a->workshopname = clean_param($workshop->name, PARAM_NOTAGS);
1121
1122    $item = array();
1123    $item['itemname'] = get_string('gradeitemsubmission', 'workshop', $a);
1124    $item['gradetype'] = GRADE_TYPE_VALUE;
1125    $item['grademax']  = $workshop->grade;
1126    $item['grademin']  = 0;
1127    grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 0, $submissiongrades , $item);
1128
1129    $item = array();
1130    $item['itemname'] = get_string('gradeitemassessment', 'workshop', $a);
1131    $item['gradetype'] = GRADE_TYPE_VALUE;
1132    $item['grademax']  = $workshop->gradinggrade;
1133    $item['grademin']  = 0;
1134    grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 1, $assessmentgrades, $item);
1135}
1136
1137/**
1138 * Update workshop grades in the gradebook
1139 *
1140 * Needed by grade_update_mod_grades() in lib/gradelib.php
1141 *
1142 * @category grade
1143 * @param stdClass $workshop instance object with extra cmidnumber and modname property
1144 * @param int $userid        update grade of specific user only, 0 means all participants
1145 * @return void
1146 */
1147function workshop_update_grades(stdclass $workshop, $userid=0) {
1148    global $CFG, $DB;
1149    require_once($CFG->libdir.'/gradelib.php');
1150
1151    $whereuser = $userid ? ' AND authorid = :userid' : '';
1152    $params = array('workshopid' => $workshop->id, 'userid' => $userid);
1153    $sql = 'SELECT authorid, grade, gradeover, gradeoverby, feedbackauthor, feedbackauthorformat, timemodified, timegraded
1154              FROM {workshop_submissions}
1155             WHERE workshopid = :workshopid AND example=0' . $whereuser;
1156    $records = $DB->get_records_sql($sql, $params);
1157    $submissiongrades = array();
1158    foreach ($records as $record) {
1159        $grade = new stdclass();
1160        $grade->userid = $record->authorid;
1161        if (!is_null($record->gradeover)) {
1162            $grade->rawgrade = grade_floatval($workshop->grade * $record->gradeover / 100);
1163            $grade->usermodified = $record->gradeoverby;
1164        } else {
1165            $grade->rawgrade = grade_floatval($workshop->grade * $record->grade / 100);
1166        }
1167        $grade->feedback = $record->feedbackauthor;
1168        $grade->feedbackformat = $record->feedbackauthorformat;
1169        $grade->datesubmitted = $record->timemodified;
1170        $grade->dategraded = $record->timegraded;
1171        $submissiongrades[$record->authorid] = $grade;
1172    }
1173
1174    $whereuser = $userid ? ' AND userid = :userid' : '';
1175    $params = array('workshopid' => $workshop->id, 'userid' => $userid);
1176    $sql = 'SELECT userid, gradinggrade, timegraded
1177              FROM {workshop_aggregations}
1178             WHERE workshopid = :workshopid' . $whereuser;
1179    $records = $DB->get_records_sql($sql, $params);
1180    $assessmentgrades = array();
1181    foreach ($records as $record) {
1182        $grade = new stdclass();
1183        $grade->userid = $record->userid;
1184        $grade->rawgrade = grade_floatval($workshop->gradinggrade * $record->gradinggrade / 100);
1185        $grade->dategraded = $record->timegraded;
1186        $assessmentgrades[$record->userid] = $grade;
1187    }
1188
1189    workshop_grade_item_update($workshop, $submissiongrades, $assessmentgrades);
1190}
1191
1192/**
1193 * Update the grade items categories if they are changed via mod_form.php
1194 *
1195 * We must do it manually here in the workshop module because modedit supports only
1196 * single grade item while we use two.
1197 *
1198 * @param stdClass $workshop An object from the form in mod_form.php
1199 */
1200function workshop_grade_item_category_update($workshop) {
1201
1202    $gradeitems = grade_item::fetch_all(array(
1203        'itemtype'      => 'mod',
1204        'itemmodule'    => 'workshop',
1205        'iteminstance'  => $workshop->id,
1206        'courseid'      => $workshop->course));
1207
1208    if (!empty($gradeitems)) {
1209        foreach ($gradeitems as $gradeitem) {
1210            if ($gradeitem->itemnumber == 0) {
1211                if (isset($workshop->submissiongradepass) &&
1212                        $gradeitem->gradepass != $workshop->submissiongradepass) {
1213                    $gradeitem->gradepass = $workshop->submissiongradepass;
1214                    $gradeitem->update();
1215                }
1216                if ($gradeitem->categoryid != $workshop->gradecategory) {
1217                    $gradeitem->set_parent($workshop->gradecategory);
1218                }
1219            } else if ($gradeitem->itemnumber == 1) {
1220                if (isset($workshop->gradinggradepass) &&
1221                        $gradeitem->gradepass != $workshop->gradinggradepass) {
1222                    $gradeitem->gradepass = $workshop->gradinggradepass;
1223                    $gradeitem->update();
1224                }
1225                if ($gradeitem->categoryid != $workshop->gradinggradecategory) {
1226                    $gradeitem->set_parent($workshop->gradinggradecategory);
1227                }
1228            }
1229        }
1230    }
1231}
1232
1233////////////////////////////////////////////////////////////////////////////////
1234// File API                                                                   //
1235////////////////////////////////////////////////////////////////////////////////
1236
1237/**
1238 * Returns the lists of all browsable file areas within the given module context
1239 *
1240 * The file area workshop_intro for the activity introduction field is added automatically
1241 * by {@link file_browser::get_file_info_context_module()}
1242 *
1243 * @package  mod_workshop
1244 * @category files
1245 *
1246 * @param stdClass $course
1247 * @param stdClass $cm
1248 * @param stdClass $context
1249 * @return array of [(string)filearea] => (string)description
1250 */
1251function workshop_get_file_areas($course, $cm, $context) {
1252    $areas = array();
1253    $areas['instructauthors']          = get_string('areainstructauthors', 'workshop');
1254    $areas['instructreviewers']        = get_string('areainstructreviewers', 'workshop');
1255    $areas['submission_content']       = get_string('areasubmissioncontent', 'workshop');
1256    $areas['submission_attachment']    = get_string('areasubmissionattachment', 'workshop');
1257    $areas['conclusion']               = get_string('areaconclusion', 'workshop');
1258    $areas['overallfeedback_content']  = get_string('areaoverallfeedbackcontent', 'workshop');
1259    $areas['overallfeedback_attachment'] = get_string('areaoverallfeedbackattachment', 'workshop');
1260
1261    return $areas;
1262}
1263
1264/**
1265 * Serves the files from the workshop file areas
1266 *
1267 * Apart from module intro (handled by pluginfile.php automatically), workshop files may be
1268 * media inserted into submission content (like images) and submission attachments. For these two,
1269 * the fileareas submission_content and submission_attachment are used.
1270 * Besides that, areas instructauthors, instructreviewers and conclusion contain the media
1271 * embedded using the mod_form.php.
1272 *
1273 * @package  mod_workshop
1274 * @category files
1275 *
1276 * @param stdClass $course the course object
1277 * @param stdClass $cm the course module object
1278 * @param stdClass $context the workshop's context
1279 * @param string $filearea the name of the file area
1280 * @param array $args extra arguments (itemid, path)
1281 * @param bool $forcedownload whether or not force download
1282 * @param array $options additional options affecting the file serving
1283 * @return bool false if the file not found, just send the file otherwise and do not return anything
1284 */
1285function workshop_pluginfile($course, $cm, $context, $filearea, array $args, $forcedownload, array $options=array()) {
1286    global $DB, $CFG, $USER;
1287
1288    if ($context->contextlevel != CONTEXT_MODULE) {
1289        return false;
1290    }
1291
1292    require_login($course, true, $cm);
1293
1294    if ($filearea === 'instructauthors' or $filearea === 'instructreviewers' or $filearea === 'conclusion') {
1295        // The $args are supposed to contain just the path, not the item id.
1296        $relativepath = implode('/', $args);
1297        $fullpath = "/$context->id/mod_workshop/$filearea/0/$relativepath";
1298
1299        $fs = get_file_storage();
1300        if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1301            send_file_not_found();
1302        }
1303        send_stored_file($file, null, 0, $forcedownload, $options);
1304
1305    } else if ($filearea === 'submission_content' or $filearea === 'submission_attachment') {
1306        $itemid = (int)array_shift($args);
1307        if (!$workshop = $DB->get_record('workshop', array('id' => $cm->instance))) {
1308            return false;
1309        }
1310        if (!$submission = $DB->get_record('workshop_submissions', array('id' => $itemid, 'workshopid' => $workshop->id))) {
1311            return false;
1312        }
1313
1314        // make sure the user is allowed to see the file
1315        if (empty($submission->example)) {
1316            if ($USER->id != $submission->authorid) {
1317                if ($submission->published == 1 and $workshop->phase == 50
1318                        and has_capability('mod/workshop:viewpublishedsubmissions', $context)) {
1319                    // Published submission, we can go (workshop does not take the group mode
1320                    // into account in this case yet).
1321                } else if (!$DB->record_exists('workshop_assessments', array('submissionid' => $submission->id, 'reviewerid' => $USER->id))) {
1322                    if (!has_capability('mod/workshop:viewallsubmissions', $context)) {
1323                        send_file_not_found();
1324                    } else {
1325                        $gmode = groups_get_activity_groupmode($cm, $course);
1326                        if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1327                            // check there is at least one common group with both the $USER
1328                            // and the submission author
1329                            $sql = "SELECT 'x'
1330                                      FROM {workshop_submissions} s
1331                                      JOIN {user} a ON (a.id = s.authorid)
1332                                      JOIN {groups_members} agm ON (a.id = agm.userid)
1333                                      JOIN {user} u ON (u.id = ?)
1334                                      JOIN {groups_members} ugm ON (u.id = ugm.userid)
1335                                     WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1336                            $params = array($USER->id, $workshop->id, $submission->id);
1337                            if (!$DB->record_exists_sql($sql, $params)) {
1338                                send_file_not_found();
1339                            }
1340                        }
1341                    }
1342                }
1343            }
1344        }
1345
1346        $fs = get_file_storage();
1347        $relativepath = implode('/', $args);
1348        $fullpath = "/$context->id/mod_workshop/$filearea/$itemid/$relativepath";
1349        if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1350            return false;
1351        }
1352        // finally send the file
1353        // these files are uploaded by students - forcing download for security reasons
1354        send_stored_file($file, 0, 0, true, $options);
1355
1356    } else if ($filearea === 'overallfeedback_content' or $filearea === 'overallfeedback_attachment') {
1357        $itemid = (int)array_shift($args);
1358        if (!$workshop = $DB->get_record('workshop', array('id' => $cm->instance))) {
1359            return false;
1360        }
1361        if (!$assessment = $DB->get_record('workshop_assessments', array('id' => $itemid))) {
1362            return false;
1363        }
1364        if (!$submission = $DB->get_record('workshop_submissions', array('id' => $assessment->submissionid, 'workshopid' => $workshop->id))) {
1365            return false;
1366        }
1367
1368        if ($USER->id == $assessment->reviewerid) {
1369            // Reviewers can always see their own files.
1370        } else if ($USER->id == $submission->authorid and $workshop->phase == 50) {
1371            // Authors can see the feedback once the workshop is closed.
1372        } else if (!empty($submission->example) and $assessment->weight == 1) {
1373            // Reference assessments of example submissions can be displayed.
1374        } else if (!has_capability('mod/workshop:viewallassessments', $context)) {
1375            send_file_not_found();
1376        } else {
1377            $gmode = groups_get_activity_groupmode($cm, $course);
1378            if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1379                // Check there is at least one common group with both the $USER
1380                // and the submission author.
1381                $sql = "SELECT 'x'
1382                          FROM {workshop_submissions} s
1383                          JOIN {user} a ON (a.id = s.authorid)
1384                          JOIN {groups_members} agm ON (a.id = agm.userid)
1385                          JOIN {user} u ON (u.id = ?)
1386                          JOIN {groups_members} ugm ON (u.id = ugm.userid)
1387                         WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1388                $params = array($USER->id, $workshop->id, $submission->id);
1389                if (!$DB->record_exists_sql($sql, $params)) {
1390                    send_file_not_found();
1391                }
1392            }
1393        }
1394
1395        $fs = get_file_storage();
1396        $relativepath = implode('/', $args);
1397        $fullpath = "/$context->id/mod_workshop/$filearea/$itemid/$relativepath";
1398        if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1399            return false;
1400        }
1401        // finally send the file
1402        // these files are uploaded by students - forcing download for security reasons
1403        send_stored_file($file, 0, 0, true, $options);
1404    }
1405
1406    return false;
1407}
1408
1409/**
1410 * File browsing support for workshop file areas
1411 *
1412 * @package  mod_workshop
1413 * @category files
1414 *
1415 * @param file_browser $browser
1416 * @param array $areas
1417 * @param stdClass $course
1418 * @param stdClass $cm
1419 * @param stdClass $context
1420 * @param string $filearea
1421 * @param int $itemid
1422 * @param string $filepath
1423 * @param string $filename
1424 * @return file_info instance or null if not found
1425 */
1426function workshop_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
1427    global $CFG, $DB, $USER;
1428
1429    /** @var array internal cache for author names */
1430    static $submissionauthors = array();
1431
1432    $fs = get_file_storage();
1433
1434    if ($filearea === 'submission_content' or $filearea === 'submission_attachment') {
1435
1436        if (!has_capability('mod/workshop:viewallsubmissions', $context)) {
1437            return null;
1438        }
1439
1440        if (is_null($itemid)) {
1441            // no itemid (submissionid) passed, display the list of all submissions
1442            require_once($CFG->dirroot . '/mod/workshop/fileinfolib.php');
1443            return new workshop_file_info_submissions_container($browser, $course, $cm, $context, $areas, $filearea);
1444        }
1445
1446        // make sure the user can see the particular submission in separate groups mode
1447        $gmode = groups_get_activity_groupmode($cm, $course);
1448
1449        if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1450            // check there is at least one common group with both the $USER
1451            // and the submission author (this is not expected to be a frequent
1452            // usecase so we can live with pretty ineffective one query per submission here...)
1453            $sql = "SELECT 'x'
1454                      FROM {workshop_submissions} s
1455                      JOIN {user} a ON (a.id = s.authorid)
1456                      JOIN {groups_members} agm ON (a.id = agm.userid)
1457                      JOIN {user} u ON (u.id = ?)
1458                      JOIN {groups_members} ugm ON (u.id = ugm.userid)
1459                     WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1460            $params = array($USER->id, $cm->instance, $itemid);
1461            if (!$DB->record_exists_sql($sql, $params)) {
1462                return null;
1463            }
1464        }
1465
1466        // we are inside some particular submission container
1467
1468        $filepath = is_null($filepath) ? '/' : $filepath;
1469        $filename = is_null($filename) ? '.' : $filename;
1470
1471        if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, $itemid, $filepath, $filename)) {
1472            if ($filepath === '/' and $filename === '.') {
1473                $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, $itemid);
1474            } else {
1475                // not found
1476                return null;
1477            }
1478        }
1479
1480        // Checks to see if the user can manage files or is the owner.
1481        // TODO MDL-33805 - Do not use userid here and move the capability check above.
1482        if (!has_capability('moodle/course:managefiles', $context) && $storedfile->get_userid() != $USER->id) {
1483            return null;
1484        }
1485
1486        // let us display the author's name instead of itemid (submission id)
1487
1488        if (isset($submissionauthors[$itemid])) {
1489            $topvisiblename = $submissionauthors[$itemid];
1490
1491        } else {
1492
1493            $userfields = get_all_user_name_fields(true, 'u');
1494            $sql = "SELECT s.id, $userfields
1495                      FROM {workshop_submissions} s
1496                      JOIN {user} u ON (s.authorid = u.id)
1497                     WHERE s.example = 0 AND s.workshopid = ?";
1498            $params = array($cm->instance);
1499            $rs = $DB->get_recordset_sql($sql, $params);
1500
1501            foreach ($rs as $submissionauthor) {
1502                $title = s(fullname($submissionauthor)); // this is generally not unique...
1503                $submissionauthors[$submissionauthor->id] = $title;
1504            }
1505            $rs->close();
1506
1507            if (!isset($submissionauthors[$itemid])) {
1508                // should not happen
1509                return null;
1510            } else {
1511                $topvisiblename = $submissionauthors[$itemid];
1512            }
1513        }
1514
1515        $urlbase = $CFG->wwwroot . '/pluginfile.php';
1516        // do not allow manual modification of any files!
1517        return new file_info_stored($browser, $context, $storedfile, $urlbase, $topvisiblename, true, true, false, false);
1518    }
1519
1520    if ($filearea === 'overallfeedback_content' or $filearea === 'overallfeedback_attachment') {
1521
1522        if (!has_capability('mod/workshop:viewallassessments', $context)) {
1523            return null;
1524        }
1525
1526        if (is_null($itemid)) {
1527            // No itemid (assessmentid) passed, display the list of all assessments.
1528            require_once($CFG->dirroot . '/mod/workshop/fileinfolib.php');
1529            return new workshop_file_info_overallfeedback_container($browser, $course, $cm, $context, $areas, $filearea);
1530        }
1531
1532        // Make sure the user can see the particular assessment in separate groups mode.
1533        $gmode = groups_get_activity_groupmode($cm, $course);
1534        if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1535            // Check there is at least one common group with both the $USER
1536            // and the submission author.
1537            $sql = "SELECT 'x'
1538                      FROM {workshop_submissions} s
1539                      JOIN {user} a ON (a.id = s.authorid)
1540                      JOIN {groups_members} agm ON (a.id = agm.userid)
1541                      JOIN {user} u ON (u.id = ?)
1542                      JOIN {groups_members} ugm ON (u.id = ugm.userid)
1543                     WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1544            $params = array($USER->id, $cm->instance, $itemid);
1545            if (!$DB->record_exists_sql($sql, $params)) {
1546                return null;
1547            }
1548        }
1549
1550        // We are inside a particular assessment container.
1551        $filepath = is_null($filepath) ? '/' : $filepath;
1552        $filename = is_null($filename) ? '.' : $filename;
1553
1554        if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, $itemid, $filepath, $filename)) {
1555            if ($filepath === '/' and $filename === '.') {
1556                $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, $itemid);
1557            } else {
1558                // Not found
1559                return null;
1560            }
1561        }
1562
1563        // Check to see if the user can manage files or is the owner.
1564        if (!has_capability('moodle/course:managefiles', $context) and $storedfile->get_userid() != $USER->id) {
1565            return null;
1566        }
1567
1568        $urlbase = $CFG->wwwroot . '/pluginfile.php';
1569
1570        // Do not allow manual modification of any files.
1571        return new file_info_stored($browser, $context, $storedfile, $urlbase, $itemid, true, true, false, false);
1572    }
1573
1574    if ($filearea == 'instructauthors' or $filearea == 'instructreviewers' or $filearea == 'conclusion') {
1575        // always only itemid 0
1576
1577        $filepath = is_null($filepath) ? '/' : $filepath;
1578        $filename = is_null($filename) ? '.' : $filename;
1579
1580        $urlbase = $CFG->wwwroot.'/pluginfile.php';
1581        if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, 0, $filepath, $filename)) {
1582            if ($filepath === '/' and $filename === '.') {
1583                $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, 0);
1584            } else {
1585                // not found
1586                return null;
1587            }
1588        }
1589        return new file_info_stored($browser, $context, $storedfile, $urlbase, $areas[$filearea], false, true, true, false);
1590    }
1591}
1592
1593////////////////////////////////////////////////////////////////////////////////
1594// Navigation API                                                             //
1595////////////////////////////////////////////////////////////////////////////////
1596
1597/**
1598 * Extends the global navigation tree by adding workshop nodes if there is a relevant content
1599 *
1600 * This can be called by an AJAX request so do not rely on $PAGE as it might not be set up properly.
1601 *
1602 * @param navigation_node $navref An object representing the navigation tree node of the workshop module instance
1603 * @param stdClass $course
1604 * @param stdClass $module
1605 * @param cm_info $cm
1606 */
1607function workshop_extend_navigation(navigation_node $navref, stdclass $course, stdclass $module, cm_info $cm) {
1608    global $CFG;
1609
1610    if (has_capability('mod/workshop:submit', context_module::instance($cm->id))) {
1611        $url = new moodle_url('/mod/workshop/submission.php', array('cmid' => $cm->id));
1612        $mysubmission = $navref->add(get_string('mysubmission', 'workshop'), $url);
1613        $mysubmission->mainnavonly = true;
1614    }
1615}
1616
1617/**
1618 * Extends the settings navigation with the Workshop settings
1619
1620 * This function is called when the context for the page is a workshop module. This is not called by AJAX
1621 * so it is safe to rely on the $PAGE.
1622 *
1623 * @param settings_navigation $settingsnav {@link settings_navigation}
1624 * @param navigation_node $workshopnode {@link navigation_node}
1625 */
1626function workshop_extend_settings_navigation(settings_navigation $settingsnav, navigation_node $workshopnode=null) {
1627    global $PAGE;
1628
1629    //$workshopobject = $DB->get_record("workshop", array("id" => $PAGE->cm->instance));
1630
1631    if (has_capability('mod/workshop:editdimensions', $PAGE->cm->context)) {
1632        $url = new moodle_url('/mod/workshop/editform.php', array('cmid' => $PAGE->cm->id));
1633        $workshopnode->add(get_string('editassessmentform', 'workshop'), $url, settings_navigation::TYPE_SETTING);
1634    }
1635    if (has_capability('mod/workshop:allocate', $PAGE->cm->context)) {
1636        $url = new moodle_url('/mod/workshop/allocation.php', array('cmid' => $PAGE->cm->id));
1637        $workshopnode->add(get_string('allocate', 'workshop'), $url, settings_navigation::TYPE_SETTING);
1638    }
1639}
1640
1641/**
1642 * Return a list of page types
1643 * @param string $pagetype current page type
1644 * @param stdClass $parentcontext Block's parent context
1645 * @param stdClass $currentcontext Current context of block
1646 */
1647function workshop_page_type_list($pagetype, $parentcontext, $currentcontext) {
1648    $module_pagetype = array('mod-workshop-*'=>get_string('page-mod-workshop-x', 'workshop'));
1649    return $module_pagetype;
1650}
1651
1652////////////////////////////////////////////////////////////////////////////////
1653// Calendar API                                                               //
1654////////////////////////////////////////////////////////////////////////////////
1655
1656/**
1657 * Updates the calendar events associated to the given workshop
1658 *
1659 * @param stdClass $workshop the workshop instance record
1660 * @param int $cmid course module id
1661 */
1662function workshop_calendar_update(stdClass $workshop, $cmid) {
1663    global $DB;
1664
1665    // get the currently registered events so that we can re-use their ids
1666    $currentevents = $DB->get_records('event', array('modulename' => 'workshop', 'instance' => $workshop->id));
1667
1668    // the common properties for all events
1669    $base = new stdClass();
1670    $base->description  = format_module_intro('workshop', $workshop, $cmid, false);
1671    $base->format       = FORMAT_HTML;
1672    $base->courseid     = $workshop->course;
1673    $base->groupid      = 0;
1674    $base->userid       = 0;
1675    $base->modulename   = 'workshop';
1676    $base->instance     = $workshop->id;
1677    $base->visible      = instance_is_visible('workshop', $workshop);
1678    $base->timeduration = 0;
1679
1680    if ($workshop->submissionstart) {
1681        $event = clone($base);
1682        $event->name = get_string('submissionstartevent', 'mod_workshop', $workshop->name);
1683        $event->eventtype = WORKSHOP_EVENT_TYPE_SUBMISSION_OPEN;
1684        $event->type = empty($workshop->submissionend) ? CALENDAR_EVENT_TYPE_ACTION : CALENDAR_EVENT_TYPE_STANDARD;
1685        $event->timestart = $workshop->submissionstart;
1686        $event->timesort  = $workshop->submissionstart;
1687        if ($reusedevent = array_shift($currentevents)) {
1688            $event->id = $reusedevent->id;
1689        } else {
1690            // should not be set but just in case
1691            unset($event->id);
1692        }
1693        // update() will reuse a db record if the id field is set
1694        $eventobj = new calendar_event($event);
1695        $eventobj->update($event, false);
1696    }
1697
1698    if ($workshop->submissionend) {
1699        $event = clone($base);
1700        $event->name = get_string('submissionendevent', 'mod_workshop', $workshop->name);
1701        $event->eventtype = WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE;
1702        $event->type      = CALENDAR_EVENT_TYPE_ACTION;
1703        $event->timestart = $workshop->submissionend;
1704        $event->timesort  = $workshop->submissionend;
1705        if ($reusedevent = array_shift($currentevents)) {
1706            $event->id = $reusedevent->id;
1707        } else {
1708            // should not be set but just in case
1709            unset($event->id);
1710        }
1711        // update() will reuse a db record if the id field is set
1712        $eventobj = new calendar_event($event);
1713        $eventobj->update($event, false);
1714    }
1715
1716    if ($workshop->assessmentstart) {
1717        $event = clone($base);
1718        $event->name = get_string('assessmentstartevent', 'mod_workshop', $workshop->name);
1719        $event->eventtype = WORKSHOP_EVENT_TYPE_ASSESSMENT_OPEN;
1720        $event->type      = empty($workshop->assessmentend) ? CALENDAR_EVENT_TYPE_ACTION : CALENDAR_EVENT_TYPE_STANDARD;
1721        $event->timestart = $workshop->assessmentstart;
1722        $event->timesort  = $workshop->assessmentstart;
1723        if ($reusedevent = array_shift($currentevents)) {
1724            $event->id = $reusedevent->id;
1725        } else {
1726            // should not be set but just in case
1727            unset($event->id);
1728        }
1729        // update() will reuse a db record if the id field is set
1730        $eventobj = new calendar_event($event);
1731        $eventobj->update($event, false);
1732    }
1733
1734    if ($workshop->assessmentend) {
1735        $event = clone($base);
1736        $event->name = get_string('assessmentendevent', 'mod_workshop', $workshop->name);
1737        $event->eventtype = WORKSHOP_EVENT_TYPE_ASSESSMENT_CLOSE;
1738        $event->type      = CALENDAR_EVENT_TYPE_ACTION;
1739        $event->timestart = $workshop->assessmentend;
1740        $event->timesort  = $workshop->assessmentend;
1741        if ($reusedevent = array_shift($currentevents)) {
1742            $event->id = $reusedevent->id;
1743        } else {
1744            // should not be set but just in case
1745            unset($event->id);
1746        }
1747        // update() will reuse a db record if the id field is set
1748        $eventobj = new calendar_event($event);
1749        $eventobj->update($event, false);
1750    }
1751
1752    // delete any leftover events
1753    foreach ($currentevents as $oldevent) {
1754        $oldevent = calendar_event::load($oldevent);
1755        $oldevent->delete();
1756    }
1757}
1758
1759/**
1760 * This function receives a calendar event and returns the action associated with it, or null if there is none.
1761 *
1762 * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event
1763 * is not displayed on the block.
1764 *
1765 * @param calendar_event $event
1766 * @param \core_calendar\action_factory $factory
1767 * @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default).
1768 * @return \core_calendar\local\event\entities\action_interface|null
1769 */
1770function mod_workshop_core_calendar_provide_event_action(calendar_event $event,
1771        \core_calendar\action_factory $factory, int $userid = 0) {
1772    global $USER;
1773
1774    if (!$userid) {
1775        $userid = $USER->id;
1776    }
1777
1778    $cm = get_fast_modinfo($event->courseid, $userid)->instances['workshop'][$event->instance];
1779
1780    if (!$cm->uservisible) {
1781        // The module is not visible to the user for any reason.
1782        return null;
1783    }
1784
1785    $completion = new \completion_info($cm->get_course());
1786
1787    $completiondata = $completion->get_data($cm, false, $userid);
1788
1789    if ($completiondata->completionstate != COMPLETION_INCOMPLETE) {
1790        return null;
1791    }
1792
1793    return $factory->create_instance(
1794        get_string('viewworkshopsummary', 'workshop'),
1795        new \moodle_url('/mod/workshop/view.php', array('id' => $cm->id)),
1796        1,
1797        true
1798    );
1799}
1800
1801/**
1802 * This function calculates the minimum and maximum cutoff values for the timestart of
1803 * the given event.
1804 *
1805 * It will return an array with two values, the first being the minimum cutoff value and
1806 * the second being the maximum cutoff value. Either or both values can be null, which
1807 * indicates there is no minimum or maximum, respectively.
1808 *
1809 * If a cutoff is required then the function must return an array containing the cutoff
1810 * timestamp and error string to display to the user if the cutoff value is violated.
1811 *
1812 * A minimum and maximum cutoff return value will look like:
1813 * [
1814 *     [1505704373, 'The date must be after this date'],
1815 *     [1506741172, 'The date must be before this date']
1816 * ]
1817 *
1818 * @param calendar_event $event The calendar event to get the time range for
1819 * @param stdClass $workshop The module instance to get the range from
1820 * @return array Returns an array with min and max date.
1821 */
1822function mod_workshop_core_calendar_get_valid_event_timestart_range(\calendar_event $event, \stdClass $workshop) : array {
1823    $mindate = null;
1824    $maxdate = null;
1825
1826    $phasesubmissionend = max($workshop->submissionstart, $workshop->submissionend);
1827    $phaseassessmentstart = min($workshop->assessmentstart, $workshop->assessmentend);
1828    if ($phaseassessmentstart == 0) {
1829        $phaseassessmentstart = max($workshop->assessmentstart, $workshop->assessmentend);
1830    }
1831
1832    switch ($event->eventtype) {
1833        case WORKSHOP_EVENT_TYPE_SUBMISSION_OPEN:
1834            if (!empty($workshop->submissionend)) {
1835                $maxdate = [
1836                    $workshop->submissionend - 1,   // The submissionstart and submissionend cannot be exactly the same.
1837                    get_string('submissionendbeforestart', 'mod_workshop')
1838                ];
1839            } else if ($phaseassessmentstart) {
1840                $maxdate = [
1841                    $phaseassessmentstart,
1842                    get_string('phasesoverlap', 'mod_workshop')
1843                ];
1844            }
1845            break;
1846        case WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE:
1847            if (!empty($workshop->submissionstart)) {
1848                $mindate = [
1849                    $workshop->submissionstart + 1, // The submissionstart and submissionend cannot be exactly the same.
1850                    get_string('submissionendbeforestart', 'mod_workshop')
1851                ];
1852            }
1853            if ($phaseassessmentstart) {
1854                $maxdate = [
1855                    $phaseassessmentstart,
1856                    get_string('phasesoverlap', 'mod_workshop')
1857                ];
1858            }
1859            break;
1860        case WORKSHOP_EVENT_TYPE_ASSESSMENT_OPEN:
1861            if ($phasesubmissionend) {
1862                $mindate = [
1863                    $phasesubmissionend,
1864                    get_string('phasesoverlap', 'mod_workshop')
1865                ];
1866            }
1867            if (!empty($workshop->assessmentend)) {
1868                $maxdate = [
1869                    $workshop->assessmentend - 1,   // The assessmentstart and assessmentend cannot be exactly the same.
1870                    get_string('assessmentendbeforestart', 'mod_workshop')
1871                ];
1872            }
1873            break;
1874        case WORKSHOP_EVENT_TYPE_ASSESSMENT_CLOSE:
1875            if (!empty($workshop->assessmentstart)) {
1876                $mindate = [
1877                    $workshop->assessmentstart + 1, // The assessmentstart and assessmentend cannot be exactly the same.
1878                    get_string('assessmentendbeforestart', 'mod_workshop')
1879                ];
1880            } else if ($phasesubmissionend) {
1881                $mindate = [
1882                    $phasesubmissionend,
1883                    get_string('phasesoverlap', 'mod_workshop')
1884                ];
1885            }
1886            break;
1887    }
1888
1889    return [$mindate, $maxdate];
1890}
1891
1892/**
1893 * This function will update the workshop module according to the
1894 * event that has been modified.
1895 *
1896 * @param \calendar_event $event
1897 * @param stdClass $workshop The module instance to get the range from
1898 */
1899function mod_workshop_core_calendar_event_timestart_updated(\calendar_event $event, \stdClass $workshop) : void {
1900    global $DB;
1901
1902    $courseid = $event->courseid;
1903    $modulename = $event->modulename;
1904    $instanceid = $event->instance;
1905
1906    // Something weird going on. The event is for a different module so
1907    // we should ignore it.
1908    if ($modulename != 'workshop') {
1909        return;
1910    }
1911
1912    if ($workshop->id != $instanceid) {
1913        return;
1914    }
1915
1916    if (!in_array(
1917            $event->eventtype,
1918            [
1919                WORKSHOP_EVENT_TYPE_SUBMISSION_OPEN,
1920                WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE,
1921                WORKSHOP_EVENT_TYPE_ASSESSMENT_OPEN,
1922                WORKSHOP_EVENT_TYPE_ASSESSMENT_CLOSE
1923            ]
1924    )) {
1925        return;
1926    }
1927
1928    $coursemodule = get_fast_modinfo($courseid)->instances[$modulename][$instanceid];
1929    $context = context_module::instance($coursemodule->id);
1930
1931    // The user does not have the capability to modify this activity.
1932    if (!has_capability('moodle/course:manageactivities', $context)) {
1933        return;
1934    }
1935
1936    $modified = false;
1937
1938    switch ($event->eventtype) {
1939        case WORKSHOP_EVENT_TYPE_SUBMISSION_OPEN:
1940            if ($event->timestart != $workshop->submissionstart) {
1941                $workshop->submissionstart = $event->timestart;
1942                $modified = true;
1943            }
1944            break;
1945        case WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE:
1946            if ($event->timestart != $workshop->submissionend) {
1947                $workshop->submissionend = $event->timestart;
1948                $modified = true;
1949            }
1950            break;
1951        case WORKSHOP_EVENT_TYPE_ASSESSMENT_OPEN:
1952            if ($event->timestart != $workshop->assessmentstart) {
1953                $workshop->assessmentstart = $event->timestart;
1954                $modified = true;
1955            }
1956            break;
1957        case WORKSHOP_EVENT_TYPE_ASSESSMENT_CLOSE:
1958            if ($event->timestart != $workshop->assessmentend) {
1959                $workshop->assessmentend = $event->timestart;
1960                $modified = true;
1961            }
1962            break;
1963    }
1964
1965    if ($modified) {
1966        $workshop->timemodified = time();
1967        // Persist the assign instance changes.
1968        $DB->update_record('workshop', $workshop);
1969        $event = \core\event\course_module_updated::create_from_cm($coursemodule, $context);
1970        $event->trigger();
1971    }
1972}
1973
1974////////////////////////////////////////////////////////////////////////////////
1975// Course reset API                                                           //
1976////////////////////////////////////////////////////////////////////////////////
1977
1978/**
1979 * Extends the course reset form with workshop specific settings.
1980 *
1981 * @param MoodleQuickForm $mform
1982 */
1983function workshop_reset_course_form_definition($mform) {
1984
1985    $mform->addElement('header', 'workshopheader', get_string('modulenameplural', 'mod_workshop'));
1986
1987    $mform->addElement('advcheckbox', 'reset_workshop_submissions', get_string('resetsubmissions', 'mod_workshop'));
1988    $mform->addHelpButton('reset_workshop_submissions', 'resetsubmissions', 'mod_workshop');
1989
1990    $mform->addElement('advcheckbox', 'reset_workshop_assessments', get_string('resetassessments', 'mod_workshop'));
1991    $mform->addHelpButton('reset_workshop_assessments', 'resetassessments', 'mod_workshop');
1992    $mform->disabledIf('reset_workshop_assessments', 'reset_workshop_submissions', 'checked');
1993
1994    $mform->addElement('advcheckbox', 'reset_workshop_phase', get_string('resetphase', 'mod_workshop'));
1995    $mform->addHelpButton('reset_workshop_phase', 'resetphase', 'mod_workshop');
1996}
1997
1998/**
1999 * Provides default values for the workshop settings in the course reset form.
2000 *
2001 * @param stdClass $course The course to be reset.
2002 */
2003function workshop_reset_course_form_defaults(stdClass $course) {
2004
2005    $defaults = array(
2006        'reset_workshop_submissions'    => 1,
2007        'reset_workshop_assessments'    => 1,
2008        'reset_workshop_phase'          => 1,
2009    );
2010
2011    return $defaults;
2012}
2013
2014/**
2015 * Performs the reset of all workshop instances in the course.
2016 *
2017 * @param stdClass $data The actual course reset settings.
2018 * @return array List of results, each being array[(string)component, (string)item, (string)error]
2019 */
2020function workshop_reset_userdata(stdClass $data) {
2021    global $CFG, $DB;
2022
2023    // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset.
2024    // See MDL-9367.
2025    shift_course_mod_dates('workshop', array('submissionstart', 'submissionend', 'assessmentstart', 'assessmentend'),
2026        $data->timeshift, $data->courseid);
2027    $status = array();
2028    $status[] = array('component' => get_string('modulenameplural', 'workshop'), 'item' => get_string('datechanged'),
2029        'error' => false);
2030
2031    if (empty($data->reset_workshop_submissions)
2032            and empty($data->reset_workshop_assessments)
2033            and empty($data->reset_workshop_phase) ) {
2034        // Nothing to do here.
2035        return $status;
2036    }
2037
2038    $workshoprecords = $DB->get_records('workshop', array('course' => $data->courseid));
2039
2040    if (empty($workshoprecords)) {
2041        // What a boring course - no workshops here!
2042        return $status;
2043    }
2044
2045    require_once($CFG->dirroot . '/mod/workshop/locallib.php');
2046
2047    $course = $DB->get_record('course', array('id' => $data->courseid), '*', MUST_EXIST);
2048
2049    foreach ($workshoprecords as $workshoprecord) {
2050        $cm = get_coursemodule_from_instance('workshop', $workshoprecord->id, $course->id, false, MUST_EXIST);
2051        $workshop = new workshop($workshoprecord, $cm, $course);
2052        $status = array_merge($status, $workshop->reset_userdata($data));
2053    }
2054
2055    return $status;
2056}
2057
2058/**
2059 * Get icon mapping for font-awesome.
2060 */
2061function mod_workshop_get_fontawesome_icon_map() {
2062    return [
2063        'mod_workshop:userplan/task-info' => 'fa-info text-info',
2064        'mod_workshop:userplan/task-todo' => 'fa-square-o',
2065        'mod_workshop:userplan/task-done' => 'fa-check text-success',
2066        'mod_workshop:userplan/task-fail' => 'fa-remove text-danger',
2067    ];
2068}
2069
2070/**
2071 * Check if the module has any update that affects the current user since a given time.
2072 *
2073 * @param  cm_info $cm course module data
2074 * @param  int $from the time to check updates from
2075 * @param  array $filter  if we need to check only specific updates
2076 * @return stdClass an object with the different type of areas indicating if they were updated or not
2077 * @since Moodle 3.4
2078 */
2079function workshop_check_updates_since(cm_info $cm, $from, $filter = array()) {
2080    global $DB, $USER;
2081
2082    $updates = course_check_module_updates_since($cm, $from, array('instructauthors', 'instructreviewers', 'conclusion'), $filter);
2083
2084    // Check if there are new submissions, assessments or assessments grades in the workshop.
2085    $updates->submissions = (object) array('updated' => false);
2086    $updates->assessments = (object) array('updated' => false);
2087    $updates->assessmentgrades = (object) array('updated' => false);
2088
2089    $select = 'workshopid = ? AND authorid = ? AND (timecreated > ? OR timegraded > ? OR timemodified > ?)';
2090    $params = array($cm->instance, $USER->id, $from, $from, $from);
2091    $submissions = $DB->get_records_select('workshop_submissions', $select, $params, '', 'id');
2092    if (!empty($submissions)) {
2093        $updates->submissions->updated = true;
2094        $updates->submissions->itemids = array_keys($submissions);
2095    }
2096
2097    // Get assessments updates (both submissions reviewed by me or reviews by others).
2098    $select = "SELECT a.id
2099                 FROM {workshop_assessments} a
2100                 JOIN {workshop_submissions} s ON a.submissionid = s.id
2101                 WHERE s.workshopid = ? AND (a.timecreated > ? OR a.timemodified > ?) AND (s.authorid = ? OR a.reviewerid = ?)";
2102    $params = array($cm->instance, $from, $from, $USER->id, $USER->id);
2103    $assessments = $DB->get_records_sql($select, $params);
2104    if (!empty($assessments)) {
2105        $updates->assessments->updated = true;
2106        $updates->assessments->itemids = array_keys($assessments);
2107    }
2108    // Finally assessment aggregated grades.
2109    $select = 'workshopid = ? AND userid = ? AND timegraded > ?';
2110    $params = array($cm->instance, $USER->id, $from);
2111    $assessmentgrades = $DB->get_records_select('workshop_aggregations', $select, $params, '', 'id');
2112    if (!empty($assessmentgrades)) {
2113        $updates->assessmentgrades->updated = true;
2114        $updates->assessmentgrades->itemids = array_keys($assessmentgrades);
2115    }
2116
2117    // Now, teachers should see other students updates.
2118    $canviewallsubmissions = has_capability('mod/workshop:viewallsubmissions', $cm->context);
2119    $canviewallassessments = has_capability('mod/workshop:viewallassessments', $cm->context);
2120    if ($canviewallsubmissions || $canviewallassessments) {
2121
2122        $insql = '';
2123        $inparams = array();
2124        // To filter by users in my groups when separated groups are forced.
2125        if (groups_get_activity_groupmode($cm) == SEPARATEGROUPS) {
2126            $groupusers = array_keys(groups_get_activity_shared_group_members($cm));
2127            if (empty($groupusers)) {
2128                return $updates;
2129            }
2130            list($insql, $inparams) = $DB->get_in_or_equal($groupusers);
2131        }
2132
2133        if ($canviewallsubmissions) {
2134            $updates->usersubmissions = (object) array('updated' => false);
2135            $select = 'workshopid = ? AND (timecreated > ? OR timegraded > ? OR timemodified > ?)';
2136            $params = array($cm->instance, $from, $from, $from);
2137            if (!empty($insql)) {
2138                $select .= " AND authorid $insql";
2139                $params = array_merge($params, $inparams);
2140            }
2141            $usersubmissions = $DB->get_records_select('workshop_submissions', $select, $params, '', 'id');
2142            if (!empty($usersubmissions)) {
2143                $updates->usersubmissions->updated = true;
2144                $updates->usersubmissions->itemids = array_keys($usersubmissions);
2145            }
2146        }
2147
2148        if ($canviewallassessments) {
2149            $updates->userassessments = (object) array('updated' => false);
2150            $select = "SELECT a.id
2151                         FROM {workshop_assessments} a
2152                         JOIN {workshop_submissions} s ON a.submissionid = s.id
2153                        WHERE s.workshopid = ? AND (a.timecreated > ? OR a.timemodified > ?)";
2154            $params = array($cm->instance, $from, $from);
2155            if (!empty($insql)) {
2156                $select .= " AND s.reviewerid $insql";
2157                $params = array_merge($params, $inparams);
2158            }
2159            $userassessments = $DB->get_records_sql($select, $params);
2160            if (!empty($userassessments)) {
2161                $updates->userassessments->updated = true;
2162                $updates->userassessments->itemids = array_keys($userassessments);
2163            }
2164
2165            $updates->userassessmentgrades = (object) array('updated' => false);
2166            $select = 'workshopid = ? AND timegraded > ?';
2167            $params = array($cm->instance, $USER->id);
2168            if (!empty($insql)) {
2169                $select .= " AND userid $insql";
2170                $params = array_merge($params, $inparams);
2171            }
2172            $userassessmentgrades = $DB->get_records_select('workshop_aggregations', $select, $params, '', 'id');
2173            if (!empty($userassessmentgrades)) {
2174                $updates->userassessmentgrades->updated = true;
2175                $updates->userassessmentgrades->itemids = array_keys($userassessmentgrades);
2176            }
2177        }
2178    }
2179    return $updates;
2180}
2181
2182/**
2183 * Given an array with a file path, it returns the itemid and the filepath for the defined filearea.
2184 *
2185 * @param  string $filearea The filearea.
2186 * @param  array  $args The path (the part after the filearea and before the filename).
2187 * @return array|null The itemid and the filepath inside the $args path, for the defined filearea.
2188 */
2189function mod_workshop_get_path_from_pluginfile(string $filearea, array $args) : ?array {
2190    if ($filearea !== 'instructauthors' && $filearea !== 'instructreviewers' && $filearea !== 'conclusion') {
2191        return null;
2192    }
2193
2194    // Workshop only has empty itemid for some of the fileareas.
2195    array_shift($args);
2196
2197    // Get the filepath.
2198    if (empty($args)) {
2199        $filepath = '/';
2200    } else {
2201        $filepath = '/' . implode('/', $args) . '/';
2202    }
2203
2204    return [
2205        'itemid' => 0,
2206        'filepath' => $filepath,
2207    ];
2208}
2209