6efdf302e5742f7189117c3a945b6ffac4d3b6a6
[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  * @return stdclass|null
258  */
259 function workshop_user_outline($course, $user, $mod, $workshop) {
260     global $CFG, $DB;
261     require_once($CFG->libdir.'/gradelib.php');
263     $grades = grade_get_grades($course->id, 'mod', 'workshop', $workshop->id, $user->id);
265     $submissiongrade = null;
266     $assessmentgrade = null;
268     $info = '';
269     $time = 0;
271     if (!empty($grades->items[0]->grades)) {
272         $submissiongrade = reset($grades->items[0]->grades);
273         $info .= get_string('submissiongrade', 'workshop') . ': ' . $submissiongrade->str_long_grade . html_writer::empty_tag('br');
274         $time = max($time, $submissiongrade->dategraded);
275     }
276     if (!empty($grades->items[1]->grades)) {
277         $assessmentgrade = reset($grades->items[1]->grades);
278         $info .= get_string('gradinggrade', 'workshop') . ': ' . $assessmentgrade->str_long_grade;
279         $time = max($time, $assessmentgrade->dategraded);
280     }
282     if (!empty($info) and !empty($time)) {
283         $return = new stdclass();
284         $return->time = $time;
285         $return->info = $info;
286         return $return;
287     }
289     return null;
292 /**
293  * Print a detailed representation of what a user has done with
294  * a given particular instance of this module, for user activity reports.
295  *
296  * @return string HTML
297  */
298 function workshop_user_complete($course, $user, $mod, $workshop) {
299     global $CFG, $DB, $OUTPUT;
300     require_once(dirname(__FILE__).'/locallib.php');
301     require_once($CFG->libdir.'/gradelib.php');
303     $workshop   = new workshop($workshop, $mod, $course);
304     $grades     = grade_get_grades($course->id, 'mod', 'workshop', $workshop->id, $user->id);
306     if (!empty($grades->items[0]->grades)) {
307         $submissiongrade = reset($grades->items[0]->grades);
308         $info = get_string('submissiongrade', 'workshop') . ': ' . $submissiongrade->str_long_grade;
309         echo html_writer::tag('li', $info, array('class'=>'submissiongrade'));
310     }
311     if (!empty($grades->items[1]->grades)) {
312         $assessmentgrade = reset($grades->items[1]->grades);
313         $info = get_string('gradinggrade', 'workshop') . ': ' . $assessmentgrade->str_long_grade;
314         echo html_writer::tag('li', $info, array('class'=>'gradinggrade'));
315     }
317     if (has_capability('mod/workshop:viewallsubmissions', $workshop->context)) {
318         $canviewsubmission = true;
319         if (groups_get_activity_groupmode($workshop->cm) == SEPARATEGROUPS) {
320             // user must have accessallgroups or share at least one group with the submission author
321             if (!has_capability('moodle/site:accessallgroups', $workshop->context)) {
322                 $usersgroups = groups_get_activity_allowed_groups($workshop->cm);
323                 $authorsgroups = groups_get_all_groups($workshop->course->id, $user->id, $workshop->cm->groupingid, 'g.id');
324                 $sharedgroups = array_intersect_key($usersgroups, $authorsgroups);
325                 if (empty($sharedgroups)) {
326                     $canviewsubmission = false;
327                 }
328             }
329         }
330         if ($canviewsubmission and $submission = $workshop->get_submission_by_author($user->id)) {
331             $title      = format_string($submission->title);
332             $url        = $workshop->submission_url($submission->id);
333             $link       = html_writer::link($url, $title);
334             $info       = get_string('submission', 'workshop').': '.$link;
335             echo html_writer::tag('li', $info, array('class'=>'submission'));
336         }
337     }
339     if (has_capability('mod/workshop:viewallassessments', $workshop->context)) {
340         if ($assessments = $workshop->get_assessments_by_reviewer($user->id)) {
341             foreach ($assessments as $assessment) {
342                 $a = new stdclass();
343                 $a->submissionurl = $workshop->submission_url($assessment->submissionid)->out();
344                 $a->assessmenturl = $workshop->assess_url($assessment->id)->out();
345                 $a->submissiontitle = s($assessment->submissiontitle);
346                 echo html_writer::tag('li', get_string('assessmentofsubmission', 'workshop', $a));
347             }
348         }
349     }
352 /**
353  * Given a course and a time, this module should find recent activity
354  * that has occurred in workshop activities and print it out.
355  * Return true if there was output, or false is there was none.
356  *
357  * @param stdClass $course
358  * @param bool $viewfullnames
359  * @param int $timestart
360  * @return boolean
361  */
362 function workshop_print_recent_activity($course, $viewfullnames, $timestart) {
363     global $CFG, $USER, $DB, $OUTPUT;
365     $authoramefields = get_all_user_name_fields(true, 'author', null, 'author');
366     $reviewerfields = get_all_user_name_fields(true, 'reviewer', null, 'reviewer');
368     $sql = "SELECT s.id AS submissionid, s.title AS submissiontitle, s.timemodified AS submissionmodified,
369                    author.id AS authorid, $authoramefields, a.id AS assessmentid, a.timemodified AS assessmentmodified,
370                    reviewer.id AS reviewerid, $reviewerfields, cm.id AS cmid
371               FROM {workshop} w
372         INNER JOIN {course_modules} cm ON cm.instance = w.id
373         INNER JOIN {modules} md ON md.id = cm.module
374         INNER JOIN {workshop_submissions} s ON s.workshopid = w.id
375         INNER JOIN {user} author ON s.authorid = author.id
376          LEFT JOIN {workshop_assessments} a ON a.submissionid = s.id
377          LEFT JOIN {user} reviewer ON a.reviewerid = reviewer.id
378              WHERE cm.course = ?
379                    AND md.name = 'workshop'
380                    AND s.example = 0
381                    AND (s.timemodified > ? OR a.timemodified > ?)
382           ORDER BY s.timemodified";
384     $rs = $DB->get_recordset_sql($sql, array($course->id, $timestart, $timestart));
386     $modinfo = get_fast_modinfo($course); // reference needed because we might load the groups
388     $submissions = array(); // recent submissions indexed by submission id
389     $assessments = array(); // recent assessments indexed by assessment id
390     $users       = array();
392     foreach ($rs as $activity) {
393         if (!array_key_exists($activity->cmid, $modinfo->cms)) {
394             // this should not happen but just in case
395             continue;
396         }
398         $cm = $modinfo->cms[$activity->cmid];
399         if (!$cm->uservisible) {
400             continue;
401         }
403         // remember all user names we can use later
404         if (empty($users[$activity->authorid])) {
405             $u = new stdclass();
406             $users[$activity->authorid] = username_load_fields_from_object($u, $activity, 'author');
407         }
408         if ($activity->reviewerid and empty($users[$activity->reviewerid])) {
409             $u = new stdclass();
410             $users[$activity->reviewerid] = username_load_fields_from_object($u, $activity, 'reviewer');
411         }
413         $context = context_module::instance($cm->id);
414         $groupmode = groups_get_activity_groupmode($cm, $course);
416         if ($activity->submissionmodified > $timestart and empty($submissions[$activity->submissionid])) {
417             $s = new stdclass();
418             $s->title = $activity->submissiontitle;
419             $s->authorid = $activity->authorid;
420             $s->timemodified = $activity->submissionmodified;
421             $s->cmid = $activity->cmid;
422             if ($activity->authorid == $USER->id || has_capability('mod/workshop:viewauthornames', $context)) {
423                 $s->authornamevisible = true;
424             } else {
425                 $s->authornamevisible = false;
426             }
428             // the following do-while wrapper allows to break from deeply nested if-statements
429             do {
430                 if ($s->authorid === $USER->id) {
431                     // own submissions always visible
432                     $submissions[$activity->submissionid] = $s;
433                     break;
434                 }
436                 if (has_capability('mod/workshop:viewallsubmissions', $context)) {
437                     if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
438                         if (isguestuser()) {
439                             // shortcut - guest user does not belong into any group
440                             break;
441                         }
443                         // this might be slow - show only submissions by users who share group with me in this cm
444                         if (!$modinfo->get_groups($cm->groupingid)) {
445                             break;
446                         }
447                         $authorsgroups = groups_get_all_groups($course->id, $s->authorid, $cm->groupingid);
448                         if (is_array($authorsgroups)) {
449                             $authorsgroups = array_keys($authorsgroups);
450                             $intersect = array_intersect($authorsgroups, $modinfo->get_groups($cm->groupingid));
451                             if (empty($intersect)) {
452                                 break;
453                             } else {
454                                 // can see all submissions and shares a group with the author
455                                 $submissions[$activity->submissionid] = $s;
456                                 break;
457                             }
458                         }
460                     } else {
461                         // can see all submissions from all groups
462                         $submissions[$activity->submissionid] = $s;
463                     }
464                 }
465             } while (0);
466         }
468         if ($activity->assessmentmodified > $timestart and empty($assessments[$activity->assessmentid])) {
469             $a = new stdclass();
470             $a->submissionid = $activity->submissionid;
471             $a->submissiontitle = $activity->submissiontitle;
472             $a->reviewerid = $activity->reviewerid;
473             $a->timemodified = $activity->assessmentmodified;
474             $a->cmid = $activity->cmid;
475             if ($activity->reviewerid == $USER->id || has_capability('mod/workshop:viewreviewernames', $context)) {
476                 $a->reviewernamevisible = true;
477             } else {
478                 $a->reviewernamevisible = false;
479             }
481             // the following do-while wrapper allows to break from deeply nested if-statements
482             do {
483                 if ($a->reviewerid === $USER->id) {
484                     // own assessments always visible
485                     $assessments[$activity->assessmentid] = $a;
486                     break;
487                 }
489                 if (has_capability('mod/workshop:viewallassessments', $context)) {
490                     if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
491                         if (isguestuser()) {
492                             // shortcut - guest user does not belong into any group
493                             break;
494                         }
496                         // this might be slow - show only submissions by users who share group with me in this cm
497                         if (!$modinfo->get_groups($cm->groupingid)) {
498                             break;
499                         }
500                         $reviewersgroups = groups_get_all_groups($course->id, $a->reviewerid, $cm->groupingid);
501                         if (is_array($reviewersgroups)) {
502                             $reviewersgroups = array_keys($reviewersgroups);
503                             $intersect = array_intersect($reviewersgroups, $modinfo->get_groups($cm->groupingid));
504                             if (empty($intersect)) {
505                                 break;
506                             } else {
507                                 // can see all assessments and shares a group with the reviewer
508                                 $assessments[$activity->assessmentid] = $a;
509                                 break;
510                             }
511                         }
513                     } else {
514                         // can see all assessments from all groups
515                         $assessments[$activity->assessmentid] = $a;
516                     }
517                 }
518             } while (0);
519         }
520     }
521     $rs->close();
523     $shown = false;
525     if (!empty($submissions)) {
526         $shown = true;
527         echo $OUTPUT->heading(get_string('recentsubmissions', 'workshop'), 3);
528         foreach ($submissions as $id => $submission) {
529             $link = new moodle_url('/mod/workshop/submission.php', array('id'=>$id, 'cmid'=>$submission->cmid));
530             if ($submission->authornamevisible) {
531                 $author = $users[$submission->authorid];
532             } else {
533                 $author = null;
534             }
535             print_recent_activity_note($submission->timemodified, $author, $submission->title, $link->out(), false, $viewfullnames);
536         }
537     }
539     if (!empty($assessments)) {
540         $shown = true;
541         echo $OUTPUT->heading(get_string('recentassessments', 'workshop'), 3);
542         core_collator::asort_objects_by_property($assessments, 'timemodified');
543         foreach ($assessments as $id => $assessment) {
544             $link = new moodle_url('/mod/workshop/assessment.php', array('asid' => $id));
545             if ($assessment->reviewernamevisible) {
546                 $reviewer = $users[$assessment->reviewerid];
547             } else {
548                 $reviewer = null;
549             }
550             print_recent_activity_note($assessment->timemodified, $reviewer, $assessment->submissiontitle, $link->out(), false, $viewfullnames);
551         }
552     }
554     if ($shown) {
555         return true;
556     }
558     return false;
561 /**
562  * Returns all activity in course workshops since a given time
563  *
564  * @param array $activities sequentially indexed array of objects
565  * @param int $index
566  * @param int $timestart
567  * @param int $courseid
568  * @param int $cmid
569  * @param int $userid defaults to 0
570  * @param int $groupid defaults to 0
571  * @return void adds items into $activities and increases $index
572  */
573 function workshop_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid=0, $groupid=0) {
574     global $CFG, $COURSE, $USER, $DB;
576     if ($COURSE->id == $courseid) {
577         $course = $COURSE;
578     } else {
579         $course = $DB->get_record('course', array('id'=>$courseid));
580     }
582     $modinfo = get_fast_modinfo($course);
584     $cm = $modinfo->cms[$cmid];
586     $params = array();
587     if ($userid) {
588         $userselect = "AND (author.id = :authorid OR reviewer.id = :reviewerid)";
589         $params['authorid'] = $userid;
590         $params['reviewerid'] = $userid;
591     } else {
592         $userselect = "";
593     }
595     if ($groupid) {
596         $groupselect = "AND (authorgroupmembership.groupid = :authorgroupid OR reviewergroupmembership.groupid = :reviewergroupid)";
597         $groupjoin   = "LEFT JOIN {groups_members} authorgroupmembership ON authorgroupmembership.userid = author.id
598                         LEFT JOIN {groups_members} reviewergroupmembership ON reviewergroupmembership.userid = reviewer.id";
599         $params['authorgroupid'] = $groupid;
600         $params['reviewergroupid'] = $groupid;
601     } else {
602         $groupselect = "";
603         $groupjoin   = "";
604     }
606     $params['cminstance'] = $cm->instance;
607     $params['submissionmodified'] = $timestart;
608     $params['assessmentmodified'] = $timestart;
610     $authornamefields = get_all_user_name_fields(true, 'author', null, 'author');
611     $reviewerfields = get_all_user_name_fields(true, 'reviewer', null, 'reviewer');
613     $sql = "SELECT s.id AS submissionid, s.title AS submissiontitle, s.timemodified AS submissionmodified,
614                    author.id AS authorid, $authornamefields, author.picture AS authorpicture, author.imagealt AS authorimagealt,
615                    author.email AS authoremail, a.id AS assessmentid, a.timemodified AS assessmentmodified,
616                    reviewer.id AS reviewerid, $reviewerfields, reviewer.picture AS reviewerpicture,
617                    reviewer.imagealt AS reviewerimagealt, reviewer.email AS revieweremail
618               FROM {workshop_submissions} s
619         INNER JOIN {workshop} w ON s.workshopid = w.id
620         INNER JOIN {user} author ON s.authorid = author.id
621          LEFT JOIN {workshop_assessments} a ON a.submissionid = s.id
622          LEFT JOIN {user} reviewer ON a.reviewerid = reviewer.id
623         $groupjoin
624              WHERE w.id = :cminstance
625                    AND s.example = 0
626                    $userselect $groupselect
627                    AND (s.timemodified > :submissionmodified OR a.timemodified > :assessmentmodified)
628           ORDER BY s.timemodified ASC, a.timemodified ASC";
630     $rs = $DB->get_recordset_sql($sql, $params);
632     $groupmode       = groups_get_activity_groupmode($cm, $course);
633     $context         = context_module::instance($cm->id);
634     $grader          = has_capability('moodle/grade:viewall', $context);
635     $accessallgroups = has_capability('moodle/site:accessallgroups', $context);
636     $viewauthors     = has_capability('mod/workshop:viewauthornames', $context);
637     $viewreviewers   = has_capability('mod/workshop:viewreviewernames', $context);
639     $submissions = array(); // recent submissions indexed by submission id
640     $assessments = array(); // recent assessments indexed by assessment id
641     $users       = array();
643     foreach ($rs as $activity) {
645         // remember all user names we can use later
646         if (empty($users[$activity->authorid])) {
647             $u = new stdclass();
648             $additionalfields = explode(',', user_picture::fields());
649             $u = username_load_fields_from_object($u, $activity, 'author', $additionalfields);
650             $users[$activity->authorid] = $u;
651         }
652         if ($activity->reviewerid and empty($users[$activity->reviewerid])) {
653             $u = new stdclass();
654             $additionalfields = explode(',', user_picture::fields());
655             $u = username_load_fields_from_object($u, $activity, 'reviewer', $additionalfields);
656             $users[$activity->reviewerid] = $u;
657         }
659         if ($activity->submissionmodified > $timestart and empty($submissions[$activity->submissionid])) {
660             $s = new stdclass();
661             $s->id = $activity->submissionid;
662             $s->title = $activity->submissiontitle;
663             $s->authorid = $activity->authorid;
664             $s->timemodified = $activity->submissionmodified;
665             if ($activity->authorid == $USER->id || has_capability('mod/workshop:viewauthornames', $context)) {
666                 $s->authornamevisible = true;
667             } else {
668                 $s->authornamevisible = false;
669             }
671             // the following do-while wrapper allows to break from deeply nested if-statements
672             do {
673                 if ($s->authorid === $USER->id) {
674                     // own submissions always visible
675                     $submissions[$activity->submissionid] = $s;
676                     break;
677                 }
679                 if (has_capability('mod/workshop:viewallsubmissions', $context)) {
680                     if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
681                         if (isguestuser()) {
682                             // shortcut - guest user does not belong into any group
683                             break;
684                         }
686                         // this might be slow - show only submissions by users who share group with me in this cm
687                         if (!$modinfo->get_groups($cm->groupingid)) {
688                             break;
689                         }
690                         $authorsgroups = groups_get_all_groups($course->id, $s->authorid, $cm->groupingid);
691                         if (is_array($authorsgroups)) {
692                             $authorsgroups = array_keys($authorsgroups);
693                             $intersect = array_intersect($authorsgroups, $modinfo->get_groups($cm->groupingid));
694                             if (empty($intersect)) {
695                                 break;
696                             } else {
697                                 // can see all submissions and shares a group with the author
698                                 $submissions[$activity->submissionid] = $s;
699                                 break;
700                             }
701                         }
703                     } else {
704                         // can see all submissions from all groups
705                         $submissions[$activity->submissionid] = $s;
706                     }
707                 }
708             } while (0);
709         }
711         if ($activity->assessmentmodified > $timestart and empty($assessments[$activity->assessmentid])) {
712             $a = new stdclass();
713             $a->id = $activity->assessmentid;
714             $a->submissionid = $activity->submissionid;
715             $a->submissiontitle = $activity->submissiontitle;
716             $a->reviewerid = $activity->reviewerid;
717             $a->timemodified = $activity->assessmentmodified;
718             if ($activity->reviewerid == $USER->id || has_capability('mod/workshop:viewreviewernames', $context)) {
719                 $a->reviewernamevisible = true;
720             } else {
721                 $a->reviewernamevisible = false;
722             }
724             // the following do-while wrapper allows to break from deeply nested if-statements
725             do {
726                 if ($a->reviewerid === $USER->id) {
727                     // own assessments always visible
728                     $assessments[$activity->assessmentid] = $a;
729                     break;
730                 }
732                 if (has_capability('mod/workshop:viewallassessments', $context)) {
733                     if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
734                         if (isguestuser()) {
735                             // shortcut - guest user does not belong into any group
736                             break;
737                         }
739                         // this might be slow - show only submissions by users who share group with me in this cm
740                         if (!$modinfo->get_groups($cm->groupingid)) {
741                             break;
742                         }
743                         $reviewersgroups = groups_get_all_groups($course->id, $a->reviewerid, $cm->groupingid);
744                         if (is_array($reviewersgroups)) {
745                             $reviewersgroups = array_keys($reviewersgroups);
746                             $intersect = array_intersect($reviewersgroups, $modinfo->get_groups($cm->groupingid));
747                             if (empty($intersect)) {
748                                 break;
749                             } else {
750                                 // can see all assessments and shares a group with the reviewer
751                                 $assessments[$activity->assessmentid] = $a;
752                                 break;
753                             }
754                         }
756                     } else {
757                         // can see all assessments from all groups
758                         $assessments[$activity->assessmentid] = $a;
759                     }
760                 }
761             } while (0);
762         }
763     }
764     $rs->close();
766     $workshopname = format_string($cm->name, true);
768     if ($grader) {
769         require_once($CFG->libdir.'/gradelib.php');
770         $grades = grade_get_grades($courseid, 'mod', 'workshop', $cm->instance, array_keys($users));
771     }
773     foreach ($submissions as $submission) {
774         $tmpactivity                = new stdclass();
775         $tmpactivity->type          = 'workshop';
776         $tmpactivity->cmid          = $cm->id;
777         $tmpactivity->name          = $workshopname;
778         $tmpactivity->sectionnum    = $cm->sectionnum;
779         $tmpactivity->timestamp     = $submission->timemodified;
780         $tmpactivity->subtype       = 'submission';
781         $tmpactivity->content       = $submission;
782         if ($grader) {
783             $tmpactivity->grade     = $grades->items[0]->grades[$submission->authorid]->str_long_grade;
784         }
785         if ($submission->authornamevisible and !empty($users[$submission->authorid])) {
786             $tmpactivity->user      = $users[$submission->authorid];
787         }
788         $activities[$index++]       = $tmpactivity;
789     }
791     foreach ($assessments as $assessment) {
792         $tmpactivity                = new stdclass();
793         $tmpactivity->type          = 'workshop';
794         $tmpactivity->cmid          = $cm->id;
795         $tmpactivity->name          = $workshopname;
796         $tmpactivity->sectionnum    = $cm->sectionnum;
797         $tmpactivity->timestamp     = $assessment->timemodified;
798         $tmpactivity->subtype       = 'assessment';
799         $tmpactivity->content       = $assessment;
800         if ($grader) {
801             $tmpactivity->grade     = $grades->items[1]->grades[$assessment->reviewerid]->str_long_grade;
802         }
803         if ($assessment->reviewernamevisible and !empty($users[$assessment->reviewerid])) {
804             $tmpactivity->user      = $users[$assessment->reviewerid];
805         }
806         $activities[$index++]       = $tmpactivity;
807     }
810 /**
811  * Print single activity item prepared by {@see workshop_get_recent_mod_activity()}
812  */
813 function workshop_print_recent_mod_activity($activity, $courseid, $detail, $modnames, $viewfullnames) {
814     global $CFG, $OUTPUT;
816     if (!empty($activity->user)) {
817         echo html_writer::tag('div', $OUTPUT->user_picture($activity->user, array('courseid'=>$courseid)),
818                 array('style' => 'float: left; padding: 7px;'));
819     }
821     if ($activity->subtype == 'submission') {
822         echo html_writer::start_tag('div', array('class'=>'submission', 'style'=>'padding: 7px; float:left;'));
824         if ($detail) {
825             echo html_writer::start_tag('h4', array('class'=>'workshop'));
826             $url = new moodle_url('/mod/workshop/view.php', array('id'=>$activity->cmid));
827             $name = s($activity->name);
828             echo html_writer::empty_tag('img', array('src'=>$OUTPUT->pix_url('icon', $activity->type), 'class'=>'icon', 'alt'=>$name));
829             echo ' ' . $modnames[$activity->type];
830             echo html_writer::link($url, $name, array('class'=>'name', 'style'=>'margin-left: 5px'));
831             echo html_writer::end_tag('h4');
832         }
834         echo html_writer::start_tag('div', array('class'=>'title'));
835         $url = new moodle_url('/mod/workshop/submission.php', array('cmid'=>$activity->cmid, 'id'=>$activity->content->id));
836         $name = s($activity->content->title);
837         echo html_writer::tag('strong', html_writer::link($url, $name));
838         echo html_writer::end_tag('div');
840         if (!empty($activity->user)) {
841             echo html_writer::start_tag('div', array('class'=>'user'));
842             $url = new moodle_url('/user/view.php', array('id'=>$activity->user->id, 'course'=>$courseid));
843             $name = fullname($activity->user);
844             $link = html_writer::link($url, $name);
845             echo get_string('submissionby', 'workshop', $link);
846             echo ' - '.userdate($activity->timestamp);
847             echo html_writer::end_tag('div');
848         } else {
849             echo html_writer::start_tag('div', array('class'=>'anonymous'));
850             echo get_string('submission', 'workshop');
851             echo ' - '.userdate($activity->timestamp);
852             echo html_writer::end_tag('div');
853         }
855         echo html_writer::end_tag('div');
856     }
858     if ($activity->subtype == 'assessment') {
859         echo html_writer::start_tag('div', array('class'=>'assessment', 'style'=>'padding: 7px; float:left;'));
861         if ($detail) {
862             echo html_writer::start_tag('h4', array('class'=>'workshop'));
863             $url = new moodle_url('/mod/workshop/view.php', array('id'=>$activity->cmid));
864             $name = s($activity->name);
865             echo html_writer::empty_tag('img', array('src'=>$OUTPUT->pix_url('icon', $activity->type), 'class'=>'icon', 'alt'=>$name));
866             echo ' ' . $modnames[$activity->type];
867             echo html_writer::link($url, $name, array('class'=>'name', 'style'=>'margin-left: 5px'));
868             echo html_writer::end_tag('h4');
869         }
871         echo html_writer::start_tag('div', array('class'=>'title'));
872         $url = new moodle_url('/mod/workshop/assessment.php', array('asid'=>$activity->content->id));
873         $name = s($activity->content->submissiontitle);
874         echo html_writer::tag('em', html_writer::link($url, $name));
875         echo html_writer::end_tag('div');
877         if (!empty($activity->user)) {
878             echo html_writer::start_tag('div', array('class'=>'user'));
879             $url = new moodle_url('/user/view.php', array('id'=>$activity->user->id, 'course'=>$courseid));
880             $name = fullname($activity->user);
881             $link = html_writer::link($url, $name);
882             echo get_string('assessmentbyfullname', 'workshop', $link);
883             echo ' - '.userdate($activity->timestamp);
884             echo html_writer::end_tag('div');
885         } else {
886             echo html_writer::start_tag('div', array('class'=>'anonymous'));
887             echo get_string('assessment', 'workshop');
888             echo ' - '.userdate($activity->timestamp);
889             echo html_writer::end_tag('div');
890         }
892         echo html_writer::end_tag('div');
893     }
895     echo html_writer::empty_tag('br', array('style'=>'clear:both'));
898 /**
899  * Regular jobs to execute via cron
900  *
901  * @return boolean true on success, false otherwise
902  */
903 function workshop_cron() {
904     global $CFG, $DB;
906     $now = time();
908     mtrace(' processing workshop subplugins ...');
909     cron_execute_plugin_type('workshopallocation', 'workshop allocation methods');
911     // now when the scheduled allocator had a chance to do its job, check if there
912     // are some workshops to switch into the assessment phase
913     $workshops = $DB->get_records_select("workshop",
914         "phase = 20 AND phaseswitchassessment = 1 AND submissionend > 0 AND submissionend < ?", array($now));
916     if (!empty($workshops)) {
917         mtrace('Processing automatic assessment phase switch in '.count($workshops).' workshop(s) ... ', '');
918         require_once($CFG->dirroot.'/mod/workshop/locallib.php');
919         foreach ($workshops as $workshop) {
920             $cm = get_coursemodule_from_instance('workshop', $workshop->id, $workshop->course, false, MUST_EXIST);
921             $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
922             $workshop = new workshop($workshop, $cm, $course);
923             $workshop->switch_phase(workshop::PHASE_ASSESSMENT);
925             $params = array(
926                 'objectid' => $workshop->id,
927                 'context' => $workshop->context,
928                 'courseid' => $workshop->course->id,
929                 'other' => array(
930                     'workshopphase' => $workshop->phase
931                 )
932             );
933             $event = \mod_workshop\event\phase_switched::create($params);
934             $event->trigger();
936             // disable the automatic switching now so that it is not executed again by accident
937             // if the teacher changes the phase back to the submission one
938             $DB->set_field('workshop', 'phaseswitchassessment', 0, array('id' => $workshop->id));
940             // todo inform the teachers
941         }
942         mtrace('done');
943     }
945     return true;
948 /**
949  * Is a given scale used by the instance of workshop?
950  *
951  * The function asks all installed grading strategy subplugins. The workshop
952  * core itself does not use scales. Both grade for submission and grade for
953  * assessments do not use scales.
954  *
955  * @param int $workshopid id of workshop instance
956  * @param int $scaleid id of the scale to check
957  * @return bool
958  */
959 function workshop_scale_used($workshopid, $scaleid) {
960     global $CFG; // other files included from here
962     $strategies = core_component::get_plugin_list('workshopform');
963     foreach ($strategies as $strategy => $strategypath) {
964         $strategylib = $strategypath . '/lib.php';
965         if (is_readable($strategylib)) {
966             require_once($strategylib);
967         } else {
968             throw new coding_exception('the grading forms subplugin must contain library ' . $strategylib);
969         }
970         $classname = 'workshop_' . $strategy . '_strategy';
971         if (method_exists($classname, 'scale_used')) {
972             if (call_user_func_array(array($classname, 'scale_used'), array($scaleid, $workshopid))) {
973                 // no need to include any other files - scale is used
974                 return true;
975             }
976         }
977     }
979     return false;
982 /**
983  * Is a given scale used by any instance of workshop?
984  *
985  * The function asks all installed grading strategy subplugins. The workshop
986  * core itself does not use scales. Both grade for submission and grade for
987  * assessments do not use scales.
988  *
989  * @param int $scaleid id of the scale to check
990  * @return bool
991  */
992 function workshop_scale_used_anywhere($scaleid) {
993     global $CFG; // other files included from here
995     $strategies = core_component::get_plugin_list('workshopform');
996     foreach ($strategies as $strategy => $strategypath) {
997         $strategylib = $strategypath . '/lib.php';
998         if (is_readable($strategylib)) {
999             require_once($strategylib);
1000         } else {
1001             throw new coding_exception('the grading forms subplugin must contain library ' . $strategylib);
1002         }
1003         $classname = 'workshop_' . $strategy . '_strategy';
1004         if (method_exists($classname, 'scale_used')) {
1005             if (call_user_func(array($classname, 'scale_used'), $scaleid)) {
1006                 // no need to include any other files - scale is used
1007                 return true;
1008             }
1009         }
1010     }
1012     return false;
1015 /**
1016  * Returns all other caps used in the module
1017  *
1018  * @return array
1019  */
1020 function workshop_get_extra_capabilities() {
1021     return array('moodle/site:accessallgroups');
1024 ////////////////////////////////////////////////////////////////////////////////
1025 // Gradebook API                                                              //
1026 ////////////////////////////////////////////////////////////////////////////////
1028 /**
1029  * Creates or updates grade items for the give workshop instance
1030  *
1031  * Needed by grade_update_mod_grades() in lib/gradelib.php. Also used by
1032  * {@link workshop_update_grades()}.
1033  *
1034  * @param stdClass $workshop instance object with extra cmidnumber property
1035  * @param stdClass $submissiongrades data for the first grade item
1036  * @param stdClass $assessmentgrades data for the second grade item
1037  * @return void
1038  */
1039 function workshop_grade_item_update(stdclass $workshop, $submissiongrades=null, $assessmentgrades=null) {
1040     global $CFG;
1041     require_once($CFG->libdir.'/gradelib.php');
1043     $a = new stdclass();
1044     $a->workshopname = clean_param($workshop->name, PARAM_NOTAGS);
1046     $item = array();
1047     $item['itemname'] = get_string('gradeitemsubmission', 'workshop', $a);
1048     $item['gradetype'] = GRADE_TYPE_VALUE;
1049     $item['grademax']  = $workshop->grade;
1050     $item['grademin']  = 0;
1051     grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 0, $submissiongrades , $item);
1053     $item = array();
1054     $item['itemname'] = get_string('gradeitemassessment', 'workshop', $a);
1055     $item['gradetype'] = GRADE_TYPE_VALUE;
1056     $item['grademax']  = $workshop->gradinggrade;
1057     $item['grademin']  = 0;
1058     grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 1, $assessmentgrades, $item);
1061 /**
1062  * Update workshop grades in the gradebook
1063  *
1064  * Needed by grade_update_mod_grades() in lib/gradelib.php
1065  *
1066  * @category grade
1067  * @param stdClass $workshop instance object with extra cmidnumber and modname property
1068  * @param int $userid        update grade of specific user only, 0 means all participants
1069  * @return void
1070  */
1071 function workshop_update_grades(stdclass $workshop, $userid=0) {
1072     global $CFG, $DB;
1073     require_once($CFG->libdir.'/gradelib.php');
1075     $whereuser = $userid ? ' AND authorid = :userid' : '';
1076     $params = array('workshopid' => $workshop->id, 'userid' => $userid);
1077     $sql = 'SELECT authorid, grade, gradeover, gradeoverby, feedbackauthor, feedbackauthorformat, timemodified, timegraded
1078               FROM {workshop_submissions}
1079              WHERE workshopid = :workshopid AND example=0' . $whereuser;
1080     $records = $DB->get_records_sql($sql, $params);
1081     $submissiongrades = array();
1082     foreach ($records as $record) {
1083         $grade = new stdclass();
1084         $grade->userid = $record->authorid;
1085         if (!is_null($record->gradeover)) {
1086             $grade->rawgrade = grade_floatval($workshop->grade * $record->gradeover / 100);
1087             $grade->usermodified = $record->gradeoverby;
1088         } else {
1089             $grade->rawgrade = grade_floatval($workshop->grade * $record->grade / 100);
1090         }
1091         $grade->feedback = $record->feedbackauthor;
1092         $grade->feedbackformat = $record->feedbackauthorformat;
1093         $grade->datesubmitted = $record->timemodified;
1094         $grade->dategraded = $record->timegraded;
1095         $submissiongrades[$record->authorid] = $grade;
1096     }
1098     $whereuser = $userid ? ' AND userid = :userid' : '';
1099     $params = array('workshopid' => $workshop->id, 'userid' => $userid);
1100     $sql = 'SELECT userid, gradinggrade, timegraded
1101               FROM {workshop_aggregations}
1102              WHERE workshopid = :workshopid' . $whereuser;
1103     $records = $DB->get_records_sql($sql, $params);
1104     $assessmentgrades = array();
1105     foreach ($records as $record) {
1106         $grade = new stdclass();
1107         $grade->userid = $record->userid;
1108         $grade->rawgrade = grade_floatval($workshop->gradinggrade * $record->gradinggrade / 100);
1109         $grade->dategraded = $record->timegraded;
1110         $assessmentgrades[$record->userid] = $grade;
1111     }
1113     workshop_grade_item_update($workshop, $submissiongrades, $assessmentgrades);
1116 /**
1117  * Update the grade items categories if they are changed via mod_form.php
1118  *
1119  * We must do it manually here in the workshop module because modedit supports only
1120  * single grade item while we use two.
1121  *
1122  * @param stdClass $workshop An object from the form in mod_form.php
1123  */
1124 function workshop_grade_item_category_update($workshop) {
1126     $gradeitems = grade_item::fetch_all(array(
1127         'itemtype'      => 'mod',
1128         'itemmodule'    => 'workshop',
1129         'iteminstance'  => $workshop->id,
1130         'courseid'      => $workshop->course));
1132     if (!empty($gradeitems)) {
1133         foreach ($gradeitems as $gradeitem) {
1134             if ($gradeitem->itemnumber == 0) {
1135                 if ($gradeitem->categoryid != $workshop->gradecategory) {
1136                     $gradeitem->set_parent($workshop->gradecategory);
1137                 }
1138             } else if ($gradeitem->itemnumber == 1) {
1139                 if ($gradeitem->categoryid != $workshop->gradinggradecategory) {
1140                     $gradeitem->set_parent($workshop->gradinggradecategory);
1141                 }
1142             }
1143             if (!empty($workshop->add)) {
1144                 $gradecategory = $gradeitem->get_parent_category();
1145                 if (grade_category::aggregation_uses_aggregationcoef($gradecategory->aggregation)) {
1146                     if ($gradecategory->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN) {
1147                         $gradeitem->aggregationcoef = 1;
1148                     } else {
1149                         $gradeitem->aggregationcoef = 0;
1150                     }
1151                     $gradeitem->update();
1152                 }
1153             }
1154         }
1155     }
1158 ////////////////////////////////////////////////////////////////////////////////
1159 // File API                                                                   //
1160 ////////////////////////////////////////////////////////////////////////////////
1162 /**
1163  * Returns the lists of all browsable file areas within the given module context
1164  *
1165  * The file area workshop_intro for the activity introduction field is added automatically
1166  * by {@link file_browser::get_file_info_context_module()}
1167  *
1168  * @package  mod_workshop
1169  * @category files
1170  *
1171  * @param stdClass $course
1172  * @param stdClass $cm
1173  * @param stdClass $context
1174  * @return array of [(string)filearea] => (string)description
1175  */
1176 function workshop_get_file_areas($course, $cm, $context) {
1177     $areas = array();
1178     $areas['instructauthors']          = get_string('areainstructauthors', 'workshop');
1179     $areas['instructreviewers']        = get_string('areainstructreviewers', 'workshop');
1180     $areas['submission_content']       = get_string('areasubmissioncontent', 'workshop');
1181     $areas['submission_attachment']    = get_string('areasubmissionattachment', 'workshop');
1182     $areas['conclusion']               = get_string('areaconclusion', 'workshop');
1183     $areas['overallfeedback_content']  = get_string('areaoverallfeedbackcontent', 'workshop');
1184     $areas['overallfeedback_attachment'] = get_string('areaoverallfeedbackattachment', 'workshop');
1186     return $areas;
1189 /**
1190  * Serves the files from the workshop file areas
1191  *
1192  * Apart from module intro (handled by pluginfile.php automatically), workshop files may be
1193  * media inserted into submission content (like images) and submission attachments. For these two,
1194  * the fileareas submission_content and submission_attachment are used.
1195  * Besides that, areas instructauthors, instructreviewers and conclusion contain the media
1196  * embedded using the mod_form.php.
1197  *
1198  * @package  mod_workshop
1199  * @category files
1200  *
1201  * @param stdClass $course the course object
1202  * @param stdClass $cm the course module object
1203  * @param stdClass $context the workshop's context
1204  * @param string $filearea the name of the file area
1205  * @param array $args extra arguments (itemid, path)
1206  * @param bool $forcedownload whether or not force download
1207  * @param array $options additional options affecting the file serving
1208  * @return bool false if the file not found, just send the file otherwise and do not return anything
1209  */
1210 function workshop_pluginfile($course, $cm, $context, $filearea, array $args, $forcedownload, array $options=array()) {
1211     global $DB, $CFG, $USER;
1213     if ($context->contextlevel != CONTEXT_MODULE) {
1214         return false;
1215     }
1217     require_login($course, true, $cm);
1219     if ($filearea === 'instructauthors') {
1220         array_shift($args); // itemid is ignored here
1221         $relativepath = implode('/', $args);
1222         $fullpath = "/$context->id/mod_workshop/$filearea/0/$relativepath";
1224         $fs = get_file_storage();
1225         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1226             send_file_not_found();
1227         }
1229         // finally send the file
1230         send_stored_file($file, null, 0, $forcedownload, $options);
1232     } else if ($filearea === 'instructreviewers') {
1233         array_shift($args); // itemid is ignored here
1234         $relativepath = implode('/', $args);
1235         $fullpath = "/$context->id/mod_workshop/$filearea/0/$relativepath";
1237         $fs = get_file_storage();
1238         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1239             send_file_not_found();
1240         }
1242         // finally send the file
1243         send_stored_file($file, null, 0, $forcedownload, $options);
1245     } else if ($filearea === 'conclusion') {
1246         array_shift($args); // itemid is ignored here
1247         $relativepath = implode('/', $args);
1248         $fullpath = "/$context->id/mod_workshop/$filearea/0/$relativepath";
1250         $fs = get_file_storage();
1251         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1252             send_file_not_found();
1253         }
1255         // finally send the file
1256         send_stored_file($file, null, 0, $forcedownload, $options);
1258     } else if ($filearea === 'submission_content' or $filearea === 'submission_attachment') {
1259         $itemid = (int)array_shift($args);
1260         if (!$workshop = $DB->get_record('workshop', array('id' => $cm->instance))) {
1261             return false;
1262         }
1263         if (!$submission = $DB->get_record('workshop_submissions', array('id' => $itemid, 'workshopid' => $workshop->id))) {
1264             return false;
1265         }
1267         // make sure the user is allowed to see the file
1268         if (empty($submission->example)) {
1269             if ($USER->id != $submission->authorid) {
1270                 if ($submission->published == 1 and $workshop->phase == 50
1271                         and has_capability('mod/workshop:viewpublishedsubmissions', $context)) {
1272                     // Published submission, we can go (workshop does not take the group mode
1273                     // into account in this case yet).
1274                 } else if (!$DB->record_exists('workshop_assessments', array('submissionid' => $submission->id, 'reviewerid' => $USER->id))) {
1275                     if (!has_capability('mod/workshop:viewallsubmissions', $context)) {
1276                         send_file_not_found();
1277                     } else {
1278                         $gmode = groups_get_activity_groupmode($cm, $course);
1279                         if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1280                             // check there is at least one common group with both the $USER
1281                             // and the submission author
1282                             $sql = "SELECT 'x'
1283                                       FROM {workshop_submissions} s
1284                                       JOIN {user} a ON (a.id = s.authorid)
1285                                       JOIN {groups_members} agm ON (a.id = agm.userid)
1286                                       JOIN {user} u ON (u.id = ?)
1287                                       JOIN {groups_members} ugm ON (u.id = ugm.userid)
1288                                      WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1289                             $params = array($USER->id, $workshop->id, $submission->id);
1290                             if (!$DB->record_exists_sql($sql, $params)) {
1291                                 send_file_not_found();
1292                             }
1293                         }
1294                     }
1295                 }
1296             }
1297         }
1299         $fs = get_file_storage();
1300         $relativepath = implode('/', $args);
1301         $fullpath = "/$context->id/mod_workshop/$filearea/$itemid/$relativepath";
1302         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1303             return false;
1304         }
1305         // finally send the file
1306         // these files are uploaded by students - forcing download for security reasons
1307         send_stored_file($file, 0, 0, true, $options);
1309     } else if ($filearea === 'overallfeedback_content' or $filearea === 'overallfeedback_attachment') {
1310         $itemid = (int)array_shift($args);
1311         if (!$workshop = $DB->get_record('workshop', array('id' => $cm->instance))) {
1312             return false;
1313         }
1314         if (!$assessment = $DB->get_record('workshop_assessments', array('id' => $itemid))) {
1315             return false;
1316         }
1317         if (!$submission = $DB->get_record('workshop_submissions', array('id' => $assessment->submissionid, 'workshopid' => $workshop->id))) {
1318             return false;
1319         }
1321         if ($USER->id == $assessment->reviewerid) {
1322             // Reviewers can always see their own files.
1323         } else if ($USER->id == $submission->authorid and $workshop->phase == 50) {
1324             // Authors can see the feedback once the workshop is closed.
1325         } else if (!empty($submission->example) and $assessment->weight == 1) {
1326             // Reference assessments of example submissions can be displayed.
1327         } else if (!has_capability('mod/workshop:viewallassessments', $context)) {
1328             send_file_not_found();
1329         } else {
1330             $gmode = groups_get_activity_groupmode($cm, $course);
1331             if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1332                 // Check there is at least one common group with both the $USER
1333                 // and the submission author.
1334                 $sql = "SELECT 'x'
1335                           FROM {workshop_submissions} s
1336                           JOIN {user} a ON (a.id = s.authorid)
1337                           JOIN {groups_members} agm ON (a.id = agm.userid)
1338                           JOIN {user} u ON (u.id = ?)
1339                           JOIN {groups_members} ugm ON (u.id = ugm.userid)
1340                          WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1341                 $params = array($USER->id, $workshop->id, $submission->id);
1342                 if (!$DB->record_exists_sql($sql, $params)) {
1343                     send_file_not_found();
1344                 }
1345             }
1346         }
1348         $fs = get_file_storage();
1349         $relativepath = implode('/', $args);
1350         $fullpath = "/$context->id/mod_workshop/$filearea/$itemid/$relativepath";
1351         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1352             return false;
1353         }
1354         // finally send the file
1355         // these files are uploaded by students - forcing download for security reasons
1356         send_stored_file($file, 0, 0, true, $options);
1357     }
1359     return false;
1362 /**
1363  * File browsing support for workshop file areas
1364  *
1365  * @package  mod_workshop
1366  * @category files
1367  *
1368  * @param file_browser $browser
1369  * @param array $areas
1370  * @param stdClass $course
1371  * @param stdClass $cm
1372  * @param stdClass $context
1373  * @param string $filearea
1374  * @param int $itemid
1375  * @param string $filepath
1376  * @param string $filename
1377  * @return file_info instance or null if not found
1378  */
1379 function workshop_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
1380     global $CFG, $DB, $USER;
1382     /** @var array internal cache for author names */
1383     static $submissionauthors = array();
1385     $fs = get_file_storage();
1387     if ($filearea === 'submission_content' or $filearea === 'submission_attachment') {
1389         if (!has_capability('mod/workshop:viewallsubmissions', $context)) {
1390             return null;
1391         }
1393         if (is_null($itemid)) {
1394             // no itemid (submissionid) passed, display the list of all submissions
1395             require_once($CFG->dirroot . '/mod/workshop/fileinfolib.php');
1396             return new workshop_file_info_submissions_container($browser, $course, $cm, $context, $areas, $filearea);
1397         }
1399         // make sure the user can see the particular submission in separate groups mode
1400         $gmode = groups_get_activity_groupmode($cm, $course);
1402         if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1403             // check there is at least one common group with both the $USER
1404             // and the submission author (this is not expected to be a frequent
1405             // usecase so we can live with pretty ineffective one query per submission here...)
1406             $sql = "SELECT 'x'
1407                       FROM {workshop_submissions} s
1408                       JOIN {user} a ON (a.id = s.authorid)
1409                       JOIN {groups_members} agm ON (a.id = agm.userid)
1410                       JOIN {user} u ON (u.id = ?)
1411                       JOIN {groups_members} ugm ON (u.id = ugm.userid)
1412                      WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1413             $params = array($USER->id, $cm->instance, $itemid);
1414             if (!$DB->record_exists_sql($sql, $params)) {
1415                 return null;
1416             }
1417         }
1419         // we are inside some particular submission container
1421         $filepath = is_null($filepath) ? '/' : $filepath;
1422         $filename = is_null($filename) ? '.' : $filename;
1424         if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, $itemid, $filepath, $filename)) {
1425             if ($filepath === '/' and $filename === '.') {
1426                 $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, $itemid);
1427             } else {
1428                 // not found
1429                 return null;
1430             }
1431         }
1433         // Checks to see if the user can manage files or is the owner.
1434         // TODO MDL-33805 - Do not use userid here and move the capability check above.
1435         if (!has_capability('moodle/course:managefiles', $context) && $storedfile->get_userid() != $USER->id) {
1436             return null;
1437         }
1439         // let us display the author's name instead of itemid (submission id)
1441         if (isset($submissionauthors[$itemid])) {
1442             $topvisiblename = $submissionauthors[$itemid];
1444         } else {
1446             $sql = "SELECT s.id, u.lastname, u.firstname
1447                       FROM {workshop_submissions} s
1448                       JOIN {user} u ON (s.authorid = u.id)
1449                      WHERE s.example = 0 AND s.workshopid = ?";
1450             $params = array($cm->instance);
1451             $rs = $DB->get_recordset_sql($sql, $params);
1453             foreach ($rs as $submissionauthor) {
1454                 $title = s(fullname($submissionauthor)); // this is generally not unique...
1455                 $submissionauthors[$submissionauthor->id] = $title;
1456             }
1457             $rs->close();
1459             if (!isset($submissionauthors[$itemid])) {
1460                 // should not happen
1461                 return null;
1462             } else {
1463                 $topvisiblename = $submissionauthors[$itemid];
1464             }
1465         }
1467         $urlbase = $CFG->wwwroot . '/pluginfile.php';
1468         // do not allow manual modification of any files!
1469         return new file_info_stored($browser, $context, $storedfile, $urlbase, $topvisiblename, true, true, false, false);
1470     }
1472     if ($filearea === 'overallfeedback_content' or $filearea === 'overallfeedback_attachment') {
1474         if (!has_capability('mod/workshop:viewallassessments', $context)) {
1475             return null;
1476         }
1478         if (is_null($itemid)) {
1479             // No itemid (assessmentid) passed, display the list of all assessments.
1480             require_once($CFG->dirroot . '/mod/workshop/fileinfolib.php');
1481             return new workshop_file_info_overallfeedback_container($browser, $course, $cm, $context, $areas, $filearea);
1482         }
1484         // Make sure the user can see the particular assessment in separate groups mode.
1485         $gmode = groups_get_activity_groupmode($cm, $course);
1486         if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1487             // Check there is at least one common group with both the $USER
1488             // and the submission author.
1489             $sql = "SELECT 'x'
1490                       FROM {workshop_submissions} s
1491                       JOIN {user} a ON (a.id = s.authorid)
1492                       JOIN {groups_members} agm ON (a.id = agm.userid)
1493                       JOIN {user} u ON (u.id = ?)
1494                       JOIN {groups_members} ugm ON (u.id = ugm.userid)
1495                      WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1496             $params = array($USER->id, $cm->instance, $itemid);
1497             if (!$DB->record_exists_sql($sql, $params)) {
1498                 return null;
1499             }
1500         }
1502         // We are inside a particular assessment container.
1503         $filepath = is_null($filepath) ? '/' : $filepath;
1504         $filename = is_null($filename) ? '.' : $filename;
1506         if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, $itemid, $filepath, $filename)) {
1507             if ($filepath === '/' and $filename === '.') {
1508                 $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, $itemid);
1509             } else {
1510                 // Not found
1511                 return null;
1512             }
1513         }
1515         // Check to see if the user can manage files or is the owner.
1516         if (!has_capability('moodle/course:managefiles', $context) and $storedfile->get_userid() != $USER->id) {
1517             return null;
1518         }
1520         $urlbase = $CFG->wwwroot . '/pluginfile.php';
1522         // Do not allow manual modification of any files.
1523         return new file_info_stored($browser, $context, $storedfile, $urlbase, $itemid, true, true, false, false);
1524     }
1526     if ($filearea == 'instructauthors' or $filearea == 'instructreviewers' or $filearea == 'conclusion') {
1527         // always only itemid 0
1529         $filepath = is_null($filepath) ? '/' : $filepath;
1530         $filename = is_null($filename) ? '.' : $filename;
1532         $urlbase = $CFG->wwwroot.'/pluginfile.php';
1533         if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, 0, $filepath, $filename)) {
1534             if ($filepath === '/' and $filename === '.') {
1535                 $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, 0);
1536             } else {
1537                 // not found
1538                 return null;
1539             }
1540         }
1541         return new file_info_stored($browser, $context, $storedfile, $urlbase, $areas[$filearea], false, true, true, false);
1542     }
1545 ////////////////////////////////////////////////////////////////////////////////
1546 // Navigation API                                                             //
1547 ////////////////////////////////////////////////////////////////////////////////
1549 /**
1550  * Extends the global navigation tree by adding workshop nodes if there is a relevant content
1551  *
1552  * This can be called by an AJAX request so do not rely on $PAGE as it might not be set up properly.
1553  *
1554  * @param navigation_node $navref An object representing the navigation tree node of the workshop module instance
1555  * @param stdClass $course
1556  * @param stdClass $module
1557  * @param cm_info $cm
1558  */
1559 function workshop_extend_navigation(navigation_node $navref, stdclass $course, stdclass $module, cm_info $cm) {
1560     global $CFG;
1562     if (has_capability('mod/workshop:submit', context_module::instance($cm->id))) {
1563         $url = new moodle_url('/mod/workshop/submission.php', array('cmid' => $cm->id));
1564         $mysubmission = $navref->add(get_string('mysubmission', 'workshop'), $url);
1565         $mysubmission->mainnavonly = true;
1566     }
1569 /**
1570  * Extends the settings navigation with the Workshop settings
1572  * This function is called when the context for the page is a workshop module. This is not called by AJAX
1573  * so it is safe to rely on the $PAGE.
1574  *
1575  * @param settings_navigation $settingsnav {@link settings_navigation}
1576  * @param navigation_node $workshopnode {@link navigation_node}
1577  */
1578 function workshop_extend_settings_navigation(settings_navigation $settingsnav, navigation_node $workshopnode=null) {
1579     global $PAGE;
1581     //$workshopobject = $DB->get_record("workshop", array("id" => $PAGE->cm->instance));
1583     if (has_capability('mod/workshop:editdimensions', $PAGE->cm->context)) {
1584         $url = new moodle_url('/mod/workshop/editform.php', array('cmid' => $PAGE->cm->id));
1585         $workshopnode->add(get_string('editassessmentform', 'workshop'), $url, settings_navigation::TYPE_SETTING);
1586     }
1587     if (has_capability('mod/workshop:allocate', $PAGE->cm->context)) {
1588         $url = new moodle_url('/mod/workshop/allocation.php', array('cmid' => $PAGE->cm->id));
1589         $workshopnode->add(get_string('allocate', 'workshop'), $url, settings_navigation::TYPE_SETTING);
1590     }
1593 /**
1594  * Return a list of page types
1595  * @param string $pagetype current page type
1596  * @param stdClass $parentcontext Block's parent context
1597  * @param stdClass $currentcontext Current context of block
1598  */
1599 function workshop_page_type_list($pagetype, $parentcontext, $currentcontext) {
1600     $module_pagetype = array('mod-workshop-*'=>get_string('page-mod-workshop-x', 'workshop'));
1601     return $module_pagetype;
1604 ////////////////////////////////////////////////////////////////////////////////
1605 // Calendar API                                                               //
1606 ////////////////////////////////////////////////////////////////////////////////
1608 /**
1609  * Updates the calendar events associated to the given workshop
1610  *
1611  * @param stdClass $workshop the workshop instance record
1612  * @param int $cmid course module id
1613  */
1614 function workshop_calendar_update(stdClass $workshop, $cmid) {
1615     global $DB;
1617     // get the currently registered events so that we can re-use their ids
1618     $currentevents = $DB->get_records('event', array('modulename' => 'workshop', 'instance' => $workshop->id));
1620     // the common properties for all events
1621     $base = new stdClass();
1622     $base->description  = format_module_intro('workshop', $workshop, $cmid, false);
1623     $base->courseid     = $workshop->course;
1624     $base->groupid      = 0;
1625     $base->userid       = 0;
1626     $base->modulename   = 'workshop';
1627     $base->eventtype    = 'pluginname';
1628     $base->instance     = $workshop->id;
1629     $base->visible      = instance_is_visible('workshop', $workshop);
1630     $base->timeduration = 0;
1632     if ($workshop->submissionstart) {
1633         $event = clone($base);
1634         $event->name = get_string('submissionstartevent', 'mod_workshop', $workshop->name);
1635         $event->timestart = $workshop->submissionstart;
1636         if ($reusedevent = array_shift($currentevents)) {
1637             $event->id = $reusedevent->id;
1638         } else {
1639             // should not be set but just in case
1640             unset($event->id);
1641         }
1642         // update() will reuse a db record if the id field is set
1643         $eventobj = new calendar_event($event);
1644         $eventobj->update($event, false);
1645     }
1647     if ($workshop->submissionend) {
1648         $event = clone($base);
1649         $event->name = get_string('submissionendevent', 'mod_workshop', $workshop->name);
1650         $event->timestart = $workshop->submissionend;
1651         if ($reusedevent = array_shift($currentevents)) {
1652             $event->id = $reusedevent->id;
1653         } else {
1654             // should not be set but just in case
1655             unset($event->id);
1656         }
1657         // update() will reuse a db record if the id field is set
1658         $eventobj = new calendar_event($event);
1659         $eventobj->update($event, false);
1660     }
1662     if ($workshop->assessmentstart) {
1663         $event = clone($base);
1664         $event->name = get_string('assessmentstartevent', 'mod_workshop', $workshop->name);
1665         $event->timestart = $workshop->assessmentstart;
1666         if ($reusedevent = array_shift($currentevents)) {
1667             $event->id = $reusedevent->id;
1668         } else {
1669             // should not be set but just in case
1670             unset($event->id);
1671         }
1672         // update() will reuse a db record if the id field is set
1673         $eventobj = new calendar_event($event);
1674         $eventobj->update($event, false);
1675     }
1677     if ($workshop->assessmentend) {
1678         $event = clone($base);
1679         $event->name = get_string('assessmentendevent', 'mod_workshop', $workshop->name);
1680         $event->timestart = $workshop->assessmentend;
1681         if ($reusedevent = array_shift($currentevents)) {
1682             $event->id = $reusedevent->id;
1683         } else {
1684             // should not be set but just in case
1685             unset($event->id);
1686         }
1687         // update() will reuse a db record if the id field is set
1688         $eventobj = new calendar_event($event);
1689         $eventobj->update($event, false);
1690     }
1692     // delete any leftover events
1693     foreach ($currentevents as $oldevent) {
1694         $oldevent = calendar_event::load($oldevent);
1695         $oldevent->delete();
1696     }