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 ////////////////////////////////////////////////////////////////////////////////
35 ////////////////////////////////////////////////////////////////////////////////
38 * Returns the information if the module supports a feature
40 * @see plugin_supports() in lib/moodlelib.php
41 * @param string $feature FEATURE_xx constant for requested feature
42 * @return mixed true if the feature is supported, null if unknown
44 function workshop_supports($feature) {
46 case FEATURE_GRADE_HAS_GRADE: return true;
47 case FEATURE_GROUPS: return true;
48 case FEATURE_GROUPINGS: return true;
49 case FEATURE_GROUPMEMBERSONLY: return true;
50 case FEATURE_MOD_INTRO: return true;
51 case FEATURE_BACKUP_MOODLE2: return true;
52 case FEATURE_COMPLETION_TRACKS_VIEWS:
54 case FEATURE_SHOW_DESCRIPTION: return true;
55 case FEATURE_PLAGIARISM: return true;
61 * Saves a new instance of the workshop into the database
63 * Given an object containing all the necessary data,
64 * (defined by the form in mod_form.php) this function
65 * will save a new instance and return the id number
66 * of the new instance.
68 * @param stdClass $workshop An object from the form in mod_form.php
69 * @return int The id of the newly inserted workshop record
71 function workshop_add_instance(stdclass $workshop) {
73 require_once(dirname(__FILE__) . '/locallib.php');
75 $workshop->phase = workshop::PHASE_SETUP;
76 $workshop->timecreated = time();
77 $workshop->timemodified = $workshop->timecreated;
78 $workshop->useexamples = (int)!empty($workshop->useexamples);
79 $workshop->usepeerassessment = 1;
80 $workshop->useselfassessment = (int)!empty($workshop->useselfassessment);
81 $workshop->latesubmissions = (int)!empty($workshop->latesubmissions);
82 $workshop->phaseswitchassessment = (int)!empty($workshop->phaseswitchassessment);
83 $workshop->evaluation = 'best';
85 // insert the new record so we get the id
86 $workshop->id = $DB->insert_record('workshop', $workshop);
88 // we need to use context now, so we need to make sure all needed info is already in db
89 $cmid = $workshop->coursemodule;
90 $DB->set_field('course_modules', 'instance', $workshop->id, array('id' => $cmid));
91 $context = context_module::instance($cmid);
93 // process the custom wysiwyg editors
94 if ($draftitemid = $workshop->instructauthorseditor['itemid']) {
95 $workshop->instructauthors = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructauthors',
96 0, workshop::instruction_editors_options($context), $workshop->instructauthorseditor['text']);
97 $workshop->instructauthorsformat = $workshop->instructauthorseditor['format'];
100 if ($draftitemid = $workshop->instructreviewerseditor['itemid']) {
101 $workshop->instructreviewers = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructreviewers',
102 0, workshop::instruction_editors_options($context), $workshop->instructreviewerseditor['text']);
103 $workshop->instructreviewersformat = $workshop->instructreviewerseditor['format'];
106 if ($draftitemid = $workshop->conclusioneditor['itemid']) {
107 $workshop->conclusion = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'conclusion',
108 0, workshop::instruction_editors_options($context), $workshop->conclusioneditor['text']);
109 $workshop->conclusionformat = $workshop->conclusioneditor['format'];
112 // re-save the record with the replaced URLs in editor fields
113 $DB->update_record('workshop', $workshop);
115 // create gradebook items
116 workshop_grade_item_update($workshop);
117 workshop_grade_item_category_update($workshop);
119 // create calendar events
120 workshop_calendar_update($workshop, $workshop->coursemodule);
122 return $workshop->id;
126 * Given an object containing all the necessary data,
127 * (defined by the form in mod_form.php) this function
128 * will update an existing instance with new data.
130 * @param stdClass $workshop An object from the form in mod_form.php
131 * @return bool success
133 function workshop_update_instance(stdclass $workshop) {
135 require_once(dirname(__FILE__) . '/locallib.php');
137 $workshop->timemodified = time();
138 $workshop->id = $workshop->instance;
139 $workshop->useexamples = (int)!empty($workshop->useexamples);
140 $workshop->usepeerassessment = 1;
141 $workshop->useselfassessment = (int)!empty($workshop->useselfassessment);
142 $workshop->latesubmissions = (int)!empty($workshop->latesubmissions);
143 $workshop->phaseswitchassessment = (int)!empty($workshop->phaseswitchassessment);
145 // todo - if the grading strategy is being changed, we may want to replace all aggregated peer grades with nulls
147 $DB->update_record('workshop', $workshop);
148 $context = context_module::instance($workshop->coursemodule);
150 // process the custom wysiwyg editors
151 if ($draftitemid = $workshop->instructauthorseditor['itemid']) {
152 $workshop->instructauthors = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructauthors',
153 0, workshop::instruction_editors_options($context), $workshop->instructauthorseditor['text']);
154 $workshop->instructauthorsformat = $workshop->instructauthorseditor['format'];
157 if ($draftitemid = $workshop->instructreviewerseditor['itemid']) {
158 $workshop->instructreviewers = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructreviewers',
159 0, workshop::instruction_editors_options($context), $workshop->instructreviewerseditor['text']);
160 $workshop->instructreviewersformat = $workshop->instructreviewerseditor['format'];
163 if ($draftitemid = $workshop->conclusioneditor['itemid']) {
164 $workshop->conclusion = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'conclusion',
165 0, workshop::instruction_editors_options($context), $workshop->conclusioneditor['text']);
166 $workshop->conclusionformat = $workshop->conclusioneditor['format'];
169 // re-save the record with the replaced URLs in editor fields
170 $DB->update_record('workshop', $workshop);
172 // update gradebook items
173 workshop_grade_item_update($workshop);
174 workshop_grade_item_category_update($workshop);
176 // update calendar events
177 workshop_calendar_update($workshop, $workshop->coursemodule);
183 * Given an ID of an instance of this module,
184 * this function will permanently delete the instance
185 * and any data that depends on it.
187 * @param int $id Id of the module instance
188 * @return boolean Success/Failure
190 function workshop_delete_instance($id) {
192 require_once($CFG->libdir.'/gradelib.php');
194 if (! $workshop = $DB->get_record('workshop', array('id' => $id))) {
198 // delete all associated aggregations
199 $DB->delete_records('workshop_aggregations', array('workshopid' => $workshop->id));
201 // get the list of ids of all submissions
202 $submissions = $DB->get_records('workshop_submissions', array('workshopid' => $workshop->id), '', 'id');
204 // get the list of all allocated assessments
205 $assessments = $DB->get_records_list('workshop_assessments', 'submissionid', array_keys($submissions), '', 'id');
207 // delete the associated records from the workshop core tables
208 $DB->delete_records_list('workshop_grades', 'assessmentid', array_keys($assessments));
209 $DB->delete_records_list('workshop_assessments', 'id', array_keys($assessments));
210 $DB->delete_records_list('workshop_submissions', 'id', array_keys($submissions));
212 // call the static clean-up methods of all available subplugins
213 $strategies = core_component::get_plugin_list('workshopform');
214 foreach ($strategies as $strategy => $path) {
215 require_once($path.'/lib.php');
216 $classname = 'workshop_'.$strategy.'_strategy';
217 call_user_func($classname.'::delete_instance', $workshop->id);
220 $allocators = core_component::get_plugin_list('workshopallocation');
221 foreach ($allocators as $allocator => $path) {
222 require_once($path.'/lib.php');
223 $classname = 'workshop_'.$allocator.'_allocator';
224 call_user_func($classname.'::delete_instance', $workshop->id);
227 $evaluators = core_component::get_plugin_list('workshopeval');
228 foreach ($evaluators as $evaluator => $path) {
229 require_once($path.'/lib.php');
230 $classname = 'workshop_'.$evaluator.'_evaluation';
231 call_user_func($classname.'::delete_instance', $workshop->id);
234 // delete the calendar events
235 $events = $DB->get_records('event', array('modulename' => 'workshop', 'instance' => $workshop->id));
236 foreach ($events as $event) {
237 $event = calendar_event::load($event);
241 // finally remove the workshop record itself
242 $DB->delete_records('workshop', array('id' => $workshop->id));
245 grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 0, null, array('deleted' => true));
246 grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 1, null, array('deleted' => true));
252 * Return a small object with summary information about what a
253 * user has done with a given particular instance of this module
254 * Used for user activity reports.
255 * $return->time = the time they did it
256 * $return->info = a short text description
258 * @return stdclass|null
260 function workshop_user_outline($course, $user, $mod, $workshop) {
262 require_once($CFG->libdir.'/gradelib.php');
264 $grades = grade_get_grades($course->id, 'mod', 'workshop', $workshop->id, $user->id);
266 $submissiongrade = null;
267 $assessmentgrade = null;
272 if (!empty($grades->items[0]->grades)) {
273 $submissiongrade = reset($grades->items[0]->grades);
274 $info .= get_string('submissiongrade', 'workshop') . ': ' . $submissiongrade->str_long_grade . html_writer::empty_tag('br');
275 $time = max($time, $submissiongrade->dategraded);
277 if (!empty($grades->items[1]->grades)) {
278 $assessmentgrade = reset($grades->items[1]->grades);
279 $info .= get_string('gradinggrade', 'workshop') . ': ' . $assessmentgrade->str_long_grade;
280 $time = max($time, $assessmentgrade->dategraded);
283 if (!empty($info) and !empty($time)) {
284 $return = new stdclass();
285 $return->time = $time;
286 $return->info = $info;
294 * Print a detailed representation of what a user has done with
295 * a given particular instance of this module, for user activity reports.
297 * @return string HTML
299 function workshop_user_complete($course, $user, $mod, $workshop) {
300 global $CFG, $DB, $OUTPUT;
301 require_once(dirname(__FILE__).'/locallib.php');
302 require_once($CFG->libdir.'/gradelib.php');
304 $workshop = new workshop($workshop, $mod, $course);
305 $grades = grade_get_grades($course->id, 'mod', 'workshop', $workshop->id, $user->id);
307 if (!empty($grades->items[0]->grades)) {
308 $submissiongrade = reset($grades->items[0]->grades);
309 $info = get_string('submissiongrade', 'workshop') . ': ' . $submissiongrade->str_long_grade;
310 echo html_writer::tag('li', $info, array('class'=>'submissiongrade'));
312 if (!empty($grades->items[1]->grades)) {
313 $assessmentgrade = reset($grades->items[1]->grades);
314 $info = get_string('gradinggrade', 'workshop') . ': ' . $assessmentgrade->str_long_grade;
315 echo html_writer::tag('li', $info, array('class'=>'gradinggrade'));
318 if (has_capability('mod/workshop:viewallsubmissions', $workshop->context)) {
319 $canviewsubmission = true;
320 if (groups_get_activity_groupmode($workshop->cm) == SEPARATEGROUPS) {
321 // user must have accessallgroups or share at least one group with the submission author
322 if (!has_capability('moodle/site:accessallgroups', $workshop->context)) {
323 $usersgroups = groups_get_activity_allowed_groups($workshop->cm);
324 $authorsgroups = groups_get_all_groups($workshop->course->id, $user->id, $workshop->cm->groupingid, 'g.id');
325 $sharedgroups = array_intersect_key($usersgroups, $authorsgroups);
326 if (empty($sharedgroups)) {
327 $canviewsubmission = false;
331 if ($canviewsubmission and $submission = $workshop->get_submission_by_author($user->id)) {
332 $title = format_string($submission->title);
333 $url = $workshop->submission_url($submission->id);
334 $link = html_writer::link($url, $title);
335 $info = get_string('submission', 'workshop').': '.$link;
336 echo html_writer::tag('li', $info, array('class'=>'submission'));
340 if (has_capability('mod/workshop:viewallassessments', $workshop->context)) {
341 if ($assessments = $workshop->get_assessments_by_reviewer($user->id)) {
342 foreach ($assessments as $assessment) {
344 $a->submissionurl = $workshop->submission_url($assessment->submissionid)->out();
345 $a->assessmenturl = $workshop->assess_url($assessment->id)->out();
346 $a->submissiontitle = s($assessment->submissiontitle);
347 echo html_writer::tag('li', get_string('assessmentofsubmission', 'workshop', $a));
354 * Given a course and a time, this module should find recent activity
355 * that has occurred in workshop activities and print it out.
356 * Return true if there was output, or false is there was none.
358 * @param stdClass $course
359 * @param bool $viewfullnames
360 * @param int $timestart
363 function workshop_print_recent_activity($course, $viewfullnames, $timestart) {
364 global $CFG, $USER, $DB, $OUTPUT;
366 $authoramefields = get_all_user_name_fields(true, 'author', null, 'author');
367 $reviewerfields = get_all_user_name_fields(true, 'reviewer', null, 'reviewer');
369 $sql = "SELECT s.id AS submissionid, s.title AS submissiontitle, s.timemodified AS submissionmodified,
370 author.id AS authorid, $authoramefields, a.id AS assessmentid, a.timemodified AS assessmentmodified,
371 reviewer.id AS reviewerid, $reviewerfields, cm.id AS cmid
373 INNER JOIN {course_modules} cm ON cm.instance = w.id
374 INNER JOIN {modules} md ON md.id = cm.module
375 INNER JOIN {workshop_submissions} s ON s.workshopid = w.id
376 INNER JOIN {user} author ON s.authorid = author.id
377 LEFT JOIN {workshop_assessments} a ON a.submissionid = s.id
378 LEFT JOIN {user} reviewer ON a.reviewerid = reviewer.id
380 AND md.name = 'workshop'
382 AND (s.timemodified > ? OR a.timemodified > ?)
383 ORDER BY s.timemodified";
385 $rs = $DB->get_recordset_sql($sql, array($course->id, $timestart, $timestart));
387 $modinfo = get_fast_modinfo($course); // reference needed because we might load the groups
389 $submissions = array(); // recent submissions indexed by submission id
390 $assessments = array(); // recent assessments indexed by assessment id
393 foreach ($rs as $activity) {
394 if (!array_key_exists($activity->cmid, $modinfo->cms)) {
395 // this should not happen but just in case
399 $cm = $modinfo->cms[$activity->cmid];
400 if (!$cm->uservisible) {
404 // remember all user names we can use later
405 if (empty($users[$activity->authorid])) {
407 $users[$activity->authorid] = username_load_fields_from_object($u, $activity, 'author');
409 if ($activity->reviewerid and empty($users[$activity->reviewerid])) {
411 $users[$activity->reviewerid] = username_load_fields_from_object($u, $activity, 'reviewer');
414 $context = context_module::instance($cm->id);
415 $groupmode = groups_get_activity_groupmode($cm, $course);
417 if ($activity->submissionmodified > $timestart and empty($submissions[$activity->submissionid])) {
419 $s->title = $activity->submissiontitle;
420 $s->authorid = $activity->authorid;
421 $s->timemodified = $activity->submissionmodified;
422 $s->cmid = $activity->cmid;
423 if ($activity->authorid == $USER->id || has_capability('mod/workshop:viewauthornames', $context)) {
424 $s->authornamevisible = true;
426 $s->authornamevisible = false;
429 // the following do-while wrapper allows to break from deeply nested if-statements
431 if ($s->authorid === $USER->id) {
432 // own submissions always visible
433 $submissions[$activity->submissionid] = $s;
437 if (has_capability('mod/workshop:viewallsubmissions', $context)) {
438 if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
440 // shortcut - guest user does not belong into any group
444 // this might be slow - show only submissions by users who share group with me in this cm
445 if (!$modinfo->get_groups($cm->groupingid)) {
448 $authorsgroups = groups_get_all_groups($course->id, $s->authorid, $cm->groupingid);
449 if (is_array($authorsgroups)) {
450 $authorsgroups = array_keys($authorsgroups);
451 $intersect = array_intersect($authorsgroups, $modinfo->get_groups($cm->groupingid));
452 if (empty($intersect)) {
455 // can see all submissions and shares a group with the author
456 $submissions[$activity->submissionid] = $s;
462 // can see all submissions from all groups
463 $submissions[$activity->submissionid] = $s;
469 if ($activity->assessmentmodified > $timestart and empty($assessments[$activity->assessmentid])) {
471 $a->submissionid = $activity->submissionid;
472 $a->submissiontitle = $activity->submissiontitle;
473 $a->reviewerid = $activity->reviewerid;
474 $a->timemodified = $activity->assessmentmodified;
475 $a->cmid = $activity->cmid;
476 if ($activity->reviewerid == $USER->id || has_capability('mod/workshop:viewreviewernames', $context)) {
477 $a->reviewernamevisible = true;
479 $a->reviewernamevisible = false;
482 // the following do-while wrapper allows to break from deeply nested if-statements
484 if ($a->reviewerid === $USER->id) {
485 // own assessments always visible
486 $assessments[$activity->assessmentid] = $a;
490 if (has_capability('mod/workshop:viewallassessments', $context)) {
491 if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
493 // shortcut - guest user does not belong into any group
497 // this might be slow - show only submissions by users who share group with me in this cm
498 if (!$modinfo->get_groups($cm->groupingid)) {
501 $reviewersgroups = groups_get_all_groups($course->id, $a->reviewerid, $cm->groupingid);
502 if (is_array($reviewersgroups)) {
503 $reviewersgroups = array_keys($reviewersgroups);
504 $intersect = array_intersect($reviewersgroups, $modinfo->get_groups($cm->groupingid));
505 if (empty($intersect)) {
508 // can see all assessments and shares a group with the reviewer
509 $assessments[$activity->assessmentid] = $a;
515 // can see all assessments from all groups
516 $assessments[$activity->assessmentid] = $a;
526 if (!empty($submissions)) {
528 echo $OUTPUT->heading(get_string('recentsubmissions', 'workshop'), 3);
529 foreach ($submissions as $id => $submission) {
530 $link = new moodle_url('/mod/workshop/submission.php', array('id'=>$id, 'cmid'=>$submission->cmid));
531 if ($submission->authornamevisible) {
532 $author = $users[$submission->authorid];
536 print_recent_activity_note($submission->timemodified, $author, $submission->title, $link->out(), false, $viewfullnames);
540 if (!empty($assessments)) {
542 echo $OUTPUT->heading(get_string('recentassessments', 'workshop'), 3);
543 core_collator::asort_objects_by_property($assessments, 'timemodified');
544 foreach ($assessments as $id => $assessment) {
545 $link = new moodle_url('/mod/workshop/assessment.php', array('asid' => $id));
546 if ($assessment->reviewernamevisible) {
547 $reviewer = $users[$assessment->reviewerid];
551 print_recent_activity_note($assessment->timemodified, $reviewer, $assessment->submissiontitle, $link->out(), false, $viewfullnames);
563 * Returns all activity in course workshops since a given time
565 * @param array $activities sequentially indexed array of objects
567 * @param int $timestart
568 * @param int $courseid
570 * @param int $userid defaults to 0
571 * @param int $groupid defaults to 0
572 * @return void adds items into $activities and increases $index
574 function workshop_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid=0, $groupid=0) {
575 global $CFG, $COURSE, $USER, $DB;
577 if ($COURSE->id == $courseid) {
580 $course = $DB->get_record('course', array('id'=>$courseid));
583 $modinfo = get_fast_modinfo($course);
585 $cm = $modinfo->cms[$cmid];
589 $userselect = "AND (author.id = :authorid OR reviewer.id = :reviewerid)";
590 $params['authorid'] = $userid;
591 $params['reviewerid'] = $userid;
597 $groupselect = "AND (authorgroupmembership.groupid = :authorgroupid OR reviewergroupmembership.groupid = :reviewergroupid)";
598 $groupjoin = "LEFT JOIN {groups_members} authorgroupmembership ON authorgroupmembership.userid = author.id
599 LEFT JOIN {groups_members} reviewergroupmembership ON reviewergroupmembership.userid = reviewer.id";
600 $params['authorgroupid'] = $groupid;
601 $params['reviewergroupid'] = $groupid;
607 $params['cminstance'] = $cm->instance;
608 $params['submissionmodified'] = $timestart;
609 $params['assessmentmodified'] = $timestart;
611 $authornamefields = get_all_user_name_fields(true, 'author', null, 'author');
612 $reviewerfields = get_all_user_name_fields(true, 'reviewer', null, 'reviewer');
614 $sql = "SELECT s.id AS submissionid, s.title AS submissiontitle, s.timemodified AS submissionmodified,
615 author.id AS authorid, $authornamefields, author.picture AS authorpicture, author.imagealt AS authorimagealt,
616 author.email AS authoremail, a.id AS assessmentid, a.timemodified AS assessmentmodified,
617 reviewer.id AS reviewerid, $reviewerfields, reviewer.picture AS reviewerpicture,
618 reviewer.imagealt AS reviewerimagealt, reviewer.email AS revieweremail
619 FROM {workshop_submissions} s
620 INNER JOIN {workshop} w ON s.workshopid = w.id
621 INNER JOIN {user} author ON s.authorid = author.id
622 LEFT JOIN {workshop_assessments} a ON a.submissionid = s.id
623 LEFT JOIN {user} reviewer ON a.reviewerid = reviewer.id
625 WHERE w.id = :cminstance
627 $userselect $groupselect
628 AND (s.timemodified > :submissionmodified OR a.timemodified > :assessmentmodified)
629 ORDER BY s.timemodified ASC, a.timemodified ASC";
631 $rs = $DB->get_recordset_sql($sql, $params);
633 $groupmode = groups_get_activity_groupmode($cm, $course);
634 $context = context_module::instance($cm->id);
635 $grader = has_capability('moodle/grade:viewall', $context);
636 $accessallgroups = has_capability('moodle/site:accessallgroups', $context);
637 $viewauthors = has_capability('mod/workshop:viewauthornames', $context);
638 $viewreviewers = has_capability('mod/workshop:viewreviewernames', $context);
640 $submissions = array(); // recent submissions indexed by submission id
641 $assessments = array(); // recent assessments indexed by assessment id
644 foreach ($rs as $activity) {
646 // remember all user names we can use later
647 if (empty($users[$activity->authorid])) {
649 $additionalfields = explode(',', user_picture::fields());
650 $u = username_load_fields_from_object($u, $activity, 'author', $additionalfields);
651 $users[$activity->authorid] = $u;
653 if ($activity->reviewerid and empty($users[$activity->reviewerid])) {
655 $additionalfields = explode(',', user_picture::fields());
656 $u = username_load_fields_from_object($u, $activity, 'reviewer', $additionalfields);
657 $users[$activity->reviewerid] = $u;
660 if ($activity->submissionmodified > $timestart and empty($submissions[$activity->submissionid])) {
662 $s->id = $activity->submissionid;
663 $s->title = $activity->submissiontitle;
664 $s->authorid = $activity->authorid;
665 $s->timemodified = $activity->submissionmodified;
666 if ($activity->authorid == $USER->id || has_capability('mod/workshop:viewauthornames', $context)) {
667 $s->authornamevisible = true;
669 $s->authornamevisible = false;
672 // the following do-while wrapper allows to break from deeply nested if-statements
674 if ($s->authorid === $USER->id) {
675 // own submissions always visible
676 $submissions[$activity->submissionid] = $s;
680 if (has_capability('mod/workshop:viewallsubmissions', $context)) {
681 if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
683 // shortcut - guest user does not belong into any group
687 // this might be slow - show only submissions by users who share group with me in this cm
688 if (!$modinfo->get_groups($cm->groupingid)) {
691 $authorsgroups = groups_get_all_groups($course->id, $s->authorid, $cm->groupingid);
692 if (is_array($authorsgroups)) {
693 $authorsgroups = array_keys($authorsgroups);
694 $intersect = array_intersect($authorsgroups, $modinfo->get_groups($cm->groupingid));
695 if (empty($intersect)) {
698 // can see all submissions and shares a group with the author
699 $submissions[$activity->submissionid] = $s;
705 // can see all submissions from all groups
706 $submissions[$activity->submissionid] = $s;
712 if ($activity->assessmentmodified > $timestart and empty($assessments[$activity->assessmentid])) {
714 $a->id = $activity->assessmentid;
715 $a->submissionid = $activity->submissionid;
716 $a->submissiontitle = $activity->submissiontitle;
717 $a->reviewerid = $activity->reviewerid;
718 $a->timemodified = $activity->assessmentmodified;
719 if ($activity->reviewerid == $USER->id || has_capability('mod/workshop:viewreviewernames', $context)) {
720 $a->reviewernamevisible = true;
722 $a->reviewernamevisible = false;
725 // the following do-while wrapper allows to break from deeply nested if-statements
727 if ($a->reviewerid === $USER->id) {
728 // own assessments always visible
729 $assessments[$activity->assessmentid] = $a;
733 if (has_capability('mod/workshop:viewallassessments', $context)) {
734 if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
736 // shortcut - guest user does not belong into any group
740 // this might be slow - show only submissions by users who share group with me in this cm
741 if (!$modinfo->get_groups($cm->groupingid)) {
744 $reviewersgroups = groups_get_all_groups($course->id, $a->reviewerid, $cm->groupingid);
745 if (is_array($reviewersgroups)) {
746 $reviewersgroups = array_keys($reviewersgroups);
747 $intersect = array_intersect($reviewersgroups, $modinfo->get_groups($cm->groupingid));
748 if (empty($intersect)) {
751 // can see all assessments and shares a group with the reviewer
752 $assessments[$activity->assessmentid] = $a;
758 // can see all assessments from all groups
759 $assessments[$activity->assessmentid] = $a;
767 $workshopname = format_string($cm->name, true);
770 require_once($CFG->libdir.'/gradelib.php');
771 $grades = grade_get_grades($courseid, 'mod', 'workshop', $cm->instance, array_keys($users));
774 foreach ($submissions as $submission) {
775 $tmpactivity = new stdclass();
776 $tmpactivity->type = 'workshop';
777 $tmpactivity->cmid = $cm->id;
778 $tmpactivity->name = $workshopname;
779 $tmpactivity->sectionnum = $cm->sectionnum;
780 $tmpactivity->timestamp = $submission->timemodified;
781 $tmpactivity->subtype = 'submission';
782 $tmpactivity->content = $submission;
784 $tmpactivity->grade = $grades->items[0]->grades[$submission->authorid]->str_long_grade;
786 if ($submission->authornamevisible and !empty($users[$submission->authorid])) {
787 $tmpactivity->user = $users[$submission->authorid];
789 $activities[$index++] = $tmpactivity;
792 foreach ($assessments as $assessment) {
793 $tmpactivity = new stdclass();
794 $tmpactivity->type = 'workshop';
795 $tmpactivity->cmid = $cm->id;
796 $tmpactivity->name = $workshopname;
797 $tmpactivity->sectionnum = $cm->sectionnum;
798 $tmpactivity->timestamp = $assessment->timemodified;
799 $tmpactivity->subtype = 'assessment';
800 $tmpactivity->content = $assessment;
802 $tmpactivity->grade = $grades->items[1]->grades[$assessment->reviewerid]->str_long_grade;
804 if ($assessment->reviewernamevisible and !empty($users[$assessment->reviewerid])) {
805 $tmpactivity->user = $users[$assessment->reviewerid];
807 $activities[$index++] = $tmpactivity;
812 * Print single activity item prepared by {@see workshop_get_recent_mod_activity()}
814 function workshop_print_recent_mod_activity($activity, $courseid, $detail, $modnames, $viewfullnames) {
815 global $CFG, $OUTPUT;
817 if (!empty($activity->user)) {
818 echo html_writer::tag('div', $OUTPUT->user_picture($activity->user, array('courseid'=>$courseid)),
819 array('style' => 'float: left; padding: 7px;'));
822 if ($activity->subtype == 'submission') {
823 echo html_writer::start_tag('div', array('class'=>'submission', 'style'=>'padding: 7px; float:left;'));
826 echo html_writer::start_tag('h4', array('class'=>'workshop'));
827 $url = new moodle_url('/mod/workshop/view.php', array('id'=>$activity->cmid));
828 $name = s($activity->name);
829 echo html_writer::empty_tag('img', array('src'=>$OUTPUT->pix_url('icon', $activity->type), 'class'=>'icon', 'alt'=>$name));
830 echo ' ' . $modnames[$activity->type];
831 echo html_writer::link($url, $name, array('class'=>'name', 'style'=>'margin-left: 5px'));
832 echo html_writer::end_tag('h4');
835 echo html_writer::start_tag('div', array('class'=>'title'));
836 $url = new moodle_url('/mod/workshop/submission.php', array('cmid'=>$activity->cmid, 'id'=>$activity->content->id));
837 $name = s($activity->content->title);
838 echo html_writer::tag('strong', html_writer::link($url, $name));
839 echo html_writer::end_tag('div');
841 if (!empty($activity->user)) {
842 echo html_writer::start_tag('div', array('class'=>'user'));
843 $url = new moodle_url('/user/view.php', array('id'=>$activity->user->id, 'course'=>$courseid));
844 $name = fullname($activity->user);
845 $link = html_writer::link($url, $name);
846 echo get_string('submissionby', 'workshop', $link);
847 echo ' - '.userdate($activity->timestamp);
848 echo html_writer::end_tag('div');
850 echo html_writer::start_tag('div', array('class'=>'anonymous'));
851 echo get_string('submission', 'workshop');
852 echo ' - '.userdate($activity->timestamp);
853 echo html_writer::end_tag('div');
856 echo html_writer::end_tag('div');
859 if ($activity->subtype == 'assessment') {
860 echo html_writer::start_tag('div', array('class'=>'assessment', 'style'=>'padding: 7px; float:left;'));
863 echo html_writer::start_tag('h4', array('class'=>'workshop'));
864 $url = new moodle_url('/mod/workshop/view.php', array('id'=>$activity->cmid));
865 $name = s($activity->name);
866 echo html_writer::empty_tag('img', array('src'=>$OUTPUT->pix_url('icon', $activity->type), 'class'=>'icon', 'alt'=>$name));
867 echo ' ' . $modnames[$activity->type];
868 echo html_writer::link($url, $name, array('class'=>'name', 'style'=>'margin-left: 5px'));
869 echo html_writer::end_tag('h4');
872 echo html_writer::start_tag('div', array('class'=>'title'));
873 $url = new moodle_url('/mod/workshop/assessment.php', array('asid'=>$activity->content->id));
874 $name = s($activity->content->submissiontitle);
875 echo html_writer::tag('em', html_writer::link($url, $name));
876 echo html_writer::end_tag('div');
878 if (!empty($activity->user)) {
879 echo html_writer::start_tag('div', array('class'=>'user'));
880 $url = new moodle_url('/user/view.php', array('id'=>$activity->user->id, 'course'=>$courseid));
881 $name = fullname($activity->user);
882 $link = html_writer::link($url, $name);
883 echo get_string('assessmentbyfullname', 'workshop', $link);
884 echo ' - '.userdate($activity->timestamp);
885 echo html_writer::end_tag('div');
887 echo html_writer::start_tag('div', array('class'=>'anonymous'));
888 echo get_string('assessment', 'workshop');
889 echo ' - '.userdate($activity->timestamp);
890 echo html_writer::end_tag('div');
893 echo html_writer::end_tag('div');
896 echo html_writer::empty_tag('br', array('style'=>'clear:both'));
900 * Regular jobs to execute via cron
902 * @return boolean true on success, false otherwise
904 function workshop_cron() {
909 mtrace(' processing workshop subplugins ...');
910 cron_execute_plugin_type('workshopallocation', 'workshop allocation methods');
912 // now when the scheduled allocator had a chance to do its job, check if there
913 // are some workshops to switch into the assessment phase
914 $workshops = $DB->get_records_select("workshop",
915 "phase = 20 AND phaseswitchassessment = 1 AND submissionend > 0 AND submissionend < ?", array($now));
917 if (!empty($workshops)) {
918 mtrace('Processing automatic assessment phase switch in '.count($workshops).' workshop(s) ... ', '');
919 require_once($CFG->dirroot.'/mod/workshop/locallib.php');
920 foreach ($workshops as $workshop) {
921 $cm = get_coursemodule_from_instance('workshop', $workshop->id, $workshop->course, false, MUST_EXIST);
922 $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
923 $workshop = new workshop($workshop, $cm, $course);
924 $workshop->switch_phase(workshop::PHASE_ASSESSMENT);
927 'objectid' => $workshop->id,
928 'context' => $workshop->context,
929 'courseid' => $workshop->course->id,
931 'workshopphase' => $workshop->phase
934 $event = \mod_workshop\event\phase_switched::create($params);
937 // disable the automatic switching now so that it is not executed again by accident
938 // if the teacher changes the phase back to the submission one
939 $DB->set_field('workshop', 'phaseswitchassessment', 0, array('id' => $workshop->id));
941 // todo inform the teachers
950 * Is a given scale used by the instance of workshop?
952 * The function asks all installed grading strategy subplugins. The workshop
953 * core itself does not use scales. Both grade for submission and grade for
954 * assessments do not use scales.
956 * @param int $workshopid id of workshop instance
957 * @param int $scaleid id of the scale to check
960 function workshop_scale_used($workshopid, $scaleid) {
961 global $CFG; // other files included from here
963 $strategies = core_component::get_plugin_list('workshopform');
964 foreach ($strategies as $strategy => $strategypath) {
965 $strategylib = $strategypath . '/lib.php';
966 if (is_readable($strategylib)) {
967 require_once($strategylib);
969 throw new coding_exception('the grading forms subplugin must contain library ' . $strategylib);
971 $classname = 'workshop_' . $strategy . '_strategy';
972 if (method_exists($classname, 'scale_used')) {
973 if (call_user_func_array(array($classname, 'scale_used'), array($scaleid, $workshopid))) {
974 // no need to include any other files - scale is used
984 * Is a given scale used by any instance of workshop?
986 * The function asks all installed grading strategy subplugins. The workshop
987 * core itself does not use scales. Both grade for submission and grade for
988 * assessments do not use scales.
990 * @param int $scaleid id of the scale to check
993 function workshop_scale_used_anywhere($scaleid) {
994 global $CFG; // other files included from here
996 $strategies = core_component::get_plugin_list('workshopform');
997 foreach ($strategies as $strategy => $strategypath) {
998 $strategylib = $strategypath . '/lib.php';
999 if (is_readable($strategylib)) {
1000 require_once($strategylib);
1002 throw new coding_exception('the grading forms subplugin must contain library ' . $strategylib);
1004 $classname = 'workshop_' . $strategy . '_strategy';
1005 if (method_exists($classname, 'scale_used')) {
1006 if (call_user_func(array($classname, 'scale_used'), $scaleid)) {
1007 // no need to include any other files - scale is used
1017 * Returns all other caps used in the module
1021 function workshop_get_extra_capabilities() {
1022 return array('moodle/site:accessallgroups');
1025 ////////////////////////////////////////////////////////////////////////////////
1027 ////////////////////////////////////////////////////////////////////////////////
1030 * Creates or updates grade items for the give workshop instance
1032 * Needed by grade_update_mod_grades() in lib/gradelib.php. Also used by
1033 * {@link workshop_update_grades()}.
1035 * @param stdClass $workshop instance object with extra cmidnumber property
1036 * @param stdClass $submissiongrades data for the first grade item
1037 * @param stdClass $assessmentgrades data for the second grade item
1040 function workshop_grade_item_update(stdclass $workshop, $submissiongrades=null, $assessmentgrades=null) {
1042 require_once($CFG->libdir.'/gradelib.php');
1044 $a = new stdclass();
1045 $a->workshopname = clean_param($workshop->name, PARAM_NOTAGS);
1048 $item['itemname'] = get_string('gradeitemsubmission', 'workshop', $a);
1049 $item['gradetype'] = GRADE_TYPE_VALUE;
1050 $item['grademax'] = $workshop->grade;
1051 $item['grademin'] = 0;
1052 grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 0, $submissiongrades , $item);
1055 $item['itemname'] = get_string('gradeitemassessment', 'workshop', $a);
1056 $item['gradetype'] = GRADE_TYPE_VALUE;
1057 $item['grademax'] = $workshop->gradinggrade;
1058 $item['grademin'] = 0;
1059 grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 1, $assessmentgrades, $item);
1063 * Update workshop grades in the gradebook
1065 * Needed by grade_update_mod_grades() in lib/gradelib.php
1068 * @param stdClass $workshop instance object with extra cmidnumber and modname property
1069 * @param int $userid update grade of specific user only, 0 means all participants
1072 function workshop_update_grades(stdclass $workshop, $userid=0) {
1074 require_once($CFG->libdir.'/gradelib.php');
1076 $whereuser = $userid ? ' AND authorid = :userid' : '';
1077 $params = array('workshopid' => $workshop->id, 'userid' => $userid);
1078 $sql = 'SELECT authorid, grade, gradeover, gradeoverby, feedbackauthor, feedbackauthorformat, timemodified, timegraded
1079 FROM {workshop_submissions}
1080 WHERE workshopid = :workshopid AND example=0' . $whereuser;
1081 $records = $DB->get_records_sql($sql, $params);
1082 $submissiongrades = array();
1083 foreach ($records as $record) {
1084 $grade = new stdclass();
1085 $grade->userid = $record->authorid;
1086 if (!is_null($record->gradeover)) {
1087 $grade->rawgrade = grade_floatval($workshop->grade * $record->gradeover / 100);
1088 $grade->usermodified = $record->gradeoverby;
1090 $grade->rawgrade = grade_floatval($workshop->grade * $record->grade / 100);
1092 $grade->feedback = $record->feedbackauthor;
1093 $grade->feedbackformat = $record->feedbackauthorformat;
1094 $grade->datesubmitted = $record->timemodified;
1095 $grade->dategraded = $record->timegraded;
1096 $submissiongrades[$record->authorid] = $grade;
1099 $whereuser = $userid ? ' AND userid = :userid' : '';
1100 $params = array('workshopid' => $workshop->id, 'userid' => $userid);
1101 $sql = 'SELECT userid, gradinggrade, timegraded
1102 FROM {workshop_aggregations}
1103 WHERE workshopid = :workshopid' . $whereuser;
1104 $records = $DB->get_records_sql($sql, $params);
1105 $assessmentgrades = array();
1106 foreach ($records as $record) {
1107 $grade = new stdclass();
1108 $grade->userid = $record->userid;
1109 $grade->rawgrade = grade_floatval($workshop->gradinggrade * $record->gradinggrade / 100);
1110 $grade->dategraded = $record->timegraded;
1111 $assessmentgrades[$record->userid] = $grade;
1114 workshop_grade_item_update($workshop, $submissiongrades, $assessmentgrades);
1118 * Update the grade items categories if they are changed via mod_form.php
1120 * We must do it manually here in the workshop module because modedit supports only
1121 * single grade item while we use two.
1123 * @param stdClass $workshop An object from the form in mod_form.php
1125 function workshop_grade_item_category_update($workshop) {
1127 $gradeitems = grade_item::fetch_all(array(
1128 'itemtype' => 'mod',
1129 'itemmodule' => 'workshop',
1130 'iteminstance' => $workshop->id,
1131 'courseid' => $workshop->course));
1133 if (!empty($gradeitems)) {
1134 foreach ($gradeitems as $gradeitem) {
1135 if ($gradeitem->itemnumber == 0) {
1136 if ($gradeitem->categoryid != $workshop->gradecategory) {
1137 $gradeitem->set_parent($workshop->gradecategory);
1139 } else if ($gradeitem->itemnumber == 1) {
1140 if ($gradeitem->categoryid != $workshop->gradinggradecategory) {
1141 $gradeitem->set_parent($workshop->gradinggradecategory);
1148 ////////////////////////////////////////////////////////////////////////////////
1150 ////////////////////////////////////////////////////////////////////////////////
1153 * Returns the lists of all browsable file areas within the given module context
1155 * The file area workshop_intro for the activity introduction field is added automatically
1156 * by {@link file_browser::get_file_info_context_module()}
1158 * @package mod_workshop
1161 * @param stdClass $course
1162 * @param stdClass $cm
1163 * @param stdClass $context
1164 * @return array of [(string)filearea] => (string)description
1166 function workshop_get_file_areas($course, $cm, $context) {
1168 $areas['instructauthors'] = get_string('areainstructauthors', 'workshop');
1169 $areas['instructreviewers'] = get_string('areainstructreviewers', 'workshop');
1170 $areas['submission_content'] = get_string('areasubmissioncontent', 'workshop');
1171 $areas['submission_attachment'] = get_string('areasubmissionattachment', 'workshop');
1172 $areas['conclusion'] = get_string('areaconclusion', 'workshop');
1173 $areas['overallfeedback_content'] = get_string('areaoverallfeedbackcontent', 'workshop');
1174 $areas['overallfeedback_attachment'] = get_string('areaoverallfeedbackattachment', 'workshop');
1180 * Serves the files from the workshop file areas
1182 * Apart from module intro (handled by pluginfile.php automatically), workshop files may be
1183 * media inserted into submission content (like images) and submission attachments. For these two,
1184 * the fileareas submission_content and submission_attachment are used.
1185 * Besides that, areas instructauthors, instructreviewers and conclusion contain the media
1186 * embedded using the mod_form.php.
1188 * @package mod_workshop
1191 * @param stdClass $course the course object
1192 * @param stdClass $cm the course module object
1193 * @param stdClass $context the workshop's context
1194 * @param string $filearea the name of the file area
1195 * @param array $args extra arguments (itemid, path)
1196 * @param bool $forcedownload whether or not force download
1197 * @param array $options additional options affecting the file serving
1198 * @return bool false if the file not found, just send the file otherwise and do not return anything
1200 function workshop_pluginfile($course, $cm, $context, $filearea, array $args, $forcedownload, array $options=array()) {
1201 global $DB, $CFG, $USER;
1203 if ($context->contextlevel != CONTEXT_MODULE) {
1207 require_login($course, true, $cm);
1209 if ($filearea === 'instructauthors') {
1210 array_shift($args); // itemid is ignored here
1211 $relativepath = implode('/', $args);
1212 $fullpath = "/$context->id/mod_workshop/$filearea/0/$relativepath";
1214 $fs = get_file_storage();
1215 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1216 send_file_not_found();
1219 // finally send the file
1220 send_stored_file($file, null, 0, $forcedownload, $options);
1222 } else if ($filearea === 'instructreviewers') {
1223 array_shift($args); // itemid is ignored here
1224 $relativepath = implode('/', $args);
1225 $fullpath = "/$context->id/mod_workshop/$filearea/0/$relativepath";
1227 $fs = get_file_storage();
1228 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1229 send_file_not_found();
1232 // finally send the file
1233 send_stored_file($file, null, 0, $forcedownload, $options);
1235 } else if ($filearea === 'conclusion') {
1236 array_shift($args); // itemid is ignored here
1237 $relativepath = implode('/', $args);
1238 $fullpath = "/$context->id/mod_workshop/$filearea/0/$relativepath";
1240 $fs = get_file_storage();
1241 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1242 send_file_not_found();
1245 // finally send the file
1246 send_stored_file($file, null, 0, $forcedownload, $options);
1248 } else if ($filearea === 'submission_content' or $filearea === 'submission_attachment') {
1249 $itemid = (int)array_shift($args);
1250 if (!$workshop = $DB->get_record('workshop', array('id' => $cm->instance))) {
1253 if (!$submission = $DB->get_record('workshop_submissions', array('id' => $itemid, 'workshopid' => $workshop->id))) {
1257 // make sure the user is allowed to see the file
1258 if (empty($submission->example)) {
1259 if ($USER->id != $submission->authorid) {
1260 if ($submission->published == 1 and $workshop->phase == 50
1261 and has_capability('mod/workshop:viewpublishedsubmissions', $context)) {
1262 // Published submission, we can go (workshop does not take the group mode
1263 // into account in this case yet).
1264 } else if (!$DB->record_exists('workshop_assessments', array('submissionid' => $submission->id, 'reviewerid' => $USER->id))) {
1265 if (!has_capability('mod/workshop:viewallsubmissions', $context)) {
1266 send_file_not_found();
1268 $gmode = groups_get_activity_groupmode($cm, $course);
1269 if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1270 // check there is at least one common group with both the $USER
1271 // and the submission author
1273 FROM {workshop_submissions} s
1274 JOIN {user} a ON (a.id = s.authorid)
1275 JOIN {groups_members} agm ON (a.id = agm.userid)
1276 JOIN {user} u ON (u.id = ?)
1277 JOIN {groups_members} ugm ON (u.id = ugm.userid)
1278 WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1279 $params = array($USER->id, $workshop->id, $submission->id);
1280 if (!$DB->record_exists_sql($sql, $params)) {
1281 send_file_not_found();
1289 $fs = get_file_storage();
1290 $relativepath = implode('/', $args);
1291 $fullpath = "/$context->id/mod_workshop/$filearea/$itemid/$relativepath";
1292 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1295 // finally send the file
1296 // these files are uploaded by students - forcing download for security reasons
1297 send_stored_file($file, 0, 0, true, $options);
1299 } else if ($filearea === 'overallfeedback_content' or $filearea === 'overallfeedback_attachment') {
1300 $itemid = (int)array_shift($args);
1301 if (!$workshop = $DB->get_record('workshop', array('id' => $cm->instance))) {
1304 if (!$assessment = $DB->get_record('workshop_assessments', array('id' => $itemid))) {
1307 if (!$submission = $DB->get_record('workshop_submissions', array('id' => $assessment->submissionid, 'workshopid' => $workshop->id))) {
1311 if ($USER->id == $assessment->reviewerid) {
1312 // Reviewers can always see their own files.
1313 } else if ($USER->id == $submission->authorid and $workshop->phase == 50) {
1314 // Authors can see the feedback once the workshop is closed.
1315 } else if (!empty($submission->example) and $assessment->weight == 1) {
1316 // Reference assessments of example submissions can be displayed.
1317 } else if (!has_capability('mod/workshop:viewallassessments', $context)) {
1318 send_file_not_found();
1320 $gmode = groups_get_activity_groupmode($cm, $course);
1321 if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1322 // Check there is at least one common group with both the $USER
1323 // and the submission author.
1325 FROM {workshop_submissions} s
1326 JOIN {user} a ON (a.id = s.authorid)
1327 JOIN {groups_members} agm ON (a.id = agm.userid)
1328 JOIN {user} u ON (u.id = ?)
1329 JOIN {groups_members} ugm ON (u.id = ugm.userid)
1330 WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1331 $params = array($USER->id, $workshop->id, $submission->id);
1332 if (!$DB->record_exists_sql($sql, $params)) {
1333 send_file_not_found();
1338 $fs = get_file_storage();
1339 $relativepath = implode('/', $args);
1340 $fullpath = "/$context->id/mod_workshop/$filearea/$itemid/$relativepath";
1341 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1344 // finally send the file
1345 // these files are uploaded by students - forcing download for security reasons
1346 send_stored_file($file, 0, 0, true, $options);
1353 * File browsing support for workshop file areas
1355 * @package mod_workshop
1358 * @param file_browser $browser
1359 * @param array $areas
1360 * @param stdClass $course
1361 * @param stdClass $cm
1362 * @param stdClass $context
1363 * @param string $filearea
1364 * @param int $itemid
1365 * @param string $filepath
1366 * @param string $filename
1367 * @return file_info instance or null if not found
1369 function workshop_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
1370 global $CFG, $DB, $USER;
1372 /** @var array internal cache for author names */
1373 static $submissionauthors = array();
1375 $fs = get_file_storage();
1377 if ($filearea === 'submission_content' or $filearea === 'submission_attachment') {
1379 if (!has_capability('mod/workshop:viewallsubmissions', $context)) {
1383 if (is_null($itemid)) {
1384 // no itemid (submissionid) passed, display the list of all submissions
1385 require_once($CFG->dirroot . '/mod/workshop/fileinfolib.php');
1386 return new workshop_file_info_submissions_container($browser, $course, $cm, $context, $areas, $filearea);
1389 // make sure the user can see the particular submission in separate groups mode
1390 $gmode = groups_get_activity_groupmode($cm, $course);
1392 if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1393 // check there is at least one common group with both the $USER
1394 // and the submission author (this is not expected to be a frequent
1395 // usecase so we can live with pretty ineffective one query per submission here...)
1397 FROM {workshop_submissions} s
1398 JOIN {user} a ON (a.id = s.authorid)
1399 JOIN {groups_members} agm ON (a.id = agm.userid)
1400 JOIN {user} u ON (u.id = ?)
1401 JOIN {groups_members} ugm ON (u.id = ugm.userid)
1402 WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1403 $params = array($USER->id, $cm->instance, $itemid);
1404 if (!$DB->record_exists_sql($sql, $params)) {
1409 // we are inside some particular submission container
1411 $filepath = is_null($filepath) ? '/' : $filepath;
1412 $filename = is_null($filename) ? '.' : $filename;
1414 if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, $itemid, $filepath, $filename)) {
1415 if ($filepath === '/' and $filename === '.') {
1416 $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, $itemid);
1423 // Checks to see if the user can manage files or is the owner.
1424 // TODO MDL-33805 - Do not use userid here and move the capability check above.
1425 if (!has_capability('moodle/course:managefiles', $context) && $storedfile->get_userid() != $USER->id) {
1429 // let us display the author's name instead of itemid (submission id)
1431 if (isset($submissionauthors[$itemid])) {
1432 $topvisiblename = $submissionauthors[$itemid];
1436 $sql = "SELECT s.id, u.lastname, u.firstname
1437 FROM {workshop_submissions} s
1438 JOIN {user} u ON (s.authorid = u.id)
1439 WHERE s.example = 0 AND s.workshopid = ?";
1440 $params = array($cm->instance);
1441 $rs = $DB->get_recordset_sql($sql, $params);
1443 foreach ($rs as $submissionauthor) {
1444 $title = s(fullname($submissionauthor)); // this is generally not unique...
1445 $submissionauthors[$submissionauthor->id] = $title;
1449 if (!isset($submissionauthors[$itemid])) {
1450 // should not happen
1453 $topvisiblename = $submissionauthors[$itemid];
1457 $urlbase = $CFG->wwwroot . '/pluginfile.php';
1458 // do not allow manual modification of any files!
1459 return new file_info_stored($browser, $context, $storedfile, $urlbase, $topvisiblename, true, true, false, false);
1462 if ($filearea === 'overallfeedback_content' or $filearea === 'overallfeedback_attachment') {
1464 if (!has_capability('mod/workshop:viewallassessments', $context)) {
1468 if (is_null($itemid)) {
1469 // No itemid (assessmentid) passed, display the list of all assessments.
1470 require_once($CFG->dirroot . '/mod/workshop/fileinfolib.php');
1471 return new workshop_file_info_overallfeedback_container($browser, $course, $cm, $context, $areas, $filearea);
1474 // Make sure the user can see the particular assessment in separate groups mode.
1475 $gmode = groups_get_activity_groupmode($cm, $course);
1476 if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1477 // Check there is at least one common group with both the $USER
1478 // and the submission author.
1480 FROM {workshop_submissions} s
1481 JOIN {user} a ON (a.id = s.authorid)
1482 JOIN {groups_members} agm ON (a.id = agm.userid)
1483 JOIN {user} u ON (u.id = ?)
1484 JOIN {groups_members} ugm ON (u.id = ugm.userid)
1485 WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1486 $params = array($USER->id, $cm->instance, $itemid);
1487 if (!$DB->record_exists_sql($sql, $params)) {
1492 // We are inside a particular assessment container.
1493 $filepath = is_null($filepath) ? '/' : $filepath;
1494 $filename = is_null($filename) ? '.' : $filename;
1496 if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, $itemid, $filepath, $filename)) {
1497 if ($filepath === '/' and $filename === '.') {
1498 $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, $itemid);
1505 // Check to see if the user can manage files or is the owner.
1506 if (!has_capability('moodle/course:managefiles', $context) and $storedfile->get_userid() != $USER->id) {
1510 $urlbase = $CFG->wwwroot . '/pluginfile.php';
1512 // Do not allow manual modification of any files.
1513 return new file_info_stored($browser, $context, $storedfile, $urlbase, $itemid, true, true, false, false);
1516 if ($filearea == 'instructauthors' or $filearea == 'instructreviewers' or $filearea == 'conclusion') {
1517 // always only itemid 0
1519 $filepath = is_null($filepath) ? '/' : $filepath;
1520 $filename = is_null($filename) ? '.' : $filename;
1522 $urlbase = $CFG->wwwroot.'/pluginfile.php';
1523 if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, 0, $filepath, $filename)) {
1524 if ($filepath === '/' and $filename === '.') {
1525 $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, 0);
1531 return new file_info_stored($browser, $context, $storedfile, $urlbase, $areas[$filearea], false, true, true, false);
1535 ////////////////////////////////////////////////////////////////////////////////
1536 // Navigation API //
1537 ////////////////////////////////////////////////////////////////////////////////
1540 * Extends the global navigation tree by adding workshop nodes if there is a relevant content
1542 * This can be called by an AJAX request so do not rely on $PAGE as it might not be set up properly.
1544 * @param navigation_node $navref An object representing the navigation tree node of the workshop module instance
1545 * @param stdClass $course
1546 * @param stdClass $module
1547 * @param cm_info $cm
1549 function workshop_extend_navigation(navigation_node $navref, stdclass $course, stdclass $module, cm_info $cm) {
1552 if (has_capability('mod/workshop:submit', context_module::instance($cm->id))) {
1553 $url = new moodle_url('/mod/workshop/submission.php', array('cmid' => $cm->id));
1554 $mysubmission = $navref->add(get_string('mysubmission', 'workshop'), $url);
1555 $mysubmission->mainnavonly = true;
1560 * Extends the settings navigation with the Workshop settings
1562 * This function is called when the context for the page is a workshop module. This is not called by AJAX
1563 * so it is safe to rely on the $PAGE.
1565 * @param settings_navigation $settingsnav {@link settings_navigation}
1566 * @param navigation_node $workshopnode {@link navigation_node}
1568 function workshop_extend_settings_navigation(settings_navigation $settingsnav, navigation_node $workshopnode=null) {
1571 //$workshopobject = $DB->get_record("workshop", array("id" => $PAGE->cm->instance));
1573 if (has_capability('mod/workshop:editdimensions', $PAGE->cm->context)) {
1574 $url = new moodle_url('/mod/workshop/editform.php', array('cmid' => $PAGE->cm->id));
1575 $workshopnode->add(get_string('editassessmentform', 'workshop'), $url, settings_navigation::TYPE_SETTING);
1577 if (has_capability('mod/workshop:allocate', $PAGE->cm->context)) {
1578 $url = new moodle_url('/mod/workshop/allocation.php', array('cmid' => $PAGE->cm->id));
1579 $workshopnode->add(get_string('allocate', 'workshop'), $url, settings_navigation::TYPE_SETTING);
1584 * Return a list of page types
1585 * @param string $pagetype current page type
1586 * @param stdClass $parentcontext Block's parent context
1587 * @param stdClass $currentcontext Current context of block
1589 function workshop_page_type_list($pagetype, $parentcontext, $currentcontext) {
1590 $module_pagetype = array('mod-workshop-*'=>get_string('page-mod-workshop-x', 'workshop'));
1591 return $module_pagetype;
1594 ////////////////////////////////////////////////////////////////////////////////
1596 ////////////////////////////////////////////////////////////////////////////////
1599 * Updates the calendar events associated to the given workshop
1601 * @param stdClass $workshop the workshop instance record
1602 * @param int $cmid course module id
1604 function workshop_calendar_update(stdClass $workshop, $cmid) {
1607 // get the currently registered events so that we can re-use their ids
1608 $currentevents = $DB->get_records('event', array('modulename' => 'workshop', 'instance' => $workshop->id));
1610 // the common properties for all events
1611 $base = new stdClass();
1612 $base->description = format_module_intro('workshop', $workshop, $cmid, false);
1613 $base->courseid = $workshop->course;
1616 $base->modulename = 'workshop';
1617 $base->eventtype = 'pluginname';
1618 $base->instance = $workshop->id;
1619 $base->visible = instance_is_visible('workshop', $workshop);
1620 $base->timeduration = 0;
1622 if ($workshop->submissionstart) {
1623 $event = clone($base);
1624 $event->name = get_string('submissionstartevent', 'mod_workshop', $workshop->name);
1625 $event->timestart = $workshop->submissionstart;
1626 if ($reusedevent = array_shift($currentevents)) {
1627 $event->id = $reusedevent->id;
1629 // should not be set but just in case
1632 // update() will reuse a db record if the id field is set
1633 $eventobj = new calendar_event($event);
1634 $eventobj->update($event, false);
1637 if ($workshop->submissionend) {
1638 $event = clone($base);
1639 $event->name = get_string('submissionendevent', 'mod_workshop', $workshop->name);
1640 $event->timestart = $workshop->submissionend;
1641 if ($reusedevent = array_shift($currentevents)) {
1642 $event->id = $reusedevent->id;
1644 // should not be set but just in case
1647 // update() will reuse a db record if the id field is set
1648 $eventobj = new calendar_event($event);
1649 $eventobj->update($event, false);
1652 if ($workshop->assessmentstart) {
1653 $event = clone($base);
1654 $event->name = get_string('assessmentstartevent', 'mod_workshop', $workshop->name);
1655 $event->timestart = $workshop->assessmentstart;
1656 if ($reusedevent = array_shift($currentevents)) {
1657 $event->id = $reusedevent->id;
1659 // should not be set but just in case
1662 // update() will reuse a db record if the id field is set
1663 $eventobj = new calendar_event($event);
1664 $eventobj->update($event, false);
1667 if ($workshop->assessmentend) {
1668 $event = clone($base);
1669 $event->name = get_string('assessmentendevent', 'mod_workshop', $workshop->name);
1670 $event->timestart = $workshop->assessmentend;
1671 if ($reusedevent = array_shift($currentevents)) {
1672 $event->id = $reusedevent->id;
1674 // should not be set but just in case
1677 // update() will reuse a db record if the id field is set
1678 $eventobj = new calendar_event($event);
1679 $eventobj->update($event, false);
1682 // delete any leftover events
1683 foreach ($currentevents as $oldevent) {
1684 $oldevent = calendar_event::load($oldevent);
1685 $oldevent->delete();