3 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
19 * Library of workshop module functions needed by Moodle core and other subsystems
21 * All the functions neeeded by Moodle core, gradebook, file subsystem etc
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
29 defined('MOODLE_INTERNAL') || die();
31 require_once($CFG->dirroot . '/calendar/lib.php');
33 define('WORKSHOP_EVENT_TYPE_SUBMISSION_OPEN', 'opensubmission');
34 define('WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE', 'closesubmission');
35 define('WORKSHOP_EVENT_TYPE_ASSESSMENT_OPEN', 'openassessment');
36 define('WORKSHOP_EVENT_TYPE_ASSESSMENT_CLOSE', 'closeassessment');
38 ////////////////////////////////////////////////////////////////////////////////
40 ////////////////////////////////////////////////////////////////////////////////
43 * Returns the information if the module supports a feature
45 * @see plugin_supports() in lib/moodlelib.php
46 * @param string $feature FEATURE_xx constant for requested feature
47 * @return mixed true if the feature is supported, null if unknown
49 function workshop_supports($feature) {
51 case FEATURE_GRADE_HAS_GRADE: return true;
52 case FEATURE_GROUPS: return true;
53 case FEATURE_GROUPINGS: return true;
54 case FEATURE_MOD_INTRO: return true;
55 case FEATURE_BACKUP_MOODLE2: return true;
56 case FEATURE_COMPLETION_TRACKS_VIEWS:
58 case FEATURE_SHOW_DESCRIPTION: return true;
59 case FEATURE_PLAGIARISM: return true;
65 * Saves a new instance of the workshop into the database
67 * Given an object containing all the necessary data,
68 * (defined by the form in mod_form.php) this function
69 * will save a new instance and return the id number
70 * of the new instance.
72 * @param stdClass $workshop An object from the form in mod_form.php
73 * @return int The id of the newly inserted workshop record
75 function workshop_add_instance(stdclass $workshop) {
77 require_once(__DIR__ . '/locallib.php');
79 $workshop->phase = workshop::PHASE_SETUP;
80 $workshop->timecreated = time();
81 $workshop->timemodified = $workshop->timecreated;
82 $workshop->useexamples = (int)!empty($workshop->useexamples);
83 $workshop->usepeerassessment = 1;
84 $workshop->useselfassessment = (int)!empty($workshop->useselfassessment);
85 $workshop->latesubmissions = (int)!empty($workshop->latesubmissions);
86 $workshop->phaseswitchassessment = (int)!empty($workshop->phaseswitchassessment);
87 $workshop->evaluation = 'best';
89 if (isset($workshop->gradinggradepass)) {
90 $workshop->gradinggradepass = (float)unformat_float($workshop->gradinggradepass);
93 if (isset($workshop->submissiongradepass)) {
94 $workshop->submissiongradepass = (float)unformat_float($workshop->submissiongradepass);
97 if (isset($workshop->submissionfiletypes)) {
98 $filetypesutil = new \core_form\filetypes_util();
99 $submissionfiletypes = $filetypesutil->normalize_file_types($workshop->submissionfiletypes);
100 $workshop->submissionfiletypes = implode(' ', $submissionfiletypes);
103 if (isset($workshop->overallfeedbackfiletypes)) {
104 $filetypesutil = new \core_form\filetypes_util();
105 $overallfeedbackfiletypes = $filetypesutil->normalize_file_types($workshop->overallfeedbackfiletypes);
106 $workshop->overallfeedbackfiletypes = implode(' ', $overallfeedbackfiletypes);
109 // insert the new record so we get the id
110 $workshop->id = $DB->insert_record('workshop', $workshop);
112 // we need to use context now, so we need to make sure all needed info is already in db
113 $cmid = $workshop->coursemodule;
114 $DB->set_field('course_modules', 'instance', $workshop->id, array('id' => $cmid));
115 $context = context_module::instance($cmid);
117 // process the custom wysiwyg editors
118 if ($draftitemid = $workshop->instructauthorseditor['itemid']) {
119 $workshop->instructauthors = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructauthors',
120 0, workshop::instruction_editors_options($context), $workshop->instructauthorseditor['text']);
121 $workshop->instructauthorsformat = $workshop->instructauthorseditor['format'];
124 if ($draftitemid = $workshop->instructreviewerseditor['itemid']) {
125 $workshop->instructreviewers = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructreviewers',
126 0, workshop::instruction_editors_options($context), $workshop->instructreviewerseditor['text']);
127 $workshop->instructreviewersformat = $workshop->instructreviewerseditor['format'];
130 if ($draftitemid = $workshop->conclusioneditor['itemid']) {
131 $workshop->conclusion = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'conclusion',
132 0, workshop::instruction_editors_options($context), $workshop->conclusioneditor['text']);
133 $workshop->conclusionformat = $workshop->conclusioneditor['format'];
136 // re-save the record with the replaced URLs in editor fields
137 $DB->update_record('workshop', $workshop);
139 // create gradebook items
140 workshop_grade_item_update($workshop);
141 workshop_grade_item_category_update($workshop);
143 // create calendar events
144 workshop_calendar_update($workshop, $workshop->coursemodule);
146 return $workshop->id;
150 * Given an object containing all the necessary data,
151 * (defined by the form in mod_form.php) this function
152 * will update an existing instance with new data.
154 * @param stdClass $workshop An object from the form in mod_form.php
155 * @return bool success
157 function workshop_update_instance(stdclass $workshop) {
159 require_once(__DIR__ . '/locallib.php');
161 $workshop->timemodified = time();
162 $workshop->id = $workshop->instance;
163 $workshop->useexamples = (int)!empty($workshop->useexamples);
164 $workshop->usepeerassessment = 1;
165 $workshop->useselfassessment = (int)!empty($workshop->useselfassessment);
166 $workshop->latesubmissions = (int)!empty($workshop->latesubmissions);
167 $workshop->phaseswitchassessment = (int)!empty($workshop->phaseswitchassessment);
169 if (isset($workshop->gradinggradepass)) {
170 $workshop->gradinggradepass = (float)unformat_float($workshop->gradinggradepass);
173 if (isset($workshop->submissiongradepass)) {
174 $workshop->submissiongradepass = (float)unformat_float($workshop->submissiongradepass);
177 if (isset($workshop->submissionfiletypes)) {
178 $filetypesutil = new \core_form\filetypes_util();
179 $submissionfiletypes = $filetypesutil->normalize_file_types($workshop->submissionfiletypes);
180 $workshop->submissionfiletypes = implode(' ', $submissionfiletypes);
183 if (isset($workshop->overallfeedbackfiletypes)) {
184 $filetypesutil = new \core_form\filetypes_util();
185 $overallfeedbackfiletypes = $filetypesutil->normalize_file_types($workshop->overallfeedbackfiletypes);
186 $workshop->overallfeedbackfiletypes = implode(' ', $overallfeedbackfiletypes);
189 // todo - if the grading strategy is being changed, we may want to replace all aggregated peer grades with nulls
191 $DB->update_record('workshop', $workshop);
192 $context = context_module::instance($workshop->coursemodule);
194 // process the custom wysiwyg editors
195 if ($draftitemid = $workshop->instructauthorseditor['itemid']) {
196 $workshop->instructauthors = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructauthors',
197 0, workshop::instruction_editors_options($context), $workshop->instructauthorseditor['text']);
198 $workshop->instructauthorsformat = $workshop->instructauthorseditor['format'];
201 if ($draftitemid = $workshop->instructreviewerseditor['itemid']) {
202 $workshop->instructreviewers = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructreviewers',
203 0, workshop::instruction_editors_options($context), $workshop->instructreviewerseditor['text']);
204 $workshop->instructreviewersformat = $workshop->instructreviewerseditor['format'];
207 if ($draftitemid = $workshop->conclusioneditor['itemid']) {
208 $workshop->conclusion = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'conclusion',
209 0, workshop::instruction_editors_options($context), $workshop->conclusioneditor['text']);
210 $workshop->conclusionformat = $workshop->conclusioneditor['format'];
213 // re-save the record with the replaced URLs in editor fields
214 $DB->update_record('workshop', $workshop);
216 // update gradebook items
217 workshop_grade_item_update($workshop);
218 workshop_grade_item_category_update($workshop);
220 // update calendar events
221 workshop_calendar_update($workshop, $workshop->coursemodule);
227 * Given an ID of an instance of this module,
228 * this function will permanently delete the instance
229 * and any data that depends on it.
231 * @param int $id Id of the module instance
232 * @return boolean Success/Failure
234 function workshop_delete_instance($id) {
236 require_once($CFG->libdir.'/gradelib.php');
238 if (! $workshop = $DB->get_record('workshop', array('id' => $id))) {
242 // delete all associated aggregations
243 $DB->delete_records('workshop_aggregations', array('workshopid' => $workshop->id));
245 // get the list of ids of all submissions
246 $submissions = $DB->get_records('workshop_submissions', array('workshopid' => $workshop->id), '', 'id');
248 // get the list of all allocated assessments
249 $assessments = $DB->get_records_list('workshop_assessments', 'submissionid', array_keys($submissions), '', 'id');
251 // delete the associated records from the workshop core tables
252 $DB->delete_records_list('workshop_grades', 'assessmentid', array_keys($assessments));
253 $DB->delete_records_list('workshop_assessments', 'id', array_keys($assessments));
254 $DB->delete_records_list('workshop_submissions', 'id', array_keys($submissions));
256 // call the static clean-up methods of all available subplugins
257 $strategies = core_component::get_plugin_list('workshopform');
258 foreach ($strategies as $strategy => $path) {
259 require_once($path.'/lib.php');
260 $classname = 'workshop_'.$strategy.'_strategy';
261 call_user_func($classname.'::delete_instance', $workshop->id);
264 $allocators = core_component::get_plugin_list('workshopallocation');
265 foreach ($allocators as $allocator => $path) {
266 require_once($path.'/lib.php');
267 $classname = 'workshop_'.$allocator.'_allocator';
268 call_user_func($classname.'::delete_instance', $workshop->id);
271 $evaluators = core_component::get_plugin_list('workshopeval');
272 foreach ($evaluators as $evaluator => $path) {
273 require_once($path.'/lib.php');
274 $classname = 'workshop_'.$evaluator.'_evaluation';
275 call_user_func($classname.'::delete_instance', $workshop->id);
278 // delete the calendar events
279 $events = $DB->get_records('event', array('modulename' => 'workshop', 'instance' => $workshop->id));
280 foreach ($events as $event) {
281 $event = calendar_event::load($event);
285 // finally remove the workshop record itself
286 $DB->delete_records('workshop', array('id' => $workshop->id));
289 grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 0, null, array('deleted' => true));
290 grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 1, null, array('deleted' => true));
296 * This standard function will check all instances of this module
297 * and make sure there are up-to-date events created for each of them.
298 * If courseid = 0, then every workshop event in the site is checked, else
299 * only workshop events belonging to the course specified are checked.
301 * @param integer $courseid The Course ID.
302 * @return bool Returns true if the calendar events were successfully updated.
304 function workshop_refresh_events($courseid = 0) {
308 // Make sure that the course id is numeric.
309 if (!is_numeric($courseid)) {
312 if (!$workshops = $DB->get_records('workshop', array('course' => $courseid))) {
316 if (!$workshops = $DB->get_records('workshop')) {
320 foreach ($workshops as $workshop) {
321 if (!$cm = get_coursemodule_from_instance('workshop', $workshop->id, $courseid, false)) {
324 workshop_calendar_update($workshop, $cm->id);
330 * List the actions that correspond to a view of this module.
331 * This is used by the participation report.
333 * Note: This is not used by new logging system. Event with
334 * crud = 'r' and edulevel = LEVEL_PARTICIPATING will
335 * be considered as view action.
339 function workshop_get_view_actions() {
340 return array('view', 'view all', 'view submission', 'view example');
344 * List the actions that correspond to a post of this module.
345 * This is used by the participation report.
347 * Note: This is not used by new logging system. Event with
348 * crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING
349 * will be considered as post action.
353 function workshop_get_post_actions() {
354 return array('add', 'add assessment', 'add example', 'add submission',
355 'update', 'update assessment', 'update example', 'update submission');
359 * Return a small object with summary information about what a
360 * user has done with a given particular instance of this module
361 * Used for user activity reports.
362 * $return->time = the time they did it
363 * $return->info = a short text description
365 * @param stdClass $course The course record.
366 * @param stdClass $user The user record.
367 * @param cm_info|stdClass $mod The course module info object or record.
368 * @param stdClass $workshop The workshop instance record.
369 * @return stdclass|null
371 function workshop_user_outline($course, $user, $mod, $workshop) {
373 require_once($CFG->libdir.'/gradelib.php');
375 $grades = grade_get_grades($course->id, 'mod', 'workshop', $workshop->id, $user->id);
377 $submissiongrade = null;
378 $assessmentgrade = null;
383 if (!empty($grades->items[0]->grades)) {
384 $submissiongrade = reset($grades->items[0]->grades);
385 $info .= get_string('submissiongrade', 'workshop') . ': ' . $submissiongrade->str_long_grade . html_writer::empty_tag('br');
386 $time = max($time, $submissiongrade->dategraded);
388 if (!empty($grades->items[1]->grades)) {
389 $assessmentgrade = reset($grades->items[1]->grades);
390 $info .= get_string('gradinggrade', 'workshop') . ': ' . $assessmentgrade->str_long_grade;
391 $time = max($time, $assessmentgrade->dategraded);
394 if (!empty($info) and !empty($time)) {
395 $return = new stdclass();
396 $return->time = $time;
397 $return->info = $info;
405 * Print a detailed representation of what a user has done with
406 * a given particular instance of this module, for user activity reports.
408 * @param stdClass $course The course record.
409 * @param stdClass $user The user record.
410 * @param cm_info|stdClass $mod The course module info object or record.
411 * @param stdClass $workshop The workshop instance record.
412 * @return string HTML
414 function workshop_user_complete($course, $user, $mod, $workshop) {
415 global $CFG, $DB, $OUTPUT;
416 require_once(__DIR__.'/locallib.php');
417 require_once($CFG->libdir.'/gradelib.php');
419 $workshop = new workshop($workshop, $mod, $course);
420 $grades = grade_get_grades($course->id, 'mod', 'workshop', $workshop->id, $user->id);
422 if (!empty($grades->items[0]->grades)) {
423 $submissiongrade = reset($grades->items[0]->grades);
424 $info = get_string('submissiongrade', 'workshop') . ': ' . $submissiongrade->str_long_grade;
425 echo html_writer::tag('li', $info, array('class'=>'submissiongrade'));
427 if (!empty($grades->items[1]->grades)) {
428 $assessmentgrade = reset($grades->items[1]->grades);
429 $info = get_string('gradinggrade', 'workshop') . ': ' . $assessmentgrade->str_long_grade;
430 echo html_writer::tag('li', $info, array('class'=>'gradinggrade'));
433 if (has_capability('mod/workshop:viewallsubmissions', $workshop->context)) {
434 $canviewsubmission = true;
435 if (groups_get_activity_groupmode($workshop->cm) == SEPARATEGROUPS) {
436 // user must have accessallgroups or share at least one group with the submission author
437 if (!has_capability('moodle/site:accessallgroups', $workshop->context)) {
438 $usersgroups = groups_get_activity_allowed_groups($workshop->cm);
439 $authorsgroups = groups_get_all_groups($workshop->course->id, $user->id, $workshop->cm->groupingid, 'g.id');
440 $sharedgroups = array_intersect_key($usersgroups, $authorsgroups);
441 if (empty($sharedgroups)) {
442 $canviewsubmission = false;
446 if ($canviewsubmission and $submission = $workshop->get_submission_by_author($user->id)) {
447 $title = format_string($submission->title);
448 $url = $workshop->submission_url($submission->id);
449 $link = html_writer::link($url, $title);
450 $info = get_string('submission', 'workshop').': '.$link;
451 echo html_writer::tag('li', $info, array('class'=>'submission'));
455 if (has_capability('mod/workshop:viewallassessments', $workshop->context)) {
456 if ($assessments = $workshop->get_assessments_by_reviewer($user->id)) {
457 foreach ($assessments as $assessment) {
459 $a->submissionurl = $workshop->submission_url($assessment->submissionid)->out();
460 $a->assessmenturl = $workshop->assess_url($assessment->id)->out();
461 $a->submissiontitle = s($assessment->submissiontitle);
462 echo html_writer::tag('li', get_string('assessmentofsubmission', 'workshop', $a));
469 * Given a course and a time, this module should find recent activity
470 * that has occurred in workshop activities and print it out.
471 * Return true if there was output, or false is there was none.
473 * @param stdClass $course
474 * @param bool $viewfullnames
475 * @param int $timestart
478 function workshop_print_recent_activity($course, $viewfullnames, $timestart) {
479 global $CFG, $USER, $DB, $OUTPUT;
481 $authoramefields = get_all_user_name_fields(true, 'author', null, 'author');
482 $reviewerfields = get_all_user_name_fields(true, 'reviewer', null, 'reviewer');
484 $sql = "SELECT s.id AS submissionid, s.title AS submissiontitle, s.timemodified AS submissionmodified,
485 author.id AS authorid, $authoramefields, a.id AS assessmentid, a.timemodified AS assessmentmodified,
486 reviewer.id AS reviewerid, $reviewerfields, cm.id AS cmid
488 INNER JOIN {course_modules} cm ON cm.instance = w.id
489 INNER JOIN {modules} md ON md.id = cm.module
490 INNER JOIN {workshop_submissions} s ON s.workshopid = w.id
491 INNER JOIN {user} author ON s.authorid = author.id
492 LEFT JOIN {workshop_assessments} a ON a.submissionid = s.id
493 LEFT JOIN {user} reviewer ON a.reviewerid = reviewer.id
495 AND md.name = 'workshop'
497 AND (s.timemodified > ? OR a.timemodified > ?)
498 ORDER BY s.timemodified";
500 $rs = $DB->get_recordset_sql($sql, array($course->id, $timestart, $timestart));
502 $modinfo = get_fast_modinfo($course); // reference needed because we might load the groups
504 $submissions = array(); // recent submissions indexed by submission id
505 $assessments = array(); // recent assessments indexed by assessment id
508 foreach ($rs as $activity) {
509 if (!array_key_exists($activity->cmid, $modinfo->cms)) {
510 // this should not happen but just in case
514 $cm = $modinfo->cms[$activity->cmid];
515 if (!$cm->uservisible) {
519 // remember all user names we can use later
520 if (empty($users[$activity->authorid])) {
522 $users[$activity->authorid] = username_load_fields_from_object($u, $activity, 'author');
524 if ($activity->reviewerid and empty($users[$activity->reviewerid])) {
526 $users[$activity->reviewerid] = username_load_fields_from_object($u, $activity, 'reviewer');
529 $context = context_module::instance($cm->id);
530 $groupmode = groups_get_activity_groupmode($cm, $course);
532 if ($activity->submissionmodified > $timestart and empty($submissions[$activity->submissionid])) {
534 $s->title = $activity->submissiontitle;
535 $s->authorid = $activity->authorid;
536 $s->timemodified = $activity->submissionmodified;
537 $s->cmid = $activity->cmid;
538 if ($activity->authorid == $USER->id || has_capability('mod/workshop:viewauthornames', $context)) {
539 $s->authornamevisible = true;
541 $s->authornamevisible = false;
544 // the following do-while wrapper allows to break from deeply nested if-statements
546 if ($s->authorid === $USER->id) {
547 // own submissions always visible
548 $submissions[$activity->submissionid] = $s;
552 if (has_capability('mod/workshop:viewallsubmissions', $context)) {
553 if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
555 // shortcut - guest user does not belong into any group
559 // this might be slow - show only submissions by users who share group with me in this cm
560 if (!$modinfo->get_groups($cm->groupingid)) {
563 $authorsgroups = groups_get_all_groups($course->id, $s->authorid, $cm->groupingid);
564 if (is_array($authorsgroups)) {
565 $authorsgroups = array_keys($authorsgroups);
566 $intersect = array_intersect($authorsgroups, $modinfo->get_groups($cm->groupingid));
567 if (empty($intersect)) {
570 // can see all submissions and shares a group with the author
571 $submissions[$activity->submissionid] = $s;
577 // can see all submissions from all groups
578 $submissions[$activity->submissionid] = $s;
584 if ($activity->assessmentmodified > $timestart and empty($assessments[$activity->assessmentid])) {
586 $a->submissionid = $activity->submissionid;
587 $a->submissiontitle = $activity->submissiontitle;
588 $a->reviewerid = $activity->reviewerid;
589 $a->timemodified = $activity->assessmentmodified;
590 $a->cmid = $activity->cmid;
591 if ($activity->reviewerid == $USER->id || has_capability('mod/workshop:viewreviewernames', $context)) {
592 $a->reviewernamevisible = true;
594 $a->reviewernamevisible = false;
597 // the following do-while wrapper allows to break from deeply nested if-statements
599 if ($a->reviewerid === $USER->id) {
600 // own assessments always visible
601 $assessments[$activity->assessmentid] = $a;
605 if (has_capability('mod/workshop:viewallassessments', $context)) {
606 if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
608 // shortcut - guest user does not belong into any group
612 // this might be slow - show only submissions by users who share group with me in this cm
613 if (!$modinfo->get_groups($cm->groupingid)) {
616 $reviewersgroups = groups_get_all_groups($course->id, $a->reviewerid, $cm->groupingid);
617 if (is_array($reviewersgroups)) {
618 $reviewersgroups = array_keys($reviewersgroups);
619 $intersect = array_intersect($reviewersgroups, $modinfo->get_groups($cm->groupingid));
620 if (empty($intersect)) {
623 // can see all assessments and shares a group with the reviewer
624 $assessments[$activity->assessmentid] = $a;
630 // can see all assessments from all groups
631 $assessments[$activity->assessmentid] = $a;
641 if (!empty($submissions)) {
643 echo $OUTPUT->heading(get_string('recentsubmissions', 'workshop'), 3);
644 foreach ($submissions as $id => $submission) {
645 $link = new moodle_url('/mod/workshop/submission.php', array('id'=>$id, 'cmid'=>$submission->cmid));
646 if ($submission->authornamevisible) {
647 $author = $users[$submission->authorid];
651 print_recent_activity_note($submission->timemodified, $author, $submission->title, $link->out(), false, $viewfullnames);
655 if (!empty($assessments)) {
657 echo $OUTPUT->heading(get_string('recentassessments', 'workshop'), 3);
658 core_collator::asort_objects_by_property($assessments, 'timemodified');
659 foreach ($assessments as $id => $assessment) {
660 $link = new moodle_url('/mod/workshop/assessment.php', array('asid' => $id));
661 if ($assessment->reviewernamevisible) {
662 $reviewer = $users[$assessment->reviewerid];
666 print_recent_activity_note($assessment->timemodified, $reviewer, $assessment->submissiontitle, $link->out(), false, $viewfullnames);
678 * Returns all activity in course workshops since a given time
680 * @param array $activities sequentially indexed array of objects
682 * @param int $timestart
683 * @param int $courseid
685 * @param int $userid defaults to 0
686 * @param int $groupid defaults to 0
687 * @return void adds items into $activities and increases $index
689 function workshop_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid=0, $groupid=0) {
690 global $CFG, $COURSE, $USER, $DB;
692 if ($COURSE->id == $courseid) {
695 $course = $DB->get_record('course', array('id'=>$courseid));
698 $modinfo = get_fast_modinfo($course);
700 $cm = $modinfo->cms[$cmid];
704 $userselect = "AND (author.id = :authorid OR reviewer.id = :reviewerid)";
705 $params['authorid'] = $userid;
706 $params['reviewerid'] = $userid;
712 $groupselect = "AND (authorgroupmembership.groupid = :authorgroupid OR reviewergroupmembership.groupid = :reviewergroupid)";
713 $groupjoin = "LEFT JOIN {groups_members} authorgroupmembership ON authorgroupmembership.userid = author.id
714 LEFT JOIN {groups_members} reviewergroupmembership ON reviewergroupmembership.userid = reviewer.id";
715 $params['authorgroupid'] = $groupid;
716 $params['reviewergroupid'] = $groupid;
722 $params['cminstance'] = $cm->instance;
723 $params['submissionmodified'] = $timestart;
724 $params['assessmentmodified'] = $timestart;
726 $authornamefields = get_all_user_name_fields(true, 'author', null, 'author');
727 $reviewerfields = get_all_user_name_fields(true, 'reviewer', null, 'reviewer');
729 $sql = "SELECT s.id AS submissionid, s.title AS submissiontitle, s.timemodified AS submissionmodified,
730 author.id AS authorid, $authornamefields, author.picture AS authorpicture, author.imagealt AS authorimagealt,
731 author.email AS authoremail, a.id AS assessmentid, a.timemodified AS assessmentmodified,
732 reviewer.id AS reviewerid, $reviewerfields, reviewer.picture AS reviewerpicture,
733 reviewer.imagealt AS reviewerimagealt, reviewer.email AS revieweremail
734 FROM {workshop_submissions} s
735 INNER JOIN {workshop} w ON s.workshopid = w.id
736 INNER JOIN {user} author ON s.authorid = author.id
737 LEFT JOIN {workshop_assessments} a ON a.submissionid = s.id
738 LEFT JOIN {user} reviewer ON a.reviewerid = reviewer.id
740 WHERE w.id = :cminstance
742 $userselect $groupselect
743 AND (s.timemodified > :submissionmodified OR a.timemodified > :assessmentmodified)
744 ORDER BY s.timemodified ASC, a.timemodified ASC";
746 $rs = $DB->get_recordset_sql($sql, $params);
748 $groupmode = groups_get_activity_groupmode($cm, $course);
749 $context = context_module::instance($cm->id);
750 $grader = has_capability('moodle/grade:viewall', $context);
751 $accessallgroups = has_capability('moodle/site:accessallgroups', $context);
752 $viewauthors = has_capability('mod/workshop:viewauthornames', $context);
753 $viewreviewers = has_capability('mod/workshop:viewreviewernames', $context);
755 $submissions = array(); // recent submissions indexed by submission id
756 $assessments = array(); // recent assessments indexed by assessment id
759 foreach ($rs as $activity) {
761 // remember all user names we can use later
762 if (empty($users[$activity->authorid])) {
764 $additionalfields = explode(',', user_picture::fields());
765 $u = username_load_fields_from_object($u, $activity, 'author', $additionalfields);
766 $users[$activity->authorid] = $u;
768 if ($activity->reviewerid and empty($users[$activity->reviewerid])) {
770 $additionalfields = explode(',', user_picture::fields());
771 $u = username_load_fields_from_object($u, $activity, 'reviewer', $additionalfields);
772 $users[$activity->reviewerid] = $u;
775 if ($activity->submissionmodified > $timestart and empty($submissions[$activity->submissionid])) {
777 $s->id = $activity->submissionid;
778 $s->title = $activity->submissiontitle;
779 $s->authorid = $activity->authorid;
780 $s->timemodified = $activity->submissionmodified;
781 if ($activity->authorid == $USER->id || has_capability('mod/workshop:viewauthornames', $context)) {
782 $s->authornamevisible = true;
784 $s->authornamevisible = false;
787 // the following do-while wrapper allows to break from deeply nested if-statements
789 if ($s->authorid === $USER->id) {
790 // own submissions always visible
791 $submissions[$activity->submissionid] = $s;
795 if (has_capability('mod/workshop:viewallsubmissions', $context)) {
796 if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
798 // shortcut - guest user does not belong into any group
802 // this might be slow - show only submissions by users who share group with me in this cm
803 if (!$modinfo->get_groups($cm->groupingid)) {
806 $authorsgroups = groups_get_all_groups($course->id, $s->authorid, $cm->groupingid);
807 if (is_array($authorsgroups)) {
808 $authorsgroups = array_keys($authorsgroups);
809 $intersect = array_intersect($authorsgroups, $modinfo->get_groups($cm->groupingid));
810 if (empty($intersect)) {
813 // can see all submissions and shares a group with the author
814 $submissions[$activity->submissionid] = $s;
820 // can see all submissions from all groups
821 $submissions[$activity->submissionid] = $s;
827 if ($activity->assessmentmodified > $timestart and empty($assessments[$activity->assessmentid])) {
829 $a->id = $activity->assessmentid;
830 $a->submissionid = $activity->submissionid;
831 $a->submissiontitle = $activity->submissiontitle;
832 $a->reviewerid = $activity->reviewerid;
833 $a->timemodified = $activity->assessmentmodified;
834 if ($activity->reviewerid == $USER->id || has_capability('mod/workshop:viewreviewernames', $context)) {
835 $a->reviewernamevisible = true;
837 $a->reviewernamevisible = false;
840 // the following do-while wrapper allows to break from deeply nested if-statements
842 if ($a->reviewerid === $USER->id) {
843 // own assessments always visible
844 $assessments[$activity->assessmentid] = $a;
848 if (has_capability('mod/workshop:viewallassessments', $context)) {
849 if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
851 // shortcut - guest user does not belong into any group
855 // this might be slow - show only submissions by users who share group with me in this cm
856 if (!$modinfo->get_groups($cm->groupingid)) {
859 $reviewersgroups = groups_get_all_groups($course->id, $a->reviewerid, $cm->groupingid);
860 if (is_array($reviewersgroups)) {
861 $reviewersgroups = array_keys($reviewersgroups);
862 $intersect = array_intersect($reviewersgroups, $modinfo->get_groups($cm->groupingid));
863 if (empty($intersect)) {
866 // can see all assessments and shares a group with the reviewer
867 $assessments[$activity->assessmentid] = $a;
873 // can see all assessments from all groups
874 $assessments[$activity->assessmentid] = $a;
882 $workshopname = format_string($cm->name, true);
885 require_once($CFG->libdir.'/gradelib.php');
886 $grades = grade_get_grades($courseid, 'mod', 'workshop', $cm->instance, array_keys($users));
889 foreach ($submissions as $submission) {
890 $tmpactivity = new stdclass();
891 $tmpactivity->type = 'workshop';
892 $tmpactivity->cmid = $cm->id;
893 $tmpactivity->name = $workshopname;
894 $tmpactivity->sectionnum = $cm->sectionnum;
895 $tmpactivity->timestamp = $submission->timemodified;
896 $tmpactivity->subtype = 'submission';
897 $tmpactivity->content = $submission;
899 $tmpactivity->grade = $grades->items[0]->grades[$submission->authorid]->str_long_grade;
901 if ($submission->authornamevisible and !empty($users[$submission->authorid])) {
902 $tmpactivity->user = $users[$submission->authorid];
904 $activities[$index++] = $tmpactivity;
907 foreach ($assessments as $assessment) {
908 $tmpactivity = new stdclass();
909 $tmpactivity->type = 'workshop';
910 $tmpactivity->cmid = $cm->id;
911 $tmpactivity->name = $workshopname;
912 $tmpactivity->sectionnum = $cm->sectionnum;
913 $tmpactivity->timestamp = $assessment->timemodified;
914 $tmpactivity->subtype = 'assessment';
915 $tmpactivity->content = $assessment;
917 $tmpactivity->grade = $grades->items[1]->grades[$assessment->reviewerid]->str_long_grade;
919 if ($assessment->reviewernamevisible and !empty($users[$assessment->reviewerid])) {
920 $tmpactivity->user = $users[$assessment->reviewerid];
922 $activities[$index++] = $tmpactivity;
927 * Print single activity item prepared by {@see workshop_get_recent_mod_activity()}
929 function workshop_print_recent_mod_activity($activity, $courseid, $detail, $modnames, $viewfullnames) {
930 global $CFG, $OUTPUT;
932 if (!empty($activity->user)) {
933 echo html_writer::tag('div', $OUTPUT->user_picture($activity->user, array('courseid'=>$courseid)),
934 array('style' => 'float: left; padding: 7px;'));
937 if ($activity->subtype == 'submission') {
938 echo html_writer::start_tag('div', array('class'=>'submission', 'style'=>'padding: 7px; float:left;'));
941 echo html_writer::start_tag('h4', array('class'=>'workshop'));
942 $url = new moodle_url('/mod/workshop/view.php', array('id'=>$activity->cmid));
943 $name = s($activity->name);
944 echo $OUTPUT->image_icon('icon', $name, $activity->type);
945 echo ' ' . $modnames[$activity->type];
946 echo html_writer::link($url, $name, array('class'=>'name', 'style'=>'margin-left: 5px'));
947 echo html_writer::end_tag('h4');
950 echo html_writer::start_tag('div', array('class'=>'title'));
951 $url = new moodle_url('/mod/workshop/submission.php', array('cmid'=>$activity->cmid, 'id'=>$activity->content->id));
952 $name = s($activity->content->title);
953 echo html_writer::tag('strong', html_writer::link($url, $name));
954 echo html_writer::end_tag('div');
956 if (!empty($activity->user)) {
957 echo html_writer::start_tag('div', array('class'=>'user'));
958 $url = new moodle_url('/user/view.php', array('id'=>$activity->user->id, 'course'=>$courseid));
959 $name = fullname($activity->user);
960 $link = html_writer::link($url, $name);
961 echo get_string('submissionby', 'workshop', $link);
962 echo ' - '.userdate($activity->timestamp);
963 echo html_writer::end_tag('div');
965 echo html_writer::start_tag('div', array('class'=>'anonymous'));
966 echo get_string('submission', 'workshop');
967 echo ' - '.userdate($activity->timestamp);
968 echo html_writer::end_tag('div');
971 echo html_writer::end_tag('div');
974 if ($activity->subtype == 'assessment') {
975 echo html_writer::start_tag('div', array('class'=>'assessment', 'style'=>'padding: 7px; float:left;'));
978 echo html_writer::start_tag('h4', array('class'=>'workshop'));
979 $url = new moodle_url('/mod/workshop/view.php', array('id'=>$activity->cmid));
980 $name = s($activity->name);
981 echo $OUTPUT->image_icon('icon', $name, $activity->type);
982 echo ' ' . $modnames[$activity->type];
983 echo html_writer::link($url, $name, array('class'=>'name', 'style'=>'margin-left: 5px'));
984 echo html_writer::end_tag('h4');
987 echo html_writer::start_tag('div', array('class'=>'title'));
988 $url = new moodle_url('/mod/workshop/assessment.php', array('asid'=>$activity->content->id));
989 $name = s($activity->content->submissiontitle);
990 echo html_writer::tag('em', html_writer::link($url, $name));
991 echo html_writer::end_tag('div');
993 if (!empty($activity->user)) {
994 echo html_writer::start_tag('div', array('class'=>'user'));
995 $url = new moodle_url('/user/view.php', array('id'=>$activity->user->id, 'course'=>$courseid));
996 $name = fullname($activity->user);
997 $link = html_writer::link($url, $name);
998 echo get_string('assessmentbyfullname', 'workshop', $link);
999 echo ' - '.userdate($activity->timestamp);
1000 echo html_writer::end_tag('div');
1002 echo html_writer::start_tag('div', array('class'=>'anonymous'));
1003 echo get_string('assessment', 'workshop');
1004 echo ' - '.userdate($activity->timestamp);
1005 echo html_writer::end_tag('div');
1008 echo html_writer::end_tag('div');
1011 echo html_writer::empty_tag('br', array('style'=>'clear:both'));
1015 * Regular jobs to execute via cron
1017 * @return boolean true on success, false otherwise
1019 function workshop_cron() {
1024 mtrace(' processing workshop subplugins ...');
1025 cron_execute_plugin_type('workshopallocation', 'workshop allocation methods');
1027 // now when the scheduled allocator had a chance to do its job, check if there
1028 // are some workshops to switch into the assessment phase
1029 $workshops = $DB->get_records_select("workshop",
1030 "phase = 20 AND phaseswitchassessment = 1 AND submissionend > 0 AND submissionend < ?", array($now));
1032 if (!empty($workshops)) {
1033 mtrace('Processing automatic assessment phase switch in '.count($workshops).' workshop(s) ... ', '');
1034 require_once($CFG->dirroot.'/mod/workshop/locallib.php');
1035 foreach ($workshops as $workshop) {
1036 $cm = get_coursemodule_from_instance('workshop', $workshop->id, $workshop->course, false, MUST_EXIST);
1037 $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
1038 $workshop = new workshop($workshop, $cm, $course);
1039 $workshop->switch_phase(workshop::PHASE_ASSESSMENT);
1042 'objectid' => $workshop->id,
1043 'context' => $workshop->context,
1044 'courseid' => $workshop->course->id,
1046 'workshopphase' => $workshop->phase
1049 $event = \mod_workshop\event\phase_switched::create($params);
1052 // disable the automatic switching now so that it is not executed again by accident
1053 // if the teacher changes the phase back to the submission one
1054 $DB->set_field('workshop', 'phaseswitchassessment', 0, array('id' => $workshop->id));
1056 // todo inform the teachers
1065 * Is a given scale used by the instance of workshop?
1067 * The function asks all installed grading strategy subplugins. The workshop
1068 * core itself does not use scales. Both grade for submission and grade for
1069 * assessments do not use scales.
1071 * @param int $workshopid id of workshop instance
1072 * @param int $scaleid id of the scale to check
1075 function workshop_scale_used($workshopid, $scaleid) {
1076 global $CFG; // other files included from here
1078 $strategies = core_component::get_plugin_list('workshopform');
1079 foreach ($strategies as $strategy => $strategypath) {
1080 $strategylib = $strategypath . '/lib.php';
1081 if (is_readable($strategylib)) {
1082 require_once($strategylib);
1084 throw new coding_exception('the grading forms subplugin must contain library ' . $strategylib);
1086 $classname = 'workshop_' . $strategy . '_strategy';
1087 if (method_exists($classname, 'scale_used')) {
1088 if (call_user_func_array(array($classname, 'scale_used'), array($scaleid, $workshopid))) {
1089 // no need to include any other files - scale is used
1099 * Is a given scale used by any instance of workshop?
1101 * The function asks all installed grading strategy subplugins. The workshop
1102 * core itself does not use scales. Both grade for submission and grade for
1103 * assessments do not use scales.
1105 * @param int $scaleid id of the scale to check
1108 function workshop_scale_used_anywhere($scaleid) {
1109 global $CFG; // other files included from here
1111 $strategies = core_component::get_plugin_list('workshopform');
1112 foreach ($strategies as $strategy => $strategypath) {
1113 $strategylib = $strategypath . '/lib.php';
1114 if (is_readable($strategylib)) {
1115 require_once($strategylib);
1117 throw new coding_exception('the grading forms subplugin must contain library ' . $strategylib);
1119 $classname = 'workshop_' . $strategy . '_strategy';
1120 if (method_exists($classname, 'scale_used')) {
1121 if (call_user_func(array($classname, 'scale_used'), $scaleid)) {
1122 // no need to include any other files - scale is used
1132 * Returns all other caps used in the module
1136 function workshop_get_extra_capabilities() {
1137 return array('moodle/site:accessallgroups');
1140 ////////////////////////////////////////////////////////////////////////////////
1142 ////////////////////////////////////////////////////////////////////////////////
1145 * Creates or updates grade items for the give workshop instance
1147 * Needed by grade_update_mod_grades() in lib/gradelib.php. Also used by
1148 * {@link workshop_update_grades()}.
1150 * @param stdClass $workshop instance object with extra cmidnumber property
1151 * @param stdClass $submissiongrades data for the first grade item
1152 * @param stdClass $assessmentgrades data for the second grade item
1155 function workshop_grade_item_update(stdclass $workshop, $submissiongrades=null, $assessmentgrades=null) {
1157 require_once($CFG->libdir.'/gradelib.php');
1159 $a = new stdclass();
1160 $a->workshopname = clean_param($workshop->name, PARAM_NOTAGS);
1163 $item['itemname'] = get_string('gradeitemsubmission', 'workshop', $a);
1164 $item['gradetype'] = GRADE_TYPE_VALUE;
1165 $item['grademax'] = $workshop->grade;
1166 $item['grademin'] = 0;
1167 grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 0, $submissiongrades , $item);
1170 $item['itemname'] = get_string('gradeitemassessment', 'workshop', $a);
1171 $item['gradetype'] = GRADE_TYPE_VALUE;
1172 $item['grademax'] = $workshop->gradinggrade;
1173 $item['grademin'] = 0;
1174 grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 1, $assessmentgrades, $item);
1178 * Update workshop grades in the gradebook
1180 * Needed by grade_update_mod_grades() in lib/gradelib.php
1183 * @param stdClass $workshop instance object with extra cmidnumber and modname property
1184 * @param int $userid update grade of specific user only, 0 means all participants
1187 function workshop_update_grades(stdclass $workshop, $userid=0) {
1189 require_once($CFG->libdir.'/gradelib.php');
1191 $whereuser = $userid ? ' AND authorid = :userid' : '';
1192 $params = array('workshopid' => $workshop->id, 'userid' => $userid);
1193 $sql = 'SELECT authorid, grade, gradeover, gradeoverby, feedbackauthor, feedbackauthorformat, timemodified, timegraded
1194 FROM {workshop_submissions}
1195 WHERE workshopid = :workshopid AND example=0' . $whereuser;
1196 $records = $DB->get_records_sql($sql, $params);
1197 $submissiongrades = array();
1198 foreach ($records as $record) {
1199 $grade = new stdclass();
1200 $grade->userid = $record->authorid;
1201 if (!is_null($record->gradeover)) {
1202 $grade->rawgrade = grade_floatval($workshop->grade * $record->gradeover / 100);
1203 $grade->usermodified = $record->gradeoverby;
1205 $grade->rawgrade = grade_floatval($workshop->grade * $record->grade / 100);
1207 $grade->feedback = $record->feedbackauthor;
1208 $grade->feedbackformat = $record->feedbackauthorformat;
1209 $grade->datesubmitted = $record->timemodified;
1210 $grade->dategraded = $record->timegraded;
1211 $submissiongrades[$record->authorid] = $grade;
1214 $whereuser = $userid ? ' AND userid = :userid' : '';
1215 $params = array('workshopid' => $workshop->id, 'userid' => $userid);
1216 $sql = 'SELECT userid, gradinggrade, timegraded
1217 FROM {workshop_aggregations}
1218 WHERE workshopid = :workshopid' . $whereuser;
1219 $records = $DB->get_records_sql($sql, $params);
1220 $assessmentgrades = array();
1221 foreach ($records as $record) {
1222 $grade = new stdclass();
1223 $grade->userid = $record->userid;
1224 $grade->rawgrade = grade_floatval($workshop->gradinggrade * $record->gradinggrade / 100);
1225 $grade->dategraded = $record->timegraded;
1226 $assessmentgrades[$record->userid] = $grade;
1229 workshop_grade_item_update($workshop, $submissiongrades, $assessmentgrades);
1233 * Update the grade items categories if they are changed via mod_form.php
1235 * We must do it manually here in the workshop module because modedit supports only
1236 * single grade item while we use two.
1238 * @param stdClass $workshop An object from the form in mod_form.php
1240 function workshop_grade_item_category_update($workshop) {
1242 $gradeitems = grade_item::fetch_all(array(
1243 'itemtype' => 'mod',
1244 'itemmodule' => 'workshop',
1245 'iteminstance' => $workshop->id,
1246 'courseid' => $workshop->course));
1248 if (!empty($gradeitems)) {
1249 foreach ($gradeitems as $gradeitem) {
1250 if ($gradeitem->itemnumber == 0) {
1251 if (isset($workshop->submissiongradepass) &&
1252 $gradeitem->gradepass != $workshop->submissiongradepass) {
1253 $gradeitem->gradepass = $workshop->submissiongradepass;
1254 $gradeitem->update();
1256 if ($gradeitem->categoryid != $workshop->gradecategory) {
1257 $gradeitem->set_parent($workshop->gradecategory);
1259 } else if ($gradeitem->itemnumber == 1) {
1260 if (isset($workshop->gradinggradepass) &&
1261 $gradeitem->gradepass != $workshop->gradinggradepass) {
1262 $gradeitem->gradepass = $workshop->gradinggradepass;
1263 $gradeitem->update();
1265 if ($gradeitem->categoryid != $workshop->gradinggradecategory) {
1266 $gradeitem->set_parent($workshop->gradinggradecategory);
1273 ////////////////////////////////////////////////////////////////////////////////
1275 ////////////////////////////////////////////////////////////////////////////////
1278 * Returns the lists of all browsable file areas within the given module context
1280 * The file area workshop_intro for the activity introduction field is added automatically
1281 * by {@link file_browser::get_file_info_context_module()}
1283 * @package mod_workshop
1286 * @param stdClass $course
1287 * @param stdClass $cm
1288 * @param stdClass $context
1289 * @return array of [(string)filearea] => (string)description
1291 function workshop_get_file_areas($course, $cm, $context) {
1293 $areas['instructauthors'] = get_string('areainstructauthors', 'workshop');
1294 $areas['instructreviewers'] = get_string('areainstructreviewers', 'workshop');
1295 $areas['submission_content'] = get_string('areasubmissioncontent', 'workshop');
1296 $areas['submission_attachment'] = get_string('areasubmissionattachment', 'workshop');
1297 $areas['conclusion'] = get_string('areaconclusion', 'workshop');
1298 $areas['overallfeedback_content'] = get_string('areaoverallfeedbackcontent', 'workshop');
1299 $areas['overallfeedback_attachment'] = get_string('areaoverallfeedbackattachment', 'workshop');
1305 * Serves the files from the workshop file areas
1307 * Apart from module intro (handled by pluginfile.php automatically), workshop files may be
1308 * media inserted into submission content (like images) and submission attachments. For these two,
1309 * the fileareas submission_content and submission_attachment are used.
1310 * Besides that, areas instructauthors, instructreviewers and conclusion contain the media
1311 * embedded using the mod_form.php.
1313 * @package mod_workshop
1316 * @param stdClass $course the course object
1317 * @param stdClass $cm the course module object
1318 * @param stdClass $context the workshop's context
1319 * @param string $filearea the name of the file area
1320 * @param array $args extra arguments (itemid, path)
1321 * @param bool $forcedownload whether or not force download
1322 * @param array $options additional options affecting the file serving
1323 * @return bool false if the file not found, just send the file otherwise and do not return anything
1325 function workshop_pluginfile($course, $cm, $context, $filearea, array $args, $forcedownload, array $options=array()) {
1326 global $DB, $CFG, $USER;
1328 if ($context->contextlevel != CONTEXT_MODULE) {
1332 require_login($course, true, $cm);
1334 if ($filearea === 'instructauthors' or $filearea === 'instructreviewers' or $filearea === 'conclusion') {
1335 // The $args are supposed to contain just the path, not the item id.
1336 $relativepath = implode('/', $args);
1337 $fullpath = "/$context->id/mod_workshop/$filearea/0/$relativepath";
1339 $fs = get_file_storage();
1340 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1341 send_file_not_found();
1343 send_stored_file($file, null, 0, $forcedownload, $options);
1345 } else if ($filearea === 'submission_content' or $filearea === 'submission_attachment') {
1346 $itemid = (int)array_shift($args);
1347 if (!$workshop = $DB->get_record('workshop', array('id' => $cm->instance))) {
1350 if (!$submission = $DB->get_record('workshop_submissions', array('id' => $itemid, 'workshopid' => $workshop->id))) {
1354 // make sure the user is allowed to see the file
1355 if (empty($submission->example)) {
1356 if ($USER->id != $submission->authorid) {
1357 if ($submission->published == 1 and $workshop->phase == 50
1358 and has_capability('mod/workshop:viewpublishedsubmissions', $context)) {
1359 // Published submission, we can go (workshop does not take the group mode
1360 // into account in this case yet).
1361 } else if (!$DB->record_exists('workshop_assessments', array('submissionid' => $submission->id, 'reviewerid' => $USER->id))) {
1362 if (!has_capability('mod/workshop:viewallsubmissions', $context)) {
1363 send_file_not_found();
1365 $gmode = groups_get_activity_groupmode($cm, $course);
1366 if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1367 // check there is at least one common group with both the $USER
1368 // and the submission author
1370 FROM {workshop_submissions} s
1371 JOIN {user} a ON (a.id = s.authorid)
1372 JOIN {groups_members} agm ON (a.id = agm.userid)
1373 JOIN {user} u ON (u.id = ?)
1374 JOIN {groups_members} ugm ON (u.id = ugm.userid)
1375 WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1376 $params = array($USER->id, $workshop->id, $submission->id);
1377 if (!$DB->record_exists_sql($sql, $params)) {
1378 send_file_not_found();
1386 $fs = get_file_storage();
1387 $relativepath = implode('/', $args);
1388 $fullpath = "/$context->id/mod_workshop/$filearea/$itemid/$relativepath";
1389 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1392 // finally send the file
1393 // these files are uploaded by students - forcing download for security reasons
1394 send_stored_file($file, 0, 0, true, $options);
1396 } else if ($filearea === 'overallfeedback_content' or $filearea === 'overallfeedback_attachment') {
1397 $itemid = (int)array_shift($args);
1398 if (!$workshop = $DB->get_record('workshop', array('id' => $cm->instance))) {
1401 if (!$assessment = $DB->get_record('workshop_assessments', array('id' => $itemid))) {
1404 if (!$submission = $DB->get_record('workshop_submissions', array('id' => $assessment->submissionid, 'workshopid' => $workshop->id))) {
1408 if ($USER->id == $assessment->reviewerid) {
1409 // Reviewers can always see their own files.
1410 } else if ($USER->id == $submission->authorid and $workshop->phase == 50) {
1411 // Authors can see the feedback once the workshop is closed.
1412 } else if (!empty($submission->example) and $assessment->weight == 1) {
1413 // Reference assessments of example submissions can be displayed.
1414 } else if (!has_capability('mod/workshop:viewallassessments', $context)) {
1415 send_file_not_found();
1417 $gmode = groups_get_activity_groupmode($cm, $course);
1418 if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1419 // Check there is at least one common group with both the $USER
1420 // and the submission author.
1422 FROM {workshop_submissions} s
1423 JOIN {user} a ON (a.id = s.authorid)
1424 JOIN {groups_members} agm ON (a.id = agm.userid)
1425 JOIN {user} u ON (u.id = ?)
1426 JOIN {groups_members} ugm ON (u.id = ugm.userid)
1427 WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1428 $params = array($USER->id, $workshop->id, $submission->id);
1429 if (!$DB->record_exists_sql($sql, $params)) {
1430 send_file_not_found();
1435 $fs = get_file_storage();
1436 $relativepath = implode('/', $args);
1437 $fullpath = "/$context->id/mod_workshop/$filearea/$itemid/$relativepath";
1438 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1441 // finally send the file
1442 // these files are uploaded by students - forcing download for security reasons
1443 send_stored_file($file, 0, 0, true, $options);
1450 * File browsing support for workshop file areas
1452 * @package mod_workshop
1455 * @param file_browser $browser
1456 * @param array $areas
1457 * @param stdClass $course
1458 * @param stdClass $cm
1459 * @param stdClass $context
1460 * @param string $filearea
1461 * @param int $itemid
1462 * @param string $filepath
1463 * @param string $filename
1464 * @return file_info instance or null if not found
1466 function workshop_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
1467 global $CFG, $DB, $USER;
1469 /** @var array internal cache for author names */
1470 static $submissionauthors = array();
1472 $fs = get_file_storage();
1474 if ($filearea === 'submission_content' or $filearea === 'submission_attachment') {
1476 if (!has_capability('mod/workshop:viewallsubmissions', $context)) {
1480 if (is_null($itemid)) {
1481 // no itemid (submissionid) passed, display the list of all submissions
1482 require_once($CFG->dirroot . '/mod/workshop/fileinfolib.php');
1483 return new workshop_file_info_submissions_container($browser, $course, $cm, $context, $areas, $filearea);
1486 // make sure the user can see the particular submission in separate groups mode
1487 $gmode = groups_get_activity_groupmode($cm, $course);
1489 if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1490 // check there is at least one common group with both the $USER
1491 // and the submission author (this is not expected to be a frequent
1492 // usecase so we can live with pretty ineffective one query per submission here...)
1494 FROM {workshop_submissions} s
1495 JOIN {user} a ON (a.id = s.authorid)
1496 JOIN {groups_members} agm ON (a.id = agm.userid)
1497 JOIN {user} u ON (u.id = ?)
1498 JOIN {groups_members} ugm ON (u.id = ugm.userid)
1499 WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1500 $params = array($USER->id, $cm->instance, $itemid);
1501 if (!$DB->record_exists_sql($sql, $params)) {
1506 // we are inside some particular submission container
1508 $filepath = is_null($filepath) ? '/' : $filepath;
1509 $filename = is_null($filename) ? '.' : $filename;
1511 if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, $itemid, $filepath, $filename)) {
1512 if ($filepath === '/' and $filename === '.') {
1513 $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, $itemid);
1520 // Checks to see if the user can manage files or is the owner.
1521 // TODO MDL-33805 - Do not use userid here and move the capability check above.
1522 if (!has_capability('moodle/course:managefiles', $context) && $storedfile->get_userid() != $USER->id) {
1526 // let us display the author's name instead of itemid (submission id)
1528 if (isset($submissionauthors[$itemid])) {
1529 $topvisiblename = $submissionauthors[$itemid];
1533 $sql = "SELECT s.id, u.lastname, u.firstname
1534 FROM {workshop_submissions} s
1535 JOIN {user} u ON (s.authorid = u.id)
1536 WHERE s.example = 0 AND s.workshopid = ?";
1537 $params = array($cm->instance);
1538 $rs = $DB->get_recordset_sql($sql, $params);
1540 foreach ($rs as $submissionauthor) {
1541 $title = s(fullname($submissionauthor)); // this is generally not unique...
1542 $submissionauthors[$submissionauthor->id] = $title;
1546 if (!isset($submissionauthors[$itemid])) {
1547 // should not happen
1550 $topvisiblename = $submissionauthors[$itemid];
1554 $urlbase = $CFG->wwwroot . '/pluginfile.php';
1555 // do not allow manual modification of any files!
1556 return new file_info_stored($browser, $context, $storedfile, $urlbase, $topvisiblename, true, true, false, false);
1559 if ($filearea === 'overallfeedback_content' or $filearea === 'overallfeedback_attachment') {
1561 if (!has_capability('mod/workshop:viewallassessments', $context)) {
1565 if (is_null($itemid)) {
1566 // No itemid (assessmentid) passed, display the list of all assessments.
1567 require_once($CFG->dirroot . '/mod/workshop/fileinfolib.php');
1568 return new workshop_file_info_overallfeedback_container($browser, $course, $cm, $context, $areas, $filearea);
1571 // Make sure the user can see the particular assessment in separate groups mode.
1572 $gmode = groups_get_activity_groupmode($cm, $course);
1573 if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1574 // Check there is at least one common group with both the $USER
1575 // and the submission author.
1577 FROM {workshop_submissions} s
1578 JOIN {user} a ON (a.id = s.authorid)
1579 JOIN {groups_members} agm ON (a.id = agm.userid)
1580 JOIN {user} u ON (u.id = ?)
1581 JOIN {groups_members} ugm ON (u.id = ugm.userid)
1582 WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1583 $params = array($USER->id, $cm->instance, $itemid);
1584 if (!$DB->record_exists_sql($sql, $params)) {
1589 // We are inside a particular assessment container.
1590 $filepath = is_null($filepath) ? '/' : $filepath;
1591 $filename = is_null($filename) ? '.' : $filename;
1593 if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, $itemid, $filepath, $filename)) {
1594 if ($filepath === '/' and $filename === '.') {
1595 $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, $itemid);
1602 // Check to see if the user can manage files or is the owner.
1603 if (!has_capability('moodle/course:managefiles', $context) and $storedfile->get_userid() != $USER->id) {
1607 $urlbase = $CFG->wwwroot . '/pluginfile.php';
1609 // Do not allow manual modification of any files.
1610 return new file_info_stored($browser, $context, $storedfile, $urlbase, $itemid, true, true, false, false);
1613 if ($filearea == 'instructauthors' or $filearea == 'instructreviewers' or $filearea == 'conclusion') {
1614 // always only itemid 0
1616 $filepath = is_null($filepath) ? '/' : $filepath;
1617 $filename = is_null($filename) ? '.' : $filename;
1619 $urlbase = $CFG->wwwroot.'/pluginfile.php';
1620 if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, 0, $filepath, $filename)) {
1621 if ($filepath === '/' and $filename === '.') {
1622 $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, 0);
1628 return new file_info_stored($browser, $context, $storedfile, $urlbase, $areas[$filearea], false, true, true, false);
1632 ////////////////////////////////////////////////////////////////////////////////
1633 // Navigation API //
1634 ////////////////////////////////////////////////////////////////////////////////
1637 * Extends the global navigation tree by adding workshop nodes if there is a relevant content
1639 * This can be called by an AJAX request so do not rely on $PAGE as it might not be set up properly.
1641 * @param navigation_node $navref An object representing the navigation tree node of the workshop module instance
1642 * @param stdClass $course
1643 * @param stdClass $module
1644 * @param cm_info $cm
1646 function workshop_extend_navigation(navigation_node $navref, stdclass $course, stdclass $module, cm_info $cm) {
1649 if (has_capability('mod/workshop:submit', context_module::instance($cm->id))) {
1650 $url = new moodle_url('/mod/workshop/submission.php', array('cmid' => $cm->id));
1651 $mysubmission = $navref->add(get_string('mysubmission', 'workshop'), $url);
1652 $mysubmission->mainnavonly = true;
1657 * Extends the settings navigation with the Workshop settings
1659 * This function is called when the context for the page is a workshop module. This is not called by AJAX
1660 * so it is safe to rely on the $PAGE.
1662 * @param settings_navigation $settingsnav {@link settings_navigation}
1663 * @param navigation_node $workshopnode {@link navigation_node}
1665 function workshop_extend_settings_navigation(settings_navigation $settingsnav, navigation_node $workshopnode=null) {
1668 //$workshopobject = $DB->get_record("workshop", array("id" => $PAGE->cm->instance));
1670 if (has_capability('mod/workshop:editdimensions', $PAGE->cm->context)) {
1671 $url = new moodle_url('/mod/workshop/editform.php', array('cmid' => $PAGE->cm->id));
1672 $workshopnode->add(get_string('editassessmentform', 'workshop'), $url, settings_navigation::TYPE_SETTING);
1674 if (has_capability('mod/workshop:allocate', $PAGE->cm->context)) {
1675 $url = new moodle_url('/mod/workshop/allocation.php', array('cmid' => $PAGE->cm->id));
1676 $workshopnode->add(get_string('allocate', 'workshop'), $url, settings_navigation::TYPE_SETTING);
1681 * Return a list of page types
1682 * @param string $pagetype current page type
1683 * @param stdClass $parentcontext Block's parent context
1684 * @param stdClass $currentcontext Current context of block
1686 function workshop_page_type_list($pagetype, $parentcontext, $currentcontext) {
1687 $module_pagetype = array('mod-workshop-*'=>get_string('page-mod-workshop-x', 'workshop'));
1688 return $module_pagetype;
1691 ////////////////////////////////////////////////////////////////////////////////
1693 ////////////////////////////////////////////////////////////////////////////////
1696 * Updates the calendar events associated to the given workshop
1698 * @param stdClass $workshop the workshop instance record
1699 * @param int $cmid course module id
1701 function workshop_calendar_update(stdClass $workshop, $cmid) {
1704 // get the currently registered events so that we can re-use their ids
1705 $currentevents = $DB->get_records('event', array('modulename' => 'workshop', 'instance' => $workshop->id));
1707 // the common properties for all events
1708 $base = new stdClass();
1709 $base->description = format_module_intro('workshop', $workshop, $cmid, false);
1710 $base->courseid = $workshop->course;
1713 $base->modulename = 'workshop';
1714 $base->instance = $workshop->id;
1715 $base->visible = instance_is_visible('workshop', $workshop);
1716 $base->timeduration = 0;
1718 if ($workshop->submissionstart) {
1719 $event = clone($base);
1720 $event->name = get_string('submissionstartevent', 'mod_workshop', $workshop->name);
1721 $event->eventtype = WORKSHOP_EVENT_TYPE_SUBMISSION_OPEN;
1722 $event->type = empty($workshop->submissionend) ? CALENDAR_EVENT_TYPE_ACTION : CALENDAR_EVENT_TYPE_STANDARD;
1723 $event->timestart = $workshop->submissionstart;
1724 $event->timesort = $workshop->submissionstart;
1725 if ($reusedevent = array_shift($currentevents)) {
1726 $event->id = $reusedevent->id;
1728 // should not be set but just in case
1731 // update() will reuse a db record if the id field is set
1732 $eventobj = new calendar_event($event);
1733 $eventobj->update($event, false);
1736 if ($workshop->submissionend) {
1737 $event = clone($base);
1738 $event->name = get_string('submissionendevent', 'mod_workshop', $workshop->name);
1739 $event->eventtype = WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE;
1740 $event->type = CALENDAR_EVENT_TYPE_ACTION;
1741 $event->timestart = $workshop->submissionend;
1742 $event->timesort = $workshop->submissionend;
1743 if ($reusedevent = array_shift($currentevents)) {
1744 $event->id = $reusedevent->id;
1746 // should not be set but just in case
1749 // update() will reuse a db record if the id field is set
1750 $eventobj = new calendar_event($event);
1751 $eventobj->update($event, false);
1754 if ($workshop->assessmentstart) {
1755 $event = clone($base);
1756 $event->name = get_string('assessmentstartevent', 'mod_workshop', $workshop->name);
1757 $event->eventtype = WORKSHOP_EVENT_TYPE_ASSESSMENT_OPEN;
1758 $event->type = empty($workshop->assessmentend) ? CALENDAR_EVENT_TYPE_ACTION : CALENDAR_EVENT_TYPE_STANDARD;
1759 $event->timestart = $workshop->assessmentstart;
1760 $event->timesort = $workshop->assessmentstart;
1761 if ($reusedevent = array_shift($currentevents)) {
1762 $event->id = $reusedevent->id;
1764 // should not be set but just in case
1767 // update() will reuse a db record if the id field is set
1768 $eventobj = new calendar_event($event);
1769 $eventobj->update($event, false);
1772 if ($workshop->assessmentend) {
1773 $event = clone($base);
1774 $event->name = get_string('assessmentendevent', 'mod_workshop', $workshop->name);
1775 $event->eventtype = WORKSHOP_EVENT_TYPE_ASSESSMENT_CLOSE;
1776 $event->type = CALENDAR_EVENT_TYPE_ACTION;
1777 $event->timestart = $workshop->assessmentend;
1778 $event->timesort = $workshop->assessmentend;
1779 if ($reusedevent = array_shift($currentevents)) {
1780 $event->id = $reusedevent->id;
1782 // should not be set but just in case
1785 // update() will reuse a db record if the id field is set
1786 $eventobj = new calendar_event($event);
1787 $eventobj->update($event, false);
1790 // delete any leftover events
1791 foreach ($currentevents as $oldevent) {
1792 $oldevent = calendar_event::load($oldevent);
1793 $oldevent->delete();
1798 * This function receives a calendar event and returns the action associated with it, or null if there is none.
1800 * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event
1801 * is not displayed on the block.
1803 * @param calendar_event $event
1804 * @param \core_calendar\action_factory $factory
1805 * @return \core_calendar\local\event\entities\action_interface|null
1807 function mod_workshop_core_calendar_provide_event_action(calendar_event $event,
1808 \core_calendar\action_factory $factory) {
1810 $cm = get_fast_modinfo($event->courseid)->instances['workshop'][$event->instance];
1812 return $factory->create_instance(
1813 get_string('viewworkshopsummary', 'workshop'),
1814 new \moodle_url('/mod/workshop/view.php', array('id' => $cm->id)),
1820 ////////////////////////////////////////////////////////////////////////////////
1821 // Course reset API //
1822 ////////////////////////////////////////////////////////////////////////////////
1825 * Extends the course reset form with workshop specific settings.
1827 * @param MoodleQuickForm $mform
1829 function workshop_reset_course_form_definition($mform) {
1831 $mform->addElement('header', 'workshopheader', get_string('modulenameplural', 'mod_workshop'));
1833 $mform->addElement('advcheckbox', 'reset_workshop_submissions', get_string('resetsubmissions', 'mod_workshop'));
1834 $mform->addHelpButton('reset_workshop_submissions', 'resetsubmissions', 'mod_workshop');
1836 $mform->addElement('advcheckbox', 'reset_workshop_assessments', get_string('resetassessments', 'mod_workshop'));
1837 $mform->addHelpButton('reset_workshop_assessments', 'resetassessments', 'mod_workshop');
1838 $mform->disabledIf('reset_workshop_assessments', 'reset_workshop_submissions', 'checked');
1840 $mform->addElement('advcheckbox', 'reset_workshop_phase', get_string('resetphase', 'mod_workshop'));
1841 $mform->addHelpButton('reset_workshop_phase', 'resetphase', 'mod_workshop');
1845 * Provides default values for the workshop settings in the course reset form.
1847 * @param stdClass $course The course to be reset.
1849 function workshop_reset_course_form_defaults(stdClass $course) {
1852 'reset_workshop_submissions' => 1,
1853 'reset_workshop_assessments' => 1,
1854 'reset_workshop_phase' => 1,
1861 * Performs the reset of all workshop instances in the course.
1863 * @param stdClass $data The actual course reset settings.
1864 * @return array List of results, each being array[(string)component, (string)item, (string)error]
1866 function workshop_reset_userdata(stdClass $data) {
1869 if (empty($data->reset_workshop_submissions)
1870 and empty($data->reset_workshop_assessments)
1871 and empty($data->reset_workshop_phase) ) {
1872 // Nothing to do here.
1876 $workshoprecords = $DB->get_records('workshop', array('course' => $data->courseid));
1878 if (empty($workshoprecords)) {
1879 // What a boring course - no workshops here!
1883 require_once($CFG->dirroot . '/mod/workshop/locallib.php');
1885 $course = $DB->get_record('course', array('id' => $data->courseid), '*', MUST_EXIST);
1888 foreach ($workshoprecords as $workshoprecord) {
1889 $cm = get_coursemodule_from_instance('workshop', $workshoprecord->id, $course->id, false, MUST_EXIST);
1890 $workshop = new workshop($workshoprecord, $cm, $course);
1891 $status = array_merge($status, $workshop->reset_userdata($data));
1898 * Get icon mapping for font-awesome.
1900 function mod_workshop_get_fontawesome_icon_map() {
1902 'mod_workshop:userplan/task-info' => 'fa-info text-info',
1903 'mod_workshop:userplan/task-todo' => 'fa-square-o',
1904 'mod_workshop:userplan/task-done' => 'fa-check text-success',
1905 'mod_workshop:userplan/task-fail' => 'fa-remove text-danger',