Merge branch 'MDL-31936-master-workshop-reset' of git://github.com/mudrd8mz/moodle
[moodle.git] / mod / workshop / lib.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
18 /**
19  * Library of workshop module functions needed by Moodle core and other subsystems
20  *
21  * All the functions neeeded by Moodle core, gradebook, file subsystem etc
22  * are placed here.
23  *
24  * @package    mod_workshop
25  * @copyright  2009 David Mudrak <david.mudrak@gmail.com>
26  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27  */
29 defined('MOODLE_INTERNAL') || die();
31 require_once($CFG->dirroot . '/calendar/lib.php');
33 ////////////////////////////////////////////////////////////////////////////////
34 // Moodle core API                                                            //
35 ////////////////////////////////////////////////////////////////////////////////
37 /**
38  * Returns the information if the module supports a feature
39  *
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
43  */
44 function workshop_supports($feature) {
45     switch($feature) {
46         case FEATURE_GRADE_HAS_GRADE:   return true;
47         case FEATURE_GROUPS:            return true;
48         case FEATURE_GROUPINGS:         return true;
49         case FEATURE_MOD_INTRO:         return true;
50         case FEATURE_BACKUP_MOODLE2:    return true;
51         case FEATURE_COMPLETION_TRACKS_VIEWS:
52             return true;
53         case FEATURE_SHOW_DESCRIPTION:  return true;
54         case FEATURE_PLAGIARISM:        return true;
55         default:                        return null;
56     }
57 }
59 /**
60  * Saves a new instance of the workshop into the database
61  *
62  * Given an object containing all the necessary data,
63  * (defined by the form in mod_form.php) this function
64  * will save a new instance and return the id number
65  * of the new instance.
66  *
67  * @param stdClass $workshop An object from the form in mod_form.php
68  * @return int The id of the newly inserted workshop record
69  */
70 function workshop_add_instance(stdclass $workshop) {
71     global $CFG, $DB;
72     require_once(dirname(__FILE__) . '/locallib.php');
74     $workshop->phase                 = workshop::PHASE_SETUP;
75     $workshop->timecreated           = time();
76     $workshop->timemodified          = $workshop->timecreated;
77     $workshop->useexamples           = (int)!empty($workshop->useexamples);
78     $workshop->usepeerassessment     = 1;
79     $workshop->useselfassessment     = (int)!empty($workshop->useselfassessment);
80     $workshop->latesubmissions       = (int)!empty($workshop->latesubmissions);
81     $workshop->phaseswitchassessment = (int)!empty($workshop->phaseswitchassessment);
82     $workshop->evaluation            = 'best';
84     // insert the new record so we get the id
85     $workshop->id = $DB->insert_record('workshop', $workshop);
87     // we need to use context now, so we need to make sure all needed info is already in db
88     $cmid = $workshop->coursemodule;
89     $DB->set_field('course_modules', 'instance', $workshop->id, array('id' => $cmid));
90     $context = context_module::instance($cmid);
92     // process the custom wysiwyg editors
93     if ($draftitemid = $workshop->instructauthorseditor['itemid']) {
94         $workshop->instructauthors = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructauthors',
95                 0, workshop::instruction_editors_options($context), $workshop->instructauthorseditor['text']);
96         $workshop->instructauthorsformat = $workshop->instructauthorseditor['format'];
97     }
99     if ($draftitemid = $workshop->instructreviewerseditor['itemid']) {
100         $workshop->instructreviewers = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructreviewers',
101                 0, workshop::instruction_editors_options($context), $workshop->instructreviewerseditor['text']);
102         $workshop->instructreviewersformat = $workshop->instructreviewerseditor['format'];
103     }
105     if ($draftitemid = $workshop->conclusioneditor['itemid']) {
106         $workshop->conclusion = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'conclusion',
107                 0, workshop::instruction_editors_options($context), $workshop->conclusioneditor['text']);
108         $workshop->conclusionformat = $workshop->conclusioneditor['format'];
109     }
111     // re-save the record with the replaced URLs in editor fields
112     $DB->update_record('workshop', $workshop);
114     // create gradebook items
115     workshop_grade_item_update($workshop);
116     workshop_grade_item_category_update($workshop);
118     // create calendar events
119     workshop_calendar_update($workshop, $workshop->coursemodule);
121     return $workshop->id;
124 /**
125  * Given an object containing all the necessary data,
126  * (defined by the form in mod_form.php) this function
127  * will update an existing instance with new data.
128  *
129  * @param stdClass $workshop An object from the form in mod_form.php
130  * @return bool success
131  */
132 function workshop_update_instance(stdclass $workshop) {
133     global $CFG, $DB;
134     require_once(dirname(__FILE__) . '/locallib.php');
136     $workshop->timemodified          = time();
137     $workshop->id                    = $workshop->instance;
138     $workshop->useexamples           = (int)!empty($workshop->useexamples);
139     $workshop->usepeerassessment     = 1;
140     $workshop->useselfassessment     = (int)!empty($workshop->useselfassessment);
141     $workshop->latesubmissions       = (int)!empty($workshop->latesubmissions);
142     $workshop->phaseswitchassessment = (int)!empty($workshop->phaseswitchassessment);
144     // todo - if the grading strategy is being changed, we may want to replace all aggregated peer grades with nulls
146     $DB->update_record('workshop', $workshop);
147     $context = context_module::instance($workshop->coursemodule);
149     // process the custom wysiwyg editors
150     if ($draftitemid = $workshop->instructauthorseditor['itemid']) {
151         $workshop->instructauthors = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructauthors',
152                 0, workshop::instruction_editors_options($context), $workshop->instructauthorseditor['text']);
153         $workshop->instructauthorsformat = $workshop->instructauthorseditor['format'];
154     }
156     if ($draftitemid = $workshop->instructreviewerseditor['itemid']) {
157         $workshop->instructreviewers = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructreviewers',
158                 0, workshop::instruction_editors_options($context), $workshop->instructreviewerseditor['text']);
159         $workshop->instructreviewersformat = $workshop->instructreviewerseditor['format'];
160     }
162     if ($draftitemid = $workshop->conclusioneditor['itemid']) {
163         $workshop->conclusion = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'conclusion',
164                 0, workshop::instruction_editors_options($context), $workshop->conclusioneditor['text']);
165         $workshop->conclusionformat = $workshop->conclusioneditor['format'];
166     }
168     // re-save the record with the replaced URLs in editor fields
169     $DB->update_record('workshop', $workshop);
171     // update gradebook items
172     workshop_grade_item_update($workshop);
173     workshop_grade_item_category_update($workshop);
175     // update calendar events
176     workshop_calendar_update($workshop, $workshop->coursemodule);
178     return true;
181 /**
182  * Given an ID of an instance of this module,
183  * this function will permanently delete the instance
184  * and any data that depends on it.
185  *
186  * @param int $id Id of the module instance
187  * @return boolean Success/Failure
188  */
189 function workshop_delete_instance($id) {
190     global $CFG, $DB;
191     require_once($CFG->libdir.'/gradelib.php');
193     if (! $workshop = $DB->get_record('workshop', array('id' => $id))) {
194         return false;
195     }
197     // delete all associated aggregations
198     $DB->delete_records('workshop_aggregations', array('workshopid' => $workshop->id));
200     // get the list of ids of all submissions
201     $submissions = $DB->get_records('workshop_submissions', array('workshopid' => $workshop->id), '', 'id');
203     // get the list of all allocated assessments
204     $assessments = $DB->get_records_list('workshop_assessments', 'submissionid', array_keys($submissions), '', 'id');
206     // delete the associated records from the workshop core tables
207     $DB->delete_records_list('workshop_grades', 'assessmentid', array_keys($assessments));
208     $DB->delete_records_list('workshop_assessments', 'id', array_keys($assessments));
209     $DB->delete_records_list('workshop_submissions', 'id', array_keys($submissions));
211     // call the static clean-up methods of all available subplugins
212     $strategies = core_component::get_plugin_list('workshopform');
213     foreach ($strategies as $strategy => $path) {
214         require_once($path.'/lib.php');
215         $classname = 'workshop_'.$strategy.'_strategy';
216         call_user_func($classname.'::delete_instance', $workshop->id);
217     }
219     $allocators = core_component::get_plugin_list('workshopallocation');
220     foreach ($allocators as $allocator => $path) {
221         require_once($path.'/lib.php');
222         $classname = 'workshop_'.$allocator.'_allocator';
223         call_user_func($classname.'::delete_instance', $workshop->id);
224     }
226     $evaluators = core_component::get_plugin_list('workshopeval');
227     foreach ($evaluators as $evaluator => $path) {
228         require_once($path.'/lib.php');
229         $classname = 'workshop_'.$evaluator.'_evaluation';
230         call_user_func($classname.'::delete_instance', $workshop->id);
231     }
233     // delete the calendar events
234     $events = $DB->get_records('event', array('modulename' => 'workshop', 'instance' => $workshop->id));
235     foreach ($events as $event) {
236         $event = calendar_event::load($event);
237         $event->delete();
238     }
240     // finally remove the workshop record itself
241     $DB->delete_records('workshop', array('id' => $workshop->id));
243     // gradebook cleanup
244     grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 0, null, array('deleted' => true));
245     grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 1, null, array('deleted' => true));
247     return true;
250 /**
251  * Return a small object with summary information about what a
252  * user has done with a given particular instance of this module
253  * Used for user activity reports.
254  * $return->time = the time they did it
255  * $return->info = a short text description
256  *
257  * @param stdClass $course The course record.
258  * @param stdClass $user The user record.
259  * @param cm_info|stdClass $mod The course module info object or record.
260  * @param stdClass $workshop The workshop instance record.
261  * @return stdclass|null
262  */
263 function workshop_user_outline($course, $user, $mod, $workshop) {
264     global $CFG, $DB;
265     require_once($CFG->libdir.'/gradelib.php');
267     $grades = grade_get_grades($course->id, 'mod', 'workshop', $workshop->id, $user->id);
269     $submissiongrade = null;
270     $assessmentgrade = null;
272     $info = '';
273     $time = 0;
275     if (!empty($grades->items[0]->grades)) {
276         $submissiongrade = reset($grades->items[0]->grades);
277         $info .= get_string('submissiongrade', 'workshop') . ': ' . $submissiongrade->str_long_grade . html_writer::empty_tag('br');
278         $time = max($time, $submissiongrade->dategraded);
279     }
280     if (!empty($grades->items[1]->grades)) {
281         $assessmentgrade = reset($grades->items[1]->grades);
282         $info .= get_string('gradinggrade', 'workshop') . ': ' . $assessmentgrade->str_long_grade;
283         $time = max($time, $assessmentgrade->dategraded);
284     }
286     if (!empty($info) and !empty($time)) {
287         $return = new stdclass();
288         $return->time = $time;
289         $return->info = $info;
290         return $return;
291     }
293     return null;
296 /**
297  * Print a detailed representation of what a user has done with
298  * a given particular instance of this module, for user activity reports.
299  *
300  * @param stdClass $course The course record.
301  * @param stdClass $user The user record.
302  * @param cm_info|stdClass $mod The course module info object or record.
303  * @param stdClass $workshop The workshop instance record.
304  * @return string HTML
305  */
306 function workshop_user_complete($course, $user, $mod, $workshop) {
307     global $CFG, $DB, $OUTPUT;
308     require_once(dirname(__FILE__).'/locallib.php');
309     require_once($CFG->libdir.'/gradelib.php');
311     $workshop   = new workshop($workshop, $mod, $course);
312     $grades     = grade_get_grades($course->id, 'mod', 'workshop', $workshop->id, $user->id);
314     if (!empty($grades->items[0]->grades)) {
315         $submissiongrade = reset($grades->items[0]->grades);
316         $info = get_string('submissiongrade', 'workshop') . ': ' . $submissiongrade->str_long_grade;
317         echo html_writer::tag('li', $info, array('class'=>'submissiongrade'));
318     }
319     if (!empty($grades->items[1]->grades)) {
320         $assessmentgrade = reset($grades->items[1]->grades);
321         $info = get_string('gradinggrade', 'workshop') . ': ' . $assessmentgrade->str_long_grade;
322         echo html_writer::tag('li', $info, array('class'=>'gradinggrade'));
323     }
325     if (has_capability('mod/workshop:viewallsubmissions', $workshop->context)) {
326         $canviewsubmission = true;
327         if (groups_get_activity_groupmode($workshop->cm) == SEPARATEGROUPS) {
328             // user must have accessallgroups or share at least one group with the submission author
329             if (!has_capability('moodle/site:accessallgroups', $workshop->context)) {
330                 $usersgroups = groups_get_activity_allowed_groups($workshop->cm);
331                 $authorsgroups = groups_get_all_groups($workshop->course->id, $user->id, $workshop->cm->groupingid, 'g.id');
332                 $sharedgroups = array_intersect_key($usersgroups, $authorsgroups);
333                 if (empty($sharedgroups)) {
334                     $canviewsubmission = false;
335                 }
336             }
337         }
338         if ($canviewsubmission and $submission = $workshop->get_submission_by_author($user->id)) {
339             $title      = format_string($submission->title);
340             $url        = $workshop->submission_url($submission->id);
341             $link       = html_writer::link($url, $title);
342             $info       = get_string('submission', 'workshop').': '.$link;
343             echo html_writer::tag('li', $info, array('class'=>'submission'));
344         }
345     }
347     if (has_capability('mod/workshop:viewallassessments', $workshop->context)) {
348         if ($assessments = $workshop->get_assessments_by_reviewer($user->id)) {
349             foreach ($assessments as $assessment) {
350                 $a = new stdclass();
351                 $a->submissionurl = $workshop->submission_url($assessment->submissionid)->out();
352                 $a->assessmenturl = $workshop->assess_url($assessment->id)->out();
353                 $a->submissiontitle = s($assessment->submissiontitle);
354                 echo html_writer::tag('li', get_string('assessmentofsubmission', 'workshop', $a));
355             }
356         }
357     }
360 /**
361  * Given a course and a time, this module should find recent activity
362  * that has occurred in workshop activities and print it out.
363  * Return true if there was output, or false is there was none.
364  *
365  * @param stdClass $course
366  * @param bool $viewfullnames
367  * @param int $timestart
368  * @return boolean
369  */
370 function workshop_print_recent_activity($course, $viewfullnames, $timestart) {
371     global $CFG, $USER, $DB, $OUTPUT;
373     $authoramefields = get_all_user_name_fields(true, 'author', null, 'author');
374     $reviewerfields = get_all_user_name_fields(true, 'reviewer', null, 'reviewer');
376     $sql = "SELECT s.id AS submissionid, s.title AS submissiontitle, s.timemodified AS submissionmodified,
377                    author.id AS authorid, $authoramefields, a.id AS assessmentid, a.timemodified AS assessmentmodified,
378                    reviewer.id AS reviewerid, $reviewerfields, cm.id AS cmid
379               FROM {workshop} w
380         INNER JOIN {course_modules} cm ON cm.instance = w.id
381         INNER JOIN {modules} md ON md.id = cm.module
382         INNER JOIN {workshop_submissions} s ON s.workshopid = w.id
383         INNER JOIN {user} author ON s.authorid = author.id
384          LEFT JOIN {workshop_assessments} a ON a.submissionid = s.id
385          LEFT JOIN {user} reviewer ON a.reviewerid = reviewer.id
386              WHERE cm.course = ?
387                    AND md.name = 'workshop'
388                    AND s.example = 0
389                    AND (s.timemodified > ? OR a.timemodified > ?)
390           ORDER BY s.timemodified";
392     $rs = $DB->get_recordset_sql($sql, array($course->id, $timestart, $timestart));
394     $modinfo = get_fast_modinfo($course); // reference needed because we might load the groups
396     $submissions = array(); // recent submissions indexed by submission id
397     $assessments = array(); // recent assessments indexed by assessment id
398     $users       = array();
400     foreach ($rs as $activity) {
401         if (!array_key_exists($activity->cmid, $modinfo->cms)) {
402             // this should not happen but just in case
403             continue;
404         }
406         $cm = $modinfo->cms[$activity->cmid];
407         if (!$cm->uservisible) {
408             continue;
409         }
411         // remember all user names we can use later
412         if (empty($users[$activity->authorid])) {
413             $u = new stdclass();
414             $users[$activity->authorid] = username_load_fields_from_object($u, $activity, 'author');
415         }
416         if ($activity->reviewerid and empty($users[$activity->reviewerid])) {
417             $u = new stdclass();
418             $users[$activity->reviewerid] = username_load_fields_from_object($u, $activity, 'reviewer');
419         }
421         $context = context_module::instance($cm->id);
422         $groupmode = groups_get_activity_groupmode($cm, $course);
424         if ($activity->submissionmodified > $timestart and empty($submissions[$activity->submissionid])) {
425             $s = new stdclass();
426             $s->title = $activity->submissiontitle;
427             $s->authorid = $activity->authorid;
428             $s->timemodified = $activity->submissionmodified;
429             $s->cmid = $activity->cmid;
430             if ($activity->authorid == $USER->id || has_capability('mod/workshop:viewauthornames', $context)) {
431                 $s->authornamevisible = true;
432             } else {
433                 $s->authornamevisible = false;
434             }
436             // the following do-while wrapper allows to break from deeply nested if-statements
437             do {
438                 if ($s->authorid === $USER->id) {
439                     // own submissions always visible
440                     $submissions[$activity->submissionid] = $s;
441                     break;
442                 }
444                 if (has_capability('mod/workshop:viewallsubmissions', $context)) {
445                     if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
446                         if (isguestuser()) {
447                             // shortcut - guest user does not belong into any group
448                             break;
449                         }
451                         // this might be slow - show only submissions by users who share group with me in this cm
452                         if (!$modinfo->get_groups($cm->groupingid)) {
453                             break;
454                         }
455                         $authorsgroups = groups_get_all_groups($course->id, $s->authorid, $cm->groupingid);
456                         if (is_array($authorsgroups)) {
457                             $authorsgroups = array_keys($authorsgroups);
458                             $intersect = array_intersect($authorsgroups, $modinfo->get_groups($cm->groupingid));
459                             if (empty($intersect)) {
460                                 break;
461                             } else {
462                                 // can see all submissions and shares a group with the author
463                                 $submissions[$activity->submissionid] = $s;
464                                 break;
465                             }
466                         }
468                     } else {
469                         // can see all submissions from all groups
470                         $submissions[$activity->submissionid] = $s;
471                     }
472                 }
473             } while (0);
474         }
476         if ($activity->assessmentmodified > $timestart and empty($assessments[$activity->assessmentid])) {
477             $a = new stdclass();
478             $a->submissionid = $activity->submissionid;
479             $a->submissiontitle = $activity->submissiontitle;
480             $a->reviewerid = $activity->reviewerid;
481             $a->timemodified = $activity->assessmentmodified;
482             $a->cmid = $activity->cmid;
483             if ($activity->reviewerid == $USER->id || has_capability('mod/workshop:viewreviewernames', $context)) {
484                 $a->reviewernamevisible = true;
485             } else {
486                 $a->reviewernamevisible = false;
487             }
489             // the following do-while wrapper allows to break from deeply nested if-statements
490             do {
491                 if ($a->reviewerid === $USER->id) {
492                     // own assessments always visible
493                     $assessments[$activity->assessmentid] = $a;
494                     break;
495                 }
497                 if (has_capability('mod/workshop:viewallassessments', $context)) {
498                     if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
499                         if (isguestuser()) {
500                             // shortcut - guest user does not belong into any group
501                             break;
502                         }
504                         // this might be slow - show only submissions by users who share group with me in this cm
505                         if (!$modinfo->get_groups($cm->groupingid)) {
506                             break;
507                         }
508                         $reviewersgroups = groups_get_all_groups($course->id, $a->reviewerid, $cm->groupingid);
509                         if (is_array($reviewersgroups)) {
510                             $reviewersgroups = array_keys($reviewersgroups);
511                             $intersect = array_intersect($reviewersgroups, $modinfo->get_groups($cm->groupingid));
512                             if (empty($intersect)) {
513                                 break;
514                             } else {
515                                 // can see all assessments and shares a group with the reviewer
516                                 $assessments[$activity->assessmentid] = $a;
517                                 break;
518                             }
519                         }
521                     } else {
522                         // can see all assessments from all groups
523                         $assessments[$activity->assessmentid] = $a;
524                     }
525                 }
526             } while (0);
527         }
528     }
529     $rs->close();
531     $shown = false;
533     if (!empty($submissions)) {
534         $shown = true;
535         echo $OUTPUT->heading(get_string('recentsubmissions', 'workshop'), 3);
536         foreach ($submissions as $id => $submission) {
537             $link = new moodle_url('/mod/workshop/submission.php', array('id'=>$id, 'cmid'=>$submission->cmid));
538             if ($submission->authornamevisible) {
539                 $author = $users[$submission->authorid];
540             } else {
541                 $author = null;
542             }
543             print_recent_activity_note($submission->timemodified, $author, $submission->title, $link->out(), false, $viewfullnames);
544         }
545     }
547     if (!empty($assessments)) {
548         $shown = true;
549         echo $OUTPUT->heading(get_string('recentassessments', 'workshop'), 3);
550         core_collator::asort_objects_by_property($assessments, 'timemodified');
551         foreach ($assessments as $id => $assessment) {
552             $link = new moodle_url('/mod/workshop/assessment.php', array('asid' => $id));
553             if ($assessment->reviewernamevisible) {
554                 $reviewer = $users[$assessment->reviewerid];
555             } else {
556                 $reviewer = null;
557             }
558             print_recent_activity_note($assessment->timemodified, $reviewer, $assessment->submissiontitle, $link->out(), false, $viewfullnames);
559         }
560     }
562     if ($shown) {
563         return true;
564     }
566     return false;
569 /**
570  * Returns all activity in course workshops since a given time
571  *
572  * @param array $activities sequentially indexed array of objects
573  * @param int $index
574  * @param int $timestart
575  * @param int $courseid
576  * @param int $cmid
577  * @param int $userid defaults to 0
578  * @param int $groupid defaults to 0
579  * @return void adds items into $activities and increases $index
580  */
581 function workshop_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid=0, $groupid=0) {
582     global $CFG, $COURSE, $USER, $DB;
584     if ($COURSE->id == $courseid) {
585         $course = $COURSE;
586     } else {
587         $course = $DB->get_record('course', array('id'=>$courseid));
588     }
590     $modinfo = get_fast_modinfo($course);
592     $cm = $modinfo->cms[$cmid];
594     $params = array();
595     if ($userid) {
596         $userselect = "AND (author.id = :authorid OR reviewer.id = :reviewerid)";
597         $params['authorid'] = $userid;
598         $params['reviewerid'] = $userid;
599     } else {
600         $userselect = "";
601     }
603     if ($groupid) {
604         $groupselect = "AND (authorgroupmembership.groupid = :authorgroupid OR reviewergroupmembership.groupid = :reviewergroupid)";
605         $groupjoin   = "LEFT JOIN {groups_members} authorgroupmembership ON authorgroupmembership.userid = author.id
606                         LEFT JOIN {groups_members} reviewergroupmembership ON reviewergroupmembership.userid = reviewer.id";
607         $params['authorgroupid'] = $groupid;
608         $params['reviewergroupid'] = $groupid;
609     } else {
610         $groupselect = "";
611         $groupjoin   = "";
612     }
614     $params['cminstance'] = $cm->instance;
615     $params['submissionmodified'] = $timestart;
616     $params['assessmentmodified'] = $timestart;
618     $authornamefields = get_all_user_name_fields(true, 'author', null, 'author');
619     $reviewerfields = get_all_user_name_fields(true, 'reviewer', null, 'reviewer');
621     $sql = "SELECT s.id AS submissionid, s.title AS submissiontitle, s.timemodified AS submissionmodified,
622                    author.id AS authorid, $authornamefields, author.picture AS authorpicture, author.imagealt AS authorimagealt,
623                    author.email AS authoremail, a.id AS assessmentid, a.timemodified AS assessmentmodified,
624                    reviewer.id AS reviewerid, $reviewerfields, reviewer.picture AS reviewerpicture,
625                    reviewer.imagealt AS reviewerimagealt, reviewer.email AS revieweremail
626               FROM {workshop_submissions} s
627         INNER JOIN {workshop} w ON s.workshopid = w.id
628         INNER JOIN {user} author ON s.authorid = author.id
629          LEFT JOIN {workshop_assessments} a ON a.submissionid = s.id
630          LEFT JOIN {user} reviewer ON a.reviewerid = reviewer.id
631         $groupjoin
632              WHERE w.id = :cminstance
633                    AND s.example = 0
634                    $userselect $groupselect
635                    AND (s.timemodified > :submissionmodified OR a.timemodified > :assessmentmodified)
636           ORDER BY s.timemodified ASC, a.timemodified ASC";
638     $rs = $DB->get_recordset_sql($sql, $params);
640     $groupmode       = groups_get_activity_groupmode($cm, $course);
641     $context         = context_module::instance($cm->id);
642     $grader          = has_capability('moodle/grade:viewall', $context);
643     $accessallgroups = has_capability('moodle/site:accessallgroups', $context);
644     $viewauthors     = has_capability('mod/workshop:viewauthornames', $context);
645     $viewreviewers   = has_capability('mod/workshop:viewreviewernames', $context);
647     $submissions = array(); // recent submissions indexed by submission id
648     $assessments = array(); // recent assessments indexed by assessment id
649     $users       = array();
651     foreach ($rs as $activity) {
653         // remember all user names we can use later
654         if (empty($users[$activity->authorid])) {
655             $u = new stdclass();
656             $additionalfields = explode(',', user_picture::fields());
657             $u = username_load_fields_from_object($u, $activity, 'author', $additionalfields);
658             $users[$activity->authorid] = $u;
659         }
660         if ($activity->reviewerid and empty($users[$activity->reviewerid])) {
661             $u = new stdclass();
662             $additionalfields = explode(',', user_picture::fields());
663             $u = username_load_fields_from_object($u, $activity, 'reviewer', $additionalfields);
664             $users[$activity->reviewerid] = $u;
665         }
667         if ($activity->submissionmodified > $timestart and empty($submissions[$activity->submissionid])) {
668             $s = new stdclass();
669             $s->id = $activity->submissionid;
670             $s->title = $activity->submissiontitle;
671             $s->authorid = $activity->authorid;
672             $s->timemodified = $activity->submissionmodified;
673             if ($activity->authorid == $USER->id || has_capability('mod/workshop:viewauthornames', $context)) {
674                 $s->authornamevisible = true;
675             } else {
676                 $s->authornamevisible = false;
677             }
679             // the following do-while wrapper allows to break from deeply nested if-statements
680             do {
681                 if ($s->authorid === $USER->id) {
682                     // own submissions always visible
683                     $submissions[$activity->submissionid] = $s;
684                     break;
685                 }
687                 if (has_capability('mod/workshop:viewallsubmissions', $context)) {
688                     if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
689                         if (isguestuser()) {
690                             // shortcut - guest user does not belong into any group
691                             break;
692                         }
694                         // this might be slow - show only submissions by users who share group with me in this cm
695                         if (!$modinfo->get_groups($cm->groupingid)) {
696                             break;
697                         }
698                         $authorsgroups = groups_get_all_groups($course->id, $s->authorid, $cm->groupingid);
699                         if (is_array($authorsgroups)) {
700                             $authorsgroups = array_keys($authorsgroups);
701                             $intersect = array_intersect($authorsgroups, $modinfo->get_groups($cm->groupingid));
702                             if (empty($intersect)) {
703                                 break;
704                             } else {
705                                 // can see all submissions and shares a group with the author
706                                 $submissions[$activity->submissionid] = $s;
707                                 break;
708                             }
709                         }
711                     } else {
712                         // can see all submissions from all groups
713                         $submissions[$activity->submissionid] = $s;
714                     }
715                 }
716             } while (0);
717         }
719         if ($activity->assessmentmodified > $timestart and empty($assessments[$activity->assessmentid])) {
720             $a = new stdclass();
721             $a->id = $activity->assessmentid;
722             $a->submissionid = $activity->submissionid;
723             $a->submissiontitle = $activity->submissiontitle;
724             $a->reviewerid = $activity->reviewerid;
725             $a->timemodified = $activity->assessmentmodified;
726             if ($activity->reviewerid == $USER->id || has_capability('mod/workshop:viewreviewernames', $context)) {
727                 $a->reviewernamevisible = true;
728             } else {
729                 $a->reviewernamevisible = false;
730             }
732             // the following do-while wrapper allows to break from deeply nested if-statements
733             do {
734                 if ($a->reviewerid === $USER->id) {
735                     // own assessments always visible
736                     $assessments[$activity->assessmentid] = $a;
737                     break;
738                 }
740                 if (has_capability('mod/workshop:viewallassessments', $context)) {
741                     if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
742                         if (isguestuser()) {
743                             // shortcut - guest user does not belong into any group
744                             break;
745                         }
747                         // this might be slow - show only submissions by users who share group with me in this cm
748                         if (!$modinfo->get_groups($cm->groupingid)) {
749                             break;
750                         }
751                         $reviewersgroups = groups_get_all_groups($course->id, $a->reviewerid, $cm->groupingid);
752                         if (is_array($reviewersgroups)) {
753                             $reviewersgroups = array_keys($reviewersgroups);
754                             $intersect = array_intersect($reviewersgroups, $modinfo->get_groups($cm->groupingid));
755                             if (empty($intersect)) {
756                                 break;
757                             } else {
758                                 // can see all assessments and shares a group with the reviewer
759                                 $assessments[$activity->assessmentid] = $a;
760                                 break;
761                             }
762                         }
764                     } else {
765                         // can see all assessments from all groups
766                         $assessments[$activity->assessmentid] = $a;
767                     }
768                 }
769             } while (0);
770         }
771     }
772     $rs->close();
774     $workshopname = format_string($cm->name, true);
776     if ($grader) {
777         require_once($CFG->libdir.'/gradelib.php');
778         $grades = grade_get_grades($courseid, 'mod', 'workshop', $cm->instance, array_keys($users));
779     }
781     foreach ($submissions as $submission) {
782         $tmpactivity                = new stdclass();
783         $tmpactivity->type          = 'workshop';
784         $tmpactivity->cmid          = $cm->id;
785         $tmpactivity->name          = $workshopname;
786         $tmpactivity->sectionnum    = $cm->sectionnum;
787         $tmpactivity->timestamp     = $submission->timemodified;
788         $tmpactivity->subtype       = 'submission';
789         $tmpactivity->content       = $submission;
790         if ($grader) {
791             $tmpactivity->grade     = $grades->items[0]->grades[$submission->authorid]->str_long_grade;
792         }
793         if ($submission->authornamevisible and !empty($users[$submission->authorid])) {
794             $tmpactivity->user      = $users[$submission->authorid];
795         }
796         $activities[$index++]       = $tmpactivity;
797     }
799     foreach ($assessments as $assessment) {
800         $tmpactivity                = new stdclass();
801         $tmpactivity->type          = 'workshop';
802         $tmpactivity->cmid          = $cm->id;
803         $tmpactivity->name          = $workshopname;
804         $tmpactivity->sectionnum    = $cm->sectionnum;
805         $tmpactivity->timestamp     = $assessment->timemodified;
806         $tmpactivity->subtype       = 'assessment';
807         $tmpactivity->content       = $assessment;
808         if ($grader) {
809             $tmpactivity->grade     = $grades->items[1]->grades[$assessment->reviewerid]->str_long_grade;
810         }
811         if ($assessment->reviewernamevisible and !empty($users[$assessment->reviewerid])) {
812             $tmpactivity->user      = $users[$assessment->reviewerid];
813         }
814         $activities[$index++]       = $tmpactivity;
815     }
818 /**
819  * Print single activity item prepared by {@see workshop_get_recent_mod_activity()}
820  */
821 function workshop_print_recent_mod_activity($activity, $courseid, $detail, $modnames, $viewfullnames) {
822     global $CFG, $OUTPUT;
824     if (!empty($activity->user)) {
825         echo html_writer::tag('div', $OUTPUT->user_picture($activity->user, array('courseid'=>$courseid)),
826                 array('style' => 'float: left; padding: 7px;'));
827     }
829     if ($activity->subtype == 'submission') {
830         echo html_writer::start_tag('div', array('class'=>'submission', 'style'=>'padding: 7px; float:left;'));
832         if ($detail) {
833             echo html_writer::start_tag('h4', array('class'=>'workshop'));
834             $url = new moodle_url('/mod/workshop/view.php', array('id'=>$activity->cmid));
835             $name = s($activity->name);
836             echo html_writer::empty_tag('img', array('src'=>$OUTPUT->pix_url('icon', $activity->type), 'class'=>'icon', 'alt'=>$name));
837             echo ' ' . $modnames[$activity->type];
838             echo html_writer::link($url, $name, array('class'=>'name', 'style'=>'margin-left: 5px'));
839             echo html_writer::end_tag('h4');
840         }
842         echo html_writer::start_tag('div', array('class'=>'title'));
843         $url = new moodle_url('/mod/workshop/submission.php', array('cmid'=>$activity->cmid, 'id'=>$activity->content->id));
844         $name = s($activity->content->title);
845         echo html_writer::tag('strong', html_writer::link($url, $name));
846         echo html_writer::end_tag('div');
848         if (!empty($activity->user)) {
849             echo html_writer::start_tag('div', array('class'=>'user'));
850             $url = new moodle_url('/user/view.php', array('id'=>$activity->user->id, 'course'=>$courseid));
851             $name = fullname($activity->user);
852             $link = html_writer::link($url, $name);
853             echo get_string('submissionby', 'workshop', $link);
854             echo ' - '.userdate($activity->timestamp);
855             echo html_writer::end_tag('div');
856         } else {
857             echo html_writer::start_tag('div', array('class'=>'anonymous'));
858             echo get_string('submission', 'workshop');
859             echo ' - '.userdate($activity->timestamp);
860             echo html_writer::end_tag('div');
861         }
863         echo html_writer::end_tag('div');
864     }
866     if ($activity->subtype == 'assessment') {
867         echo html_writer::start_tag('div', array('class'=>'assessment', 'style'=>'padding: 7px; float:left;'));
869         if ($detail) {
870             echo html_writer::start_tag('h4', array('class'=>'workshop'));
871             $url = new moodle_url('/mod/workshop/view.php', array('id'=>$activity->cmid));
872             $name = s($activity->name);
873             echo html_writer::empty_tag('img', array('src'=>$OUTPUT->pix_url('icon', $activity->type), 'class'=>'icon', 'alt'=>$name));
874             echo ' ' . $modnames[$activity->type];
875             echo html_writer::link($url, $name, array('class'=>'name', 'style'=>'margin-left: 5px'));
876             echo html_writer::end_tag('h4');
877         }
879         echo html_writer::start_tag('div', array('class'=>'title'));
880         $url = new moodle_url('/mod/workshop/assessment.php', array('asid'=>$activity->content->id));
881         $name = s($activity->content->submissiontitle);
882         echo html_writer::tag('em', html_writer::link($url, $name));
883         echo html_writer::end_tag('div');
885         if (!empty($activity->user)) {
886             echo html_writer::start_tag('div', array('class'=>'user'));
887             $url = new moodle_url('/user/view.php', array('id'=>$activity->user->id, 'course'=>$courseid));
888             $name = fullname($activity->user);
889             $link = html_writer::link($url, $name);
890             echo get_string('assessmentbyfullname', 'workshop', $link);
891             echo ' - '.userdate($activity->timestamp);
892             echo html_writer::end_tag('div');
893         } else {
894             echo html_writer::start_tag('div', array('class'=>'anonymous'));
895             echo get_string('assessment', 'workshop');
896             echo ' - '.userdate($activity->timestamp);
897             echo html_writer::end_tag('div');
898         }
900         echo html_writer::end_tag('div');
901     }
903     echo html_writer::empty_tag('br', array('style'=>'clear:both'));
906 /**
907  * Regular jobs to execute via cron
908  *
909  * @return boolean true on success, false otherwise
910  */
911 function workshop_cron() {
912     global $CFG, $DB;
914     $now = time();
916     mtrace(' processing workshop subplugins ...');
917     cron_execute_plugin_type('workshopallocation', 'workshop allocation methods');
919     // now when the scheduled allocator had a chance to do its job, check if there
920     // are some workshops to switch into the assessment phase
921     $workshops = $DB->get_records_select("workshop",
922         "phase = 20 AND phaseswitchassessment = 1 AND submissionend > 0 AND submissionend < ?", array($now));
924     if (!empty($workshops)) {
925         mtrace('Processing automatic assessment phase switch in '.count($workshops).' workshop(s) ... ', '');
926         require_once($CFG->dirroot.'/mod/workshop/locallib.php');
927         foreach ($workshops as $workshop) {
928             $cm = get_coursemodule_from_instance('workshop', $workshop->id, $workshop->course, false, MUST_EXIST);
929             $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
930             $workshop = new workshop($workshop, $cm, $course);
931             $workshop->switch_phase(workshop::PHASE_ASSESSMENT);
933             $params = array(
934                 'objectid' => $workshop->id,
935                 'context' => $workshop->context,
936                 'courseid' => $workshop->course->id,
937                 'other' => array(
938                     'workshopphase' => $workshop->phase
939                 )
940             );
941             $event = \mod_workshop\event\phase_switched::create($params);
942             $event->trigger();
944             // disable the automatic switching now so that it is not executed again by accident
945             // if the teacher changes the phase back to the submission one
946             $DB->set_field('workshop', 'phaseswitchassessment', 0, array('id' => $workshop->id));
948             // todo inform the teachers
949         }
950         mtrace('done');
951     }
953     return true;
956 /**
957  * Is a given scale used by the instance of workshop?
958  *
959  * The function asks all installed grading strategy subplugins. The workshop
960  * core itself does not use scales. Both grade for submission and grade for
961  * assessments do not use scales.
962  *
963  * @param int $workshopid id of workshop instance
964  * @param int $scaleid id of the scale to check
965  * @return bool
966  */
967 function workshop_scale_used($workshopid, $scaleid) {
968     global $CFG; // other files included from here
970     $strategies = core_component::get_plugin_list('workshopform');
971     foreach ($strategies as $strategy => $strategypath) {
972         $strategylib = $strategypath . '/lib.php';
973         if (is_readable($strategylib)) {
974             require_once($strategylib);
975         } else {
976             throw new coding_exception('the grading forms subplugin must contain library ' . $strategylib);
977         }
978         $classname = 'workshop_' . $strategy . '_strategy';
979         if (method_exists($classname, 'scale_used')) {
980             if (call_user_func_array(array($classname, 'scale_used'), array($scaleid, $workshopid))) {
981                 // no need to include any other files - scale is used
982                 return true;
983             }
984         }
985     }
987     return false;
990 /**
991  * Is a given scale used by any instance of workshop?
992  *
993  * The function asks all installed grading strategy subplugins. The workshop
994  * core itself does not use scales. Both grade for submission and grade for
995  * assessments do not use scales.
996  *
997  * @param int $scaleid id of the scale to check
998  * @return bool
999  */
1000 function workshop_scale_used_anywhere($scaleid) {
1001     global $CFG; // other files included from here
1003     $strategies = core_component::get_plugin_list('workshopform');
1004     foreach ($strategies as $strategy => $strategypath) {
1005         $strategylib = $strategypath . '/lib.php';
1006         if (is_readable($strategylib)) {
1007             require_once($strategylib);
1008         } else {
1009             throw new coding_exception('the grading forms subplugin must contain library ' . $strategylib);
1010         }
1011         $classname = 'workshop_' . $strategy . '_strategy';
1012         if (method_exists($classname, 'scale_used')) {
1013             if (call_user_func(array($classname, 'scale_used'), $scaleid)) {
1014                 // no need to include any other files - scale is used
1015                 return true;
1016             }
1017         }
1018     }
1020     return false;
1023 /**
1024  * Returns all other caps used in the module
1025  *
1026  * @return array
1027  */
1028 function workshop_get_extra_capabilities() {
1029     return array('moodle/site:accessallgroups');
1032 ////////////////////////////////////////////////////////////////////////////////
1033 // Gradebook API                                                              //
1034 ////////////////////////////////////////////////////////////////////////////////
1036 /**
1037  * Creates or updates grade items for the give workshop instance
1038  *
1039  * Needed by grade_update_mod_grades() in lib/gradelib.php. Also used by
1040  * {@link workshop_update_grades()}.
1041  *
1042  * @param stdClass $workshop instance object with extra cmidnumber property
1043  * @param stdClass $submissiongrades data for the first grade item
1044  * @param stdClass $assessmentgrades data for the second grade item
1045  * @return void
1046  */
1047 function workshop_grade_item_update(stdclass $workshop, $submissiongrades=null, $assessmentgrades=null) {
1048     global $CFG;
1049     require_once($CFG->libdir.'/gradelib.php');
1051     $a = new stdclass();
1052     $a->workshopname = clean_param($workshop->name, PARAM_NOTAGS);
1054     $item = array();
1055     $item['itemname'] = get_string('gradeitemsubmission', 'workshop', $a);
1056     $item['gradetype'] = GRADE_TYPE_VALUE;
1057     $item['grademax']  = $workshop->grade;
1058     $item['grademin']  = 0;
1059     grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 0, $submissiongrades , $item);
1061     $item = array();
1062     $item['itemname'] = get_string('gradeitemassessment', 'workshop', $a);
1063     $item['gradetype'] = GRADE_TYPE_VALUE;
1064     $item['grademax']  = $workshop->gradinggrade;
1065     $item['grademin']  = 0;
1066     grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 1, $assessmentgrades, $item);
1069 /**
1070  * Update workshop grades in the gradebook
1071  *
1072  * Needed by grade_update_mod_grades() in lib/gradelib.php
1073  *
1074  * @category grade
1075  * @param stdClass $workshop instance object with extra cmidnumber and modname property
1076  * @param int $userid        update grade of specific user only, 0 means all participants
1077  * @return void
1078  */
1079 function workshop_update_grades(stdclass $workshop, $userid=0) {
1080     global $CFG, $DB;
1081     require_once($CFG->libdir.'/gradelib.php');
1083     $whereuser = $userid ? ' AND authorid = :userid' : '';
1084     $params = array('workshopid' => $workshop->id, 'userid' => $userid);
1085     $sql = 'SELECT authorid, grade, gradeover, gradeoverby, feedbackauthor, feedbackauthorformat, timemodified, timegraded
1086               FROM {workshop_submissions}
1087              WHERE workshopid = :workshopid AND example=0' . $whereuser;
1088     $records = $DB->get_records_sql($sql, $params);
1089     $submissiongrades = array();
1090     foreach ($records as $record) {
1091         $grade = new stdclass();
1092         $grade->userid = $record->authorid;
1093         if (!is_null($record->gradeover)) {
1094             $grade->rawgrade = grade_floatval($workshop->grade * $record->gradeover / 100);
1095             $grade->usermodified = $record->gradeoverby;
1096         } else {
1097             $grade->rawgrade = grade_floatval($workshop->grade * $record->grade / 100);
1098         }
1099         $grade->feedback = $record->feedbackauthor;
1100         $grade->feedbackformat = $record->feedbackauthorformat;
1101         $grade->datesubmitted = $record->timemodified;
1102         $grade->dategraded = $record->timegraded;
1103         $submissiongrades[$record->authorid] = $grade;
1104     }
1106     $whereuser = $userid ? ' AND userid = :userid' : '';
1107     $params = array('workshopid' => $workshop->id, 'userid' => $userid);
1108     $sql = 'SELECT userid, gradinggrade, timegraded
1109               FROM {workshop_aggregations}
1110              WHERE workshopid = :workshopid' . $whereuser;
1111     $records = $DB->get_records_sql($sql, $params);
1112     $assessmentgrades = array();
1113     foreach ($records as $record) {
1114         $grade = new stdclass();
1115         $grade->userid = $record->userid;
1116         $grade->rawgrade = grade_floatval($workshop->gradinggrade * $record->gradinggrade / 100);
1117         $grade->dategraded = $record->timegraded;
1118         $assessmentgrades[$record->userid] = $grade;
1119     }
1121     workshop_grade_item_update($workshop, $submissiongrades, $assessmentgrades);
1124 /**
1125  * Update the grade items categories if they are changed via mod_form.php
1126  *
1127  * We must do it manually here in the workshop module because modedit supports only
1128  * single grade item while we use two.
1129  *
1130  * @param stdClass $workshop An object from the form in mod_form.php
1131  */
1132 function workshop_grade_item_category_update($workshop) {
1134     $gradeitems = grade_item::fetch_all(array(
1135         'itemtype'      => 'mod',
1136         'itemmodule'    => 'workshop',
1137         'iteminstance'  => $workshop->id,
1138         'courseid'      => $workshop->course));
1140     if (!empty($gradeitems)) {
1141         foreach ($gradeitems as $gradeitem) {
1142             if ($gradeitem->itemnumber == 0) {
1143                 if ($gradeitem->categoryid != $workshop->gradecategory) {
1144                     $gradeitem->set_parent($workshop->gradecategory);
1145                 }
1146             } else if ($gradeitem->itemnumber == 1) {
1147                 if ($gradeitem->categoryid != $workshop->gradinggradecategory) {
1148                     $gradeitem->set_parent($workshop->gradinggradecategory);
1149                 }
1150             }
1151             if (!empty($workshop->add)) {
1152                 $gradecategory = $gradeitem->get_parent_category();
1153                 if (grade_category::aggregation_uses_aggregationcoef($gradecategory->aggregation)) {
1154                     if ($gradecategory->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN) {
1155                         $gradeitem->aggregationcoef = 1;
1156                     } else {
1157                         $gradeitem->aggregationcoef = 0;
1158                     }
1159                     $gradeitem->update();
1160                 }
1161             }
1162         }
1163     }
1166 ////////////////////////////////////////////////////////////////////////////////
1167 // File API                                                                   //
1168 ////////////////////////////////////////////////////////////////////////////////
1170 /**
1171  * Returns the lists of all browsable file areas within the given module context
1172  *
1173  * The file area workshop_intro for the activity introduction field is added automatically
1174  * by {@link file_browser::get_file_info_context_module()}
1175  *
1176  * @package  mod_workshop
1177  * @category files
1178  *
1179  * @param stdClass $course
1180  * @param stdClass $cm
1181  * @param stdClass $context
1182  * @return array of [(string)filearea] => (string)description
1183  */
1184 function workshop_get_file_areas($course, $cm, $context) {
1185     $areas = array();
1186     $areas['instructauthors']          = get_string('areainstructauthors', 'workshop');
1187     $areas['instructreviewers']        = get_string('areainstructreviewers', 'workshop');
1188     $areas['submission_content']       = get_string('areasubmissioncontent', 'workshop');
1189     $areas['submission_attachment']    = get_string('areasubmissionattachment', 'workshop');
1190     $areas['conclusion']               = get_string('areaconclusion', 'workshop');
1191     $areas['overallfeedback_content']  = get_string('areaoverallfeedbackcontent', 'workshop');
1192     $areas['overallfeedback_attachment'] = get_string('areaoverallfeedbackattachment', 'workshop');
1194     return $areas;
1197 /**
1198  * Serves the files from the workshop file areas
1199  *
1200  * Apart from module intro (handled by pluginfile.php automatically), workshop files may be
1201  * media inserted into submission content (like images) and submission attachments. For these two,
1202  * the fileareas submission_content and submission_attachment are used.
1203  * Besides that, areas instructauthors, instructreviewers and conclusion contain the media
1204  * embedded using the mod_form.php.
1205  *
1206  * @package  mod_workshop
1207  * @category files
1208  *
1209  * @param stdClass $course the course object
1210  * @param stdClass $cm the course module object
1211  * @param stdClass $context the workshop's context
1212  * @param string $filearea the name of the file area
1213  * @param array $args extra arguments (itemid, path)
1214  * @param bool $forcedownload whether or not force download
1215  * @param array $options additional options affecting the file serving
1216  * @return bool false if the file not found, just send the file otherwise and do not return anything
1217  */
1218 function workshop_pluginfile($course, $cm, $context, $filearea, array $args, $forcedownload, array $options=array()) {
1219     global $DB, $CFG, $USER;
1221     if ($context->contextlevel != CONTEXT_MODULE) {
1222         return false;
1223     }
1225     require_login($course, true, $cm);
1227     if ($filearea === 'instructauthors') {
1228         array_shift($args); // itemid is ignored here
1229         $relativepath = implode('/', $args);
1230         $fullpath = "/$context->id/mod_workshop/$filearea/0/$relativepath";
1232         $fs = get_file_storage();
1233         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1234             send_file_not_found();
1235         }
1237         // finally send the file
1238         send_stored_file($file, null, 0, $forcedownload, $options);
1240     } else if ($filearea === 'instructreviewers') {
1241         array_shift($args); // itemid is ignored here
1242         $relativepath = implode('/', $args);
1243         $fullpath = "/$context->id/mod_workshop/$filearea/0/$relativepath";
1245         $fs = get_file_storage();
1246         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1247             send_file_not_found();
1248         }
1250         // finally send the file
1251         send_stored_file($file, null, 0, $forcedownload, $options);
1253     } else if ($filearea === 'conclusion') {
1254         array_shift($args); // itemid is ignored here
1255         $relativepath = implode('/', $args);
1256         $fullpath = "/$context->id/mod_workshop/$filearea/0/$relativepath";
1258         $fs = get_file_storage();
1259         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1260             send_file_not_found();
1261         }
1263         // finally send the file
1264         send_stored_file($file, null, 0, $forcedownload, $options);
1266     } else if ($filearea === 'submission_content' or $filearea === 'submission_attachment') {
1267         $itemid = (int)array_shift($args);
1268         if (!$workshop = $DB->get_record('workshop', array('id' => $cm->instance))) {
1269             return false;
1270         }
1271         if (!$submission = $DB->get_record('workshop_submissions', array('id' => $itemid, 'workshopid' => $workshop->id))) {
1272             return false;
1273         }
1275         // make sure the user is allowed to see the file
1276         if (empty($submission->example)) {
1277             if ($USER->id != $submission->authorid) {
1278                 if ($submission->published == 1 and $workshop->phase == 50
1279                         and has_capability('mod/workshop:viewpublishedsubmissions', $context)) {
1280                     // Published submission, we can go (workshop does not take the group mode
1281                     // into account in this case yet).
1282                 } else if (!$DB->record_exists('workshop_assessments', array('submissionid' => $submission->id, 'reviewerid' => $USER->id))) {
1283                     if (!has_capability('mod/workshop:viewallsubmissions', $context)) {
1284                         send_file_not_found();
1285                     } else {
1286                         $gmode = groups_get_activity_groupmode($cm, $course);
1287                         if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1288                             // check there is at least one common group with both the $USER
1289                             // and the submission author
1290                             $sql = "SELECT 'x'
1291                                       FROM {workshop_submissions} s
1292                                       JOIN {user} a ON (a.id = s.authorid)
1293                                       JOIN {groups_members} agm ON (a.id = agm.userid)
1294                                       JOIN {user} u ON (u.id = ?)
1295                                       JOIN {groups_members} ugm ON (u.id = ugm.userid)
1296                                      WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1297                             $params = array($USER->id, $workshop->id, $submission->id);
1298                             if (!$DB->record_exists_sql($sql, $params)) {
1299                                 send_file_not_found();
1300                             }
1301                         }
1302                     }
1303                 }
1304             }
1305         }
1307         $fs = get_file_storage();
1308         $relativepath = implode('/', $args);
1309         $fullpath = "/$context->id/mod_workshop/$filearea/$itemid/$relativepath";
1310         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1311             return false;
1312         }
1313         // finally send the file
1314         // these files are uploaded by students - forcing download for security reasons
1315         send_stored_file($file, 0, 0, true, $options);
1317     } else if ($filearea === 'overallfeedback_content' or $filearea === 'overallfeedback_attachment') {
1318         $itemid = (int)array_shift($args);
1319         if (!$workshop = $DB->get_record('workshop', array('id' => $cm->instance))) {
1320             return false;
1321         }
1322         if (!$assessment = $DB->get_record('workshop_assessments', array('id' => $itemid))) {
1323             return false;
1324         }
1325         if (!$submission = $DB->get_record('workshop_submissions', array('id' => $assessment->submissionid, 'workshopid' => $workshop->id))) {
1326             return false;
1327         }
1329         if ($USER->id == $assessment->reviewerid) {
1330             // Reviewers can always see their own files.
1331         } else if ($USER->id == $submission->authorid and $workshop->phase == 50) {
1332             // Authors can see the feedback once the workshop is closed.
1333         } else if (!empty($submission->example) and $assessment->weight == 1) {
1334             // Reference assessments of example submissions can be displayed.
1335         } else if (!has_capability('mod/workshop:viewallassessments', $context)) {
1336             send_file_not_found();
1337         } else {
1338             $gmode = groups_get_activity_groupmode($cm, $course);
1339             if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1340                 // Check there is at least one common group with both the $USER
1341                 // and the submission author.
1342                 $sql = "SELECT 'x'
1343                           FROM {workshop_submissions} s
1344                           JOIN {user} a ON (a.id = s.authorid)
1345                           JOIN {groups_members} agm ON (a.id = agm.userid)
1346                           JOIN {user} u ON (u.id = ?)
1347                           JOIN {groups_members} ugm ON (u.id = ugm.userid)
1348                          WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1349                 $params = array($USER->id, $workshop->id, $submission->id);
1350                 if (!$DB->record_exists_sql($sql, $params)) {
1351                     send_file_not_found();
1352                 }
1353             }
1354         }
1356         $fs = get_file_storage();
1357         $relativepath = implode('/', $args);
1358         $fullpath = "/$context->id/mod_workshop/$filearea/$itemid/$relativepath";
1359         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1360             return false;
1361         }
1362         // finally send the file
1363         // these files are uploaded by students - forcing download for security reasons
1364         send_stored_file($file, 0, 0, true, $options);
1365     }
1367     return false;
1370 /**
1371  * File browsing support for workshop file areas
1372  *
1373  * @package  mod_workshop
1374  * @category files
1375  *
1376  * @param file_browser $browser
1377  * @param array $areas
1378  * @param stdClass $course
1379  * @param stdClass $cm
1380  * @param stdClass $context
1381  * @param string $filearea
1382  * @param int $itemid
1383  * @param string $filepath
1384  * @param string $filename
1385  * @return file_info instance or null if not found
1386  */
1387 function workshop_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
1388     global $CFG, $DB, $USER;
1390     /** @var array internal cache for author names */
1391     static $submissionauthors = array();
1393     $fs = get_file_storage();
1395     if ($filearea === 'submission_content' or $filearea === 'submission_attachment') {
1397         if (!has_capability('mod/workshop:viewallsubmissions', $context)) {
1398             return null;
1399         }
1401         if (is_null($itemid)) {
1402             // no itemid (submissionid) passed, display the list of all submissions
1403             require_once($CFG->dirroot . '/mod/workshop/fileinfolib.php');
1404             return new workshop_file_info_submissions_container($browser, $course, $cm, $context, $areas, $filearea);
1405         }
1407         // make sure the user can see the particular submission in separate groups mode
1408         $gmode = groups_get_activity_groupmode($cm, $course);
1410         if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1411             // check there is at least one common group with both the $USER
1412             // and the submission author (this is not expected to be a frequent
1413             // usecase so we can live with pretty ineffective one query per submission here...)
1414             $sql = "SELECT 'x'
1415                       FROM {workshop_submissions} s
1416                       JOIN {user} a ON (a.id = s.authorid)
1417                       JOIN {groups_members} agm ON (a.id = agm.userid)
1418                       JOIN {user} u ON (u.id = ?)
1419                       JOIN {groups_members} ugm ON (u.id = ugm.userid)
1420                      WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1421             $params = array($USER->id, $cm->instance, $itemid);
1422             if (!$DB->record_exists_sql($sql, $params)) {
1423                 return null;
1424             }
1425         }
1427         // we are inside some particular submission container
1429         $filepath = is_null($filepath) ? '/' : $filepath;
1430         $filename = is_null($filename) ? '.' : $filename;
1432         if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, $itemid, $filepath, $filename)) {
1433             if ($filepath === '/' and $filename === '.') {
1434                 $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, $itemid);
1435             } else {
1436                 // not found
1437                 return null;
1438             }
1439         }
1441         // Checks to see if the user can manage files or is the owner.
1442         // TODO MDL-33805 - Do not use userid here and move the capability check above.
1443         if (!has_capability('moodle/course:managefiles', $context) && $storedfile->get_userid() != $USER->id) {
1444             return null;
1445         }
1447         // let us display the author's name instead of itemid (submission id)
1449         if (isset($submissionauthors[$itemid])) {
1450             $topvisiblename = $submissionauthors[$itemid];
1452         } else {
1454             $sql = "SELECT s.id, u.lastname, u.firstname
1455                       FROM {workshop_submissions} s
1456                       JOIN {user} u ON (s.authorid = u.id)
1457                      WHERE s.example = 0 AND s.workshopid = ?";
1458             $params = array($cm->instance);
1459             $rs = $DB->get_recordset_sql($sql, $params);
1461             foreach ($rs as $submissionauthor) {
1462                 $title = s(fullname($submissionauthor)); // this is generally not unique...
1463                 $submissionauthors[$submissionauthor->id] = $title;
1464             }
1465             $rs->close();
1467             if (!isset($submissionauthors[$itemid])) {
1468                 // should not happen
1469                 return null;
1470             } else {
1471                 $topvisiblename = $submissionauthors[$itemid];
1472             }
1473         }
1475         $urlbase = $CFG->wwwroot . '/pluginfile.php';
1476         // do not allow manual modification of any files!
1477         return new file_info_stored($browser, $context, $storedfile, $urlbase, $topvisiblename, true, true, false, false);
1478     }
1480     if ($filearea === 'overallfeedback_content' or $filearea === 'overallfeedback_attachment') {
1482         if (!has_capability('mod/workshop:viewallassessments', $context)) {
1483             return null;
1484         }
1486         if (is_null($itemid)) {
1487             // No itemid (assessmentid) passed, display the list of all assessments.
1488             require_once($CFG->dirroot . '/mod/workshop/fileinfolib.php');
1489             return new workshop_file_info_overallfeedback_container($browser, $course, $cm, $context, $areas, $filearea);
1490         }
1492         // Make sure the user can see the particular assessment in separate groups mode.
1493         $gmode = groups_get_activity_groupmode($cm, $course);
1494         if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1495             // Check there is at least one common group with both the $USER
1496             // and the submission author.
1497             $sql = "SELECT 'x'
1498                       FROM {workshop_submissions} s
1499                       JOIN {user} a ON (a.id = s.authorid)
1500                       JOIN {groups_members} agm ON (a.id = agm.userid)
1501                       JOIN {user} u ON (u.id = ?)
1502                       JOIN {groups_members} ugm ON (u.id = ugm.userid)
1503                      WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1504             $params = array($USER->id, $cm->instance, $itemid);
1505             if (!$DB->record_exists_sql($sql, $params)) {
1506                 return null;
1507             }
1508         }
1510         // We are inside a particular assessment container.
1511         $filepath = is_null($filepath) ? '/' : $filepath;
1512         $filename = is_null($filename) ? '.' : $filename;
1514         if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, $itemid, $filepath, $filename)) {
1515             if ($filepath === '/' and $filename === '.') {
1516                 $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, $itemid);
1517             } else {
1518                 // Not found
1519                 return null;
1520             }
1521         }
1523         // Check to see if the user can manage files or is the owner.
1524         if (!has_capability('moodle/course:managefiles', $context) and $storedfile->get_userid() != $USER->id) {
1525             return null;
1526         }
1528         $urlbase = $CFG->wwwroot . '/pluginfile.php';
1530         // Do not allow manual modification of any files.
1531         return new file_info_stored($browser, $context, $storedfile, $urlbase, $itemid, true, true, false, false);
1532     }
1534     if ($filearea == 'instructauthors' or $filearea == 'instructreviewers' or $filearea == 'conclusion') {
1535         // always only itemid 0
1537         $filepath = is_null($filepath) ? '/' : $filepath;
1538         $filename = is_null($filename) ? '.' : $filename;
1540         $urlbase = $CFG->wwwroot.'/pluginfile.php';
1541         if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, 0, $filepath, $filename)) {
1542             if ($filepath === '/' and $filename === '.') {
1543                 $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, 0);
1544             } else {
1545                 // not found
1546                 return null;
1547             }
1548         }
1549         return new file_info_stored($browser, $context, $storedfile, $urlbase, $areas[$filearea], false, true, true, false);
1550     }
1553 ////////////////////////////////////////////////////////////////////////////////
1554 // Navigation API                                                             //
1555 ////////////////////////////////////////////////////////////////////////////////
1557 /**
1558  * Extends the global navigation tree by adding workshop nodes if there is a relevant content
1559  *
1560  * This can be called by an AJAX request so do not rely on $PAGE as it might not be set up properly.
1561  *
1562  * @param navigation_node $navref An object representing the navigation tree node of the workshop module instance
1563  * @param stdClass $course
1564  * @param stdClass $module
1565  * @param cm_info $cm
1566  */
1567 function workshop_extend_navigation(navigation_node $navref, stdclass $course, stdclass $module, cm_info $cm) {
1568     global $CFG;
1570     if (has_capability('mod/workshop:submit', context_module::instance($cm->id))) {
1571         $url = new moodle_url('/mod/workshop/submission.php', array('cmid' => $cm->id));
1572         $mysubmission = $navref->add(get_string('mysubmission', 'workshop'), $url);
1573         $mysubmission->mainnavonly = true;
1574     }
1577 /**
1578  * Extends the settings navigation with the Workshop settings
1580  * This function is called when the context for the page is a workshop module. This is not called by AJAX
1581  * so it is safe to rely on the $PAGE.
1582  *
1583  * @param settings_navigation $settingsnav {@link settings_navigation}
1584  * @param navigation_node $workshopnode {@link navigation_node}
1585  */
1586 function workshop_extend_settings_navigation(settings_navigation $settingsnav, navigation_node $workshopnode=null) {
1587     global $PAGE;
1589     //$workshopobject = $DB->get_record("workshop", array("id" => $PAGE->cm->instance));
1591     if (has_capability('mod/workshop:editdimensions', $PAGE->cm->context)) {
1592         $url = new moodle_url('/mod/workshop/editform.php', array('cmid' => $PAGE->cm->id));
1593         $workshopnode->add(get_string('editassessmentform', 'workshop'), $url, settings_navigation::TYPE_SETTING);
1594     }
1595     if (has_capability('mod/workshop:allocate', $PAGE->cm->context)) {
1596         $url = new moodle_url('/mod/workshop/allocation.php', array('cmid' => $PAGE->cm->id));
1597         $workshopnode->add(get_string('allocate', 'workshop'), $url, settings_navigation::TYPE_SETTING);
1598     }
1601 /**
1602  * Return a list of page types
1603  * @param string $pagetype current page type
1604  * @param stdClass $parentcontext Block's parent context
1605  * @param stdClass $currentcontext Current context of block
1606  */
1607 function workshop_page_type_list($pagetype, $parentcontext, $currentcontext) {
1608     $module_pagetype = array('mod-workshop-*'=>get_string('page-mod-workshop-x', 'workshop'));
1609     return $module_pagetype;
1612 ////////////////////////////////////////////////////////////////////////////////
1613 // Calendar API                                                               //
1614 ////////////////////////////////////////////////////////////////////////////////
1616 /**
1617  * Updates the calendar events associated to the given workshop
1618  *
1619  * @param stdClass $workshop the workshop instance record
1620  * @param int $cmid course module id
1621  */
1622 function workshop_calendar_update(stdClass $workshop, $cmid) {
1623     global $DB;
1625     // get the currently registered events so that we can re-use their ids
1626     $currentevents = $DB->get_records('event', array('modulename' => 'workshop', 'instance' => $workshop->id));
1628     // the common properties for all events
1629     $base = new stdClass();
1630     $base->description  = format_module_intro('workshop', $workshop, $cmid, false);
1631     $base->courseid     = $workshop->course;
1632     $base->groupid      = 0;
1633     $base->userid       = 0;
1634     $base->modulename   = 'workshop';
1635     $base->eventtype    = 'pluginname';
1636     $base->instance     = $workshop->id;
1637     $base->visible      = instance_is_visible('workshop', $workshop);
1638     $base->timeduration = 0;
1640     if ($workshop->submissionstart) {
1641         $event = clone($base);
1642         $event->name = get_string('submissionstartevent', 'mod_workshop', $workshop->name);
1643         $event->timestart = $workshop->submissionstart;
1644         if ($reusedevent = array_shift($currentevents)) {
1645             $event->id = $reusedevent->id;
1646         } else {
1647             // should not be set but just in case
1648             unset($event->id);
1649         }
1650         // update() will reuse a db record if the id field is set
1651         $eventobj = new calendar_event($event);
1652         $eventobj->update($event, false);
1653     }
1655     if ($workshop->submissionend) {
1656         $event = clone($base);
1657         $event->name = get_string('submissionendevent', 'mod_workshop', $workshop->name);
1658         $event->timestart = $workshop->submissionend;
1659         if ($reusedevent = array_shift($currentevents)) {
1660             $event->id = $reusedevent->id;
1661         } else {
1662             // should not be set but just in case
1663             unset($event->id);
1664         }
1665         // update() will reuse a db record if the id field is set
1666         $eventobj = new calendar_event($event);
1667         $eventobj->update($event, false);
1668     }
1670     if ($workshop->assessmentstart) {
1671         $event = clone($base);
1672         $event->name = get_string('assessmentstartevent', 'mod_workshop', $workshop->name);
1673         $event->timestart = $workshop->assessmentstart;
1674         if ($reusedevent = array_shift($currentevents)) {
1675             $event->id = $reusedevent->id;
1676         } else {
1677             // should not be set but just in case
1678             unset($event->id);
1679         }
1680         // update() will reuse a db record if the id field is set
1681         $eventobj = new calendar_event($event);
1682         $eventobj->update($event, false);
1683     }
1685     if ($workshop->assessmentend) {
1686         $event = clone($base);
1687         $event->name = get_string('assessmentendevent', 'mod_workshop', $workshop->name);
1688         $event->timestart = $workshop->assessmentend;
1689         if ($reusedevent = array_shift($currentevents)) {
1690             $event->id = $reusedevent->id;
1691         } else {
1692             // should not be set but just in case
1693             unset($event->id);
1694         }
1695         // update() will reuse a db record if the id field is set
1696         $eventobj = new calendar_event($event);
1697         $eventobj->update($event, false);
1698     }
1700     // delete any leftover events
1701     foreach ($currentevents as $oldevent) {
1702         $oldevent = calendar_event::load($oldevent);
1703         $oldevent->delete();
1704     }
1707 ////////////////////////////////////////////////////////////////////////////////
1708 // Course reset API                                                           //
1709 ////////////////////////////////////////////////////////////////////////////////
1711 /**
1712  * Extends the course reset form with workshop specific settings.
1713  *
1714  * @param MoodleQuickForm $mform
1715  */
1716 function workshop_reset_course_form_definition($mform) {
1718     $mform->addElement('header', 'workshopheader', get_string('modulenameplural', 'mod_workshop'));
1720     $mform->addElement('advcheckbox', 'reset_workshop_submissions', get_string('resetsubmissions', 'mod_workshop'));
1721     $mform->addHelpButton('reset_workshop_submissions', 'resetsubmissions', 'mod_workshop');
1723     $mform->addElement('advcheckbox', 'reset_workshop_assessments', get_string('resetassessments', 'mod_workshop'));
1724     $mform->addHelpButton('reset_workshop_assessments', 'resetassessments', 'mod_workshop');
1725     $mform->disabledIf('reset_workshop_assessments', 'reset_workshop_submissions', 'checked');
1727     $mform->addElement('advcheckbox', 'reset_workshop_phase', get_string('resetphase', 'mod_workshop'));
1728     $mform->addHelpButton('reset_workshop_phase', 'resetphase', 'mod_workshop');
1731 /**
1732  * Provides default values for the workshop settings in the course reset form.
1733  *
1734  * @param stdClass $course The course to be reset.
1735  */
1736 function workshop_reset_course_form_defaults(stdClass $course) {
1738     $defaults = array(
1739         'reset_workshop_submissions'    => 1,
1740         'reset_workshop_assessments'    => 1,
1741         'reset_workshop_phase'          => 1,
1742     );
1744     return $defaults;
1747 /**
1748  * Performs the reset of all workshop instances in the course.
1749  *
1750  * @param stdClass $data The actual course reset settings.
1751  * @return array List of results, each being array[(string)component, (string)item, (string)error]
1752  */
1753 function workshop_reset_userdata(stdClass $data) {
1754     global $CFG, $DB;
1756     if (empty($data->reset_workshop_submissions)
1757             and empty($data->reset_workshop_assessments)
1758             and empty($data->reset_workshop_phase) ) {
1759         // Nothing to do here.
1760         return array();
1761     }
1763     $workshoprecords = $DB->get_records('workshop', array('course' => $data->courseid));
1765     if (empty($workshoprecords)) {
1766         // What a boring course - no workshops here!
1767         return array();
1768     }
1770     require_once($CFG->dirroot . '/mod/workshop/locallib.php');
1772     $course = $DB->get_record('course', array('id' => $data->courseid), '*', MUST_EXIST);
1773     $status = array();
1775     foreach ($workshoprecords as $workshoprecord) {
1776         $cm = get_coursemodule_from_instance('workshop', $workshoprecord->id, $course->id, false, MUST_EXIST);
1777         $workshop = new workshop($workshoprecord, $cm, $course);
1778         $status = array_merge($status, $workshop->reset_userdata($data));
1779     }
1781     return $status;