MDL-36805 Correct docs for workshop_grade_item_update in mod_workshop
[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
25  * @subpackage workshop
26  * @copyright  2009 David Mudrak <david.mudrak@gmail.com>
27  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
28  */
30 defined('MOODLE_INTERNAL') || die();
32 require_once($CFG->dirroot . '/calendar/lib.php');
34 ////////////////////////////////////////////////////////////////////////////////
35 // Moodle core API                                                            //
36 ////////////////////////////////////////////////////////////////////////////////
38 /**
39  * Returns the information if the module supports a feature
40  *
41  * @see plugin_supports() in lib/moodlelib.php
42  * @param string $feature FEATURE_xx constant for requested feature
43  * @return mixed true if the feature is supported, null if unknown
44  */
45 function workshop_supports($feature) {
46     switch($feature) {
47         case FEATURE_GRADE_HAS_GRADE:   return true;
48         case FEATURE_GROUPS:            return true;
49         case FEATURE_GROUPINGS:         return true;
50         case FEATURE_GROUPMEMBERSONLY:  return true;
51         case FEATURE_MOD_INTRO:         return true;
52         case FEATURE_BACKUP_MOODLE2:    return true;
53         case FEATURE_COMPLETION_TRACKS_VIEWS:
54             return true;
55         case FEATURE_SHOW_DESCRIPTION:  return true;
56         default:                        return null;
57     }
58 }
60 /**
61  * Saves a new instance of the workshop into the database
62  *
63  * Given an object containing all the necessary data,
64  * (defined by the form in mod_form.php) this function
65  * will save a new instance and return the id number
66  * of the new instance.
67  *
68  * @param stdClass $workshop An object from the form in mod_form.php
69  * @return int The id of the newly inserted workshop record
70  */
71 function workshop_add_instance(stdclass $workshop) {
72     global $CFG, $DB;
73     require_once(dirname(__FILE__) . '/locallib.php');
75     $workshop->phase                 = workshop::PHASE_SETUP;
76     $workshop->timecreated           = time();
77     $workshop->timemodified          = $workshop->timecreated;
78     $workshop->useexamples           = (int)!empty($workshop->useexamples);
79     $workshop->usepeerassessment     = (int)!empty($workshop->usepeerassessment);
80     $workshop->useselfassessment     = (int)!empty($workshop->useselfassessment);
81     $workshop->latesubmissions       = (int)!empty($workshop->latesubmissions);
82     $workshop->phaseswitchassessment = (int)!empty($workshop->phaseswitchassessment);
83     $workshop->evaluation            = 'best';
85     // insert the new record so we get the id
86     $workshop->id = $DB->insert_record('workshop', $workshop);
88     // we need to use context now, so we need to make sure all needed info is already in db
89     $cmid = $workshop->coursemodule;
90     $DB->set_field('course_modules', 'instance', $workshop->id, array('id' => $cmid));
91     $context = get_context_instance(CONTEXT_MODULE, $cmid);
93     // process the custom wysiwyg editors
94     if ($draftitemid = $workshop->instructauthorseditor['itemid']) {
95         $workshop->instructauthors = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructauthors',
96                 0, workshop::instruction_editors_options($context), $workshop->instructauthorseditor['text']);
97         $workshop->instructauthorsformat = $workshop->instructauthorseditor['format'];
98     }
100     if ($draftitemid = $workshop->instructreviewerseditor['itemid']) {
101         $workshop->instructreviewers = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructreviewers',
102                 0, workshop::instruction_editors_options($context), $workshop->instructreviewerseditor['text']);
103         $workshop->instructreviewersformat = $workshop->instructreviewerseditor['format'];
104     }
106     // re-save the record with the replaced URLs in editor fields
107     $DB->update_record('workshop', $workshop);
109     // create gradebook items
110     workshop_grade_item_update($workshop);
111     workshop_grade_item_category_update($workshop);
113     // create calendar events
114     workshop_calendar_update($workshop, $workshop->coursemodule);
116     return $workshop->id;
119 /**
120  * Given an object containing all the necessary data,
121  * (defined by the form in mod_form.php) this function
122  * will update an existing instance with new data.
123  *
124  * @param stdClass $workshop An object from the form in mod_form.php
125  * @return bool success
126  */
127 function workshop_update_instance(stdclass $workshop) {
128     global $CFG, $DB;
129     require_once(dirname(__FILE__) . '/locallib.php');
131     $workshop->timemodified          = time();
132     $workshop->id                    = $workshop->instance;
133     $workshop->useexamples           = (int)!empty($workshop->useexamples);
134     $workshop->usepeerassessment     = (int)!empty($workshop->usepeerassessment);
135     $workshop->useselfassessment     = (int)!empty($workshop->useselfassessment);
136     $workshop->latesubmissions       = (int)!empty($workshop->latesubmissions);
137     $workshop->phaseswitchassessment = (int)!empty($workshop->phaseswitchassessment);
138     $workshop->evaluation            = 'best';
140     // todo - if the grading strategy is being changed, we must replace all aggregated peer grades with nulls
141     // todo - if maximum grades are being changed, we should probably recalculate or invalidate them
143     $DB->update_record('workshop', $workshop);
144     $context = get_context_instance(CONTEXT_MODULE, $workshop->coursemodule);
146     // process the custom wysiwyg editors
147     if ($draftitemid = $workshop->instructauthorseditor['itemid']) {
148         $workshop->instructauthors = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructauthors',
149                 0, workshop::instruction_editors_options($context), $workshop->instructauthorseditor['text']);
150         $workshop->instructauthorsformat = $workshop->instructauthorseditor['format'];
151     }
153     if ($draftitemid = $workshop->instructreviewerseditor['itemid']) {
154         $workshop->instructreviewers = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructreviewers',
155                 0, workshop::instruction_editors_options($context), $workshop->instructreviewerseditor['text']);
156         $workshop->instructreviewersformat = $workshop->instructreviewerseditor['format'];
157     }
159     // re-save the record with the replaced URLs in editor fields
160     $DB->update_record('workshop', $workshop);
162     // update gradebook items
163     workshop_grade_item_update($workshop);
164     workshop_grade_item_category_update($workshop);
166     // update calendar events
167     workshop_calendar_update($workshop, $workshop->coursemodule);
169     return true;
172 /**
173  * Given an ID of an instance of this module,
174  * this function will permanently delete the instance
175  * and any data that depends on it.
176  *
177  * @param int $id Id of the module instance
178  * @return boolean Success/Failure
179  */
180 function workshop_delete_instance($id) {
181     global $CFG, $DB;
182     require_once($CFG->libdir.'/gradelib.php');
184     if (! $workshop = $DB->get_record('workshop', array('id' => $id))) {
185         return false;
186     }
188     // delete all associated aggregations
189     $DB->delete_records('workshop_aggregations', array('workshopid' => $workshop->id));
191     // get the list of ids of all submissions
192     $submissions = $DB->get_records('workshop_submissions', array('workshopid' => $workshop->id), '', 'id');
194     // get the list of all allocated assessments
195     $assessments = $DB->get_records_list('workshop_assessments', 'submissionid', array_keys($submissions), '', 'id');
197     // delete the associated records from the workshop core tables
198     $DB->delete_records_list('workshop_grades', 'assessmentid', array_keys($assessments));
199     $DB->delete_records_list('workshop_assessments', 'id', array_keys($assessments));
200     $DB->delete_records_list('workshop_submissions', 'id', array_keys($submissions));
202     // call the static clean-up methods of all available subplugins
203     $strategies = get_plugin_list('workshopform');
204     foreach ($strategies as $strategy => $path) {
205         require_once($path.'/lib.php');
206         $classname = 'workshop_'.$strategy.'_strategy';
207         call_user_func($classname.'::delete_instance', $workshop->id);
208     }
210     $allocators = get_plugin_list('workshopallocation');
211     foreach ($allocators as $allocator => $path) {
212         require_once($path.'/lib.php');
213         $classname = 'workshop_'.$allocator.'_allocator';
214         call_user_func($classname.'::delete_instance', $workshop->id);
215     }
217     $evaluators = get_plugin_list('workshopeval');
218     foreach ($evaluators as $evaluator => $path) {
219         require_once($path.'/lib.php');
220         $classname = 'workshop_'.$evaluator.'_evaluation';
221         call_user_func($classname.'::delete_instance', $workshop->id);
222     }
224     // delete the calendar events
225     $events = $DB->get_records('event', array('modulename' => 'workshop', 'instance' => $workshop->id));
226     foreach ($events as $event) {
227         $event = calendar_event::load($event);
228         $event->delete();
229     }
231     // finally remove the workshop record itself
232     $DB->delete_records('workshop', array('id' => $workshop->id));
234     // gradebook cleanup
235     grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 0, null, array('deleted' => true));
236     grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 1, null, array('deleted' => true));
238     return true;
241 /**
242  * Return a small object with summary information about what a
243  * user has done with a given particular instance of this module
244  * Used for user activity reports.
245  * $return->time = the time they did it
246  * $return->info = a short text description
247  *
248  * @return stdclass|null
249  */
250 function workshop_user_outline($course, $user, $mod, $workshop) {
251     global $CFG, $DB;
252     require_once($CFG->libdir.'/gradelib.php');
254     $grades = grade_get_grades($course->id, 'mod', 'workshop', $workshop->id, $user->id);
256     $submissiongrade = null;
257     $assessmentgrade = null;
259     $info = '';
260     $time = 0;
262     if (!empty($grades->items[0]->grades)) {
263         $submissiongrade = reset($grades->items[0]->grades);
264         $info .= get_string('submissiongrade', 'workshop') . ': ' . $submissiongrade->str_long_grade . html_writer::empty_tag('br');
265         $time = max($time, $submissiongrade->dategraded);
266     }
267     if (!empty($grades->items[1]->grades)) {
268         $assessmentgrade = reset($grades->items[1]->grades);
269         $info .= get_string('gradinggrade', 'workshop') . ': ' . $assessmentgrade->str_long_grade;
270         $time = max($time, $assessmentgrade->dategraded);
271     }
273     if (!empty($info) and !empty($time)) {
274         $return = new stdclass();
275         $return->time = $time;
276         $return->info = $info;
277         return $return;
278     }
280     return null;
283 /**
284  * Print a detailed representation of what a user has done with
285  * a given particular instance of this module, for user activity reports.
286  *
287  * @return string HTML
288  */
289 function workshop_user_complete($course, $user, $mod, $workshop) {
290     global $CFG, $DB, $OUTPUT;
291     require_once(dirname(__FILE__).'/locallib.php');
292     require_once($CFG->libdir.'/gradelib.php');
294     $workshop   = new workshop($workshop, $mod, $course);
295     $grades     = grade_get_grades($course->id, 'mod', 'workshop', $workshop->id, $user->id);
297     if (!empty($grades->items[0]->grades)) {
298         $submissiongrade = reset($grades->items[0]->grades);
299         $info = get_string('submissiongrade', 'workshop') . ': ' . $submissiongrade->str_long_grade;
300         echo html_writer::tag('li', $info, array('class'=>'submissiongrade'));
301     }
302     if (!empty($grades->items[1]->grades)) {
303         $assessmentgrade = reset($grades->items[1]->grades);
304         $info = get_string('gradinggrade', 'workshop') . ': ' . $assessmentgrade->str_long_grade;
305         echo html_writer::tag('li', $info, array('class'=>'gradinggrade'));
306     }
308     if (has_capability('mod/workshop:viewallsubmissions', $workshop->context)) {
309         $canviewsubmission = true;
310         if (groups_get_activity_groupmode($workshop->cm) == SEPARATEGROUPS) {
311             // user must have accessallgroups or share at least one group with the submission author
312             if (!has_capability('moodle/site:accessallgroups', $workshop->context)) {
313                 $usersgroups = groups_get_activity_allowed_groups($workshop->cm);
314                 $authorsgroups = groups_get_all_groups($workshop->course->id, $user->id, $workshop->cm->groupingid, 'g.id');
315                 $sharedgroups = array_intersect_key($usersgroups, $authorsgroups);
316                 if (empty($sharedgroups)) {
317                     $canviewsubmission = false;
318                 }
319             }
320         }
321         if ($canviewsubmission and $submission = $workshop->get_submission_by_author($user->id)) {
322             $title      = format_string($submission->title);
323             $url        = $workshop->submission_url($submission->id);
324             $link       = html_writer::link($url, $title);
325             $info       = get_string('submission', 'workshop').': '.$link;
326             echo html_writer::tag('li', $info, array('class'=>'submission'));
327         }
328     }
330     if (has_capability('mod/workshop:viewallassessments', $workshop->context)) {
331         if ($assessments = $workshop->get_assessments_by_reviewer($user->id)) {
332             foreach ($assessments as $assessment) {
333                 $a = new stdclass();
334                 $a->submissionurl = $workshop->submission_url($assessment->submissionid)->out();
335                 $a->assessmenturl = $workshop->assess_url($assessment->id)->out();
336                 $a->submissiontitle = s($assessment->submissiontitle);
337                 echo html_writer::tag('li', get_string('assessmentofsubmission', 'workshop', $a));
338             }
339         }
340     }
343 /**
344  * Given a course and a time, this module should find recent activity
345  * that has occurred in workshop activities and print it out.
346  * Return true if there was output, or false is there was none.
347  *
348  * @param stdClass $course
349  * @param bool $viewfullnames
350  * @param int $timestart
351  * @return boolean
352  */
353 function workshop_print_recent_activity($course, $viewfullnames, $timestart) {
354     global $CFG, $USER, $DB, $OUTPUT;
356     $sql = "SELECT s.id AS submissionid, s.title AS submissiontitle, s.timemodified AS submissionmodified,
357                    author.id AS authorid, author.lastname AS authorlastname, author.firstname AS authorfirstname,
358                    a.id AS assessmentid, a.timemodified AS assessmentmodified,
359                    reviewer.id AS reviewerid, reviewer.lastname AS reviewerlastname, reviewer.firstname AS reviewerfirstname,
360                    cm.id AS cmid
361               FROM {workshop} w
362         INNER JOIN {course_modules} cm ON cm.instance = w.id
363         INNER JOIN {modules} md ON md.id = cm.module
364         INNER JOIN {workshop_submissions} s ON s.workshopid = w.id
365         INNER JOIN {user} author ON s.authorid = author.id
366          LEFT JOIN {workshop_assessments} a ON a.submissionid = s.id
367          LEFT JOIN {user} reviewer ON a.reviewerid = reviewer.id
368              WHERE cm.course = ?
369                    AND md.name = 'workshop'
370                    AND s.example = 0
371                    AND (s.timemodified > ? OR a.timemodified > ?)";
373     $rs = $DB->get_recordset_sql($sql, array($course->id, $timestart, $timestart));
375     $modinfo = get_fast_modinfo($course); // reference needed because we might load the groups
377     $submissions = array(); // recent submissions indexed by submission id
378     $assessments = array(); // recent assessments indexed by assessment id
379     $users       = array();
381     foreach ($rs as $activity) {
382         if (!array_key_exists($activity->cmid, $modinfo->cms)) {
383             // this should not happen but just in case
384             continue;
385         }
387         $cm = $modinfo->cms[$activity->cmid];
388         if (!$cm->uservisible) {
389             continue;
390         }
392         if ($viewfullnames) {
393             // remember all user names we can use later
394             if (empty($users[$activity->authorid])) {
395                 $u = new stdclass();
396                 $u->lastname = $activity->authorlastname;
397                 $u->firstname = $activity->authorfirstname;
398                 $users[$activity->authorid] = $u;
399             }
400             if ($activity->reviewerid and empty($users[$activity->reviewerid])) {
401                 $u = new stdclass();
402                 $u->lastname = $activity->reviewerlastname;
403                 $u->firstname = $activity->reviewerfirstname;
404                 $users[$activity->reviewerid] = $u;
405             }
406         }
408         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
409         $groupmode = groups_get_activity_groupmode($cm, $course);
411         if ($activity->submissionmodified > $timestart and empty($submissions[$activity->submissionid])) {
412             $s = new stdclass();
413             $s->title = $activity->submissiontitle;
414             $s->authorid = $activity->authorid;
415             $s->timemodified = $activity->submissionmodified;
416             $s->cmid = $activity->cmid;
417             if (has_capability('mod/workshop:viewauthornames', $context)) {
418                 $s->authornamevisible = true;
419             } else {
420                 $s->authornamevisible = false;
421             }
423             // the following do-while wrapper allows to break from deeply nested if-statements
424             do {
425                 if ($s->authorid === $USER->id) {
426                     // own submissions always visible
427                     $submissions[$activity->submissionid] = $s;
428                     break;
429                 }
431                 if (has_capability('mod/workshop:viewallsubmissions', $context)) {
432                     if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
433                         if (isguestuser()) {
434                             // shortcut - guest user does not belong into any group
435                             break;
436                         }
438                         if (is_null($modinfo->groups)) {
439                             $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
440                         }
442                         // this might be slow - show only submissions by users who share group with me in this cm
443                         if (empty($modinfo->groups[$cm->id])) {
444                             break;
445                         }
446                         $authorsgroups = groups_get_all_groups($course->id, $s->authorid, $cm->groupingid);
447                         if (is_array($authorsgroups)) {
448                             $authorsgroups = array_keys($authorsgroups);
449                             $intersect = array_intersect($authorsgroups, $modinfo->groups[$cm->id]);
450                             if (empty($intersect)) {
451                                 break;
452                             } else {
453                                 // can see all submissions and shares a group with the author
454                                 $submissions[$activity->submissionid] = $s;
455                                 break;
456                             }
457                         }
459                     } else {
460                         // can see all submissions from all groups
461                         $submissions[$activity->submissionid] = $s;
462                     }
463                 }
464             } while (0);
465         }
467         if ($activity->assessmentmodified > $timestart and empty($assessments[$activity->assessmentid])) {
468             $a = new stdclass();
469             $a->submissionid = $activity->submissionid;
470             $a->submissiontitle = $activity->submissiontitle;
471             $a->reviewerid = $activity->reviewerid;
472             $a->timemodified = $activity->assessmentmodified;
473             $a->cmid = $activity->cmid;
474             if (has_capability('mod/workshop:viewreviewernames', $context)) {
475                 $a->reviewernamevisible = true;
476             } else {
477                 $a->reviewernamevisible = false;
478             }
480             // the following do-while wrapper allows to break from deeply nested if-statements
481             do {
482                 if ($a->reviewerid === $USER->id) {
483                     // own assessments always visible
484                     $assessments[$activity->assessmentid] = $a;
485                     break;
486                 }
488                 if (has_capability('mod/workshop:viewallassessments', $context)) {
489                     if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
490                         if (isguestuser()) {
491                             // shortcut - guest user does not belong into any group
492                             break;
493                         }
495                         if (is_null($modinfo->groups)) {
496                             $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
497                         }
499                         // this might be slow - show only submissions by users who share group with me in this cm
500                         if (empty($modinfo->groups[$cm->id])) {
501                             break;
502                         }
503                         $reviewersgroups = groups_get_all_groups($course->id, $a->reviewerid, $cm->groupingid);
504                         if (is_array($reviewersgroups)) {
505                             $reviewersgroups = array_keys($reviewersgroups);
506                             $intersect = array_intersect($reviewersgroups, $modinfo->groups[$cm->id]);
507                             if (empty($intersect)) {
508                                 break;
509                             } else {
510                                 // can see all assessments and shares a group with the reviewer
511                                 $assessments[$activity->assessmentid] = $a;
512                                 break;
513                             }
514                         }
516                     } else {
517                         // can see all assessments from all groups
518                         $assessments[$activity->assessmentid] = $a;
519                     }
520                 }
521             } while (0);
522         }
523     }
524     $rs->close();
526     $shown = false;
528     if (!empty($submissions)) {
529         $shown = true;
530         echo $OUTPUT->heading(get_string('recentsubmissions', 'workshop'), 3);
531         foreach ($submissions as $id => $submission) {
532             $link = new moodle_url('/mod/workshop/submission.php', array('id'=>$id, 'cmid'=>$submission->cmid));
533             if ($viewfullnames and $submission->authornamevisible) {
534                 $author = $users[$submission->authorid];
535             } else {
536                 $author = null;
537             }
538             print_recent_activity_note($submission->timemodified, $author, $submission->title, $link->out(), false, $viewfullnames);
539         }
540     }
542     if (!empty($assessments)) {
543         $shown = true;
544         echo $OUTPUT->heading(get_string('recentassessments', 'workshop'), 3);
545         foreach ($assessments as $id => $assessment) {
546             $link = new moodle_url('/mod/workshop/assessment.php', array('asid' => $id));
547             if ($viewfullnames and $assessment->reviewernamevisible) {
548                 $reviewer = $users[$assessment->reviewerid];
549             } else {
550                 $reviewer = null;
551             }
552             print_recent_activity_note($assessment->timemodified, $reviewer, $assessment->submissiontitle, $link->out(), false, $viewfullnames);
553         }
554     }
556     if ($shown) {
557         return true;
558     }
560     return false;
563 /**
564  * Returns all activity in course workshops since a given time
565  *
566  * @param array $activities sequentially indexed array of objects
567  * @param int $index
568  * @param int $timestart
569  * @param int $courseid
570  * @param int $cmid
571  * @param int $userid defaults to 0
572  * @param int $groupid defaults to 0
573  * @return void adds items into $activities and increases $index
574  */
575 function workshop_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid=0, $groupid=0) {
576     global $CFG, $COURSE, $USER, $DB;
578     if ($COURSE->id == $courseid) {
579         $course = $COURSE;
580     } else {
581         $course = $DB->get_record('course', array('id'=>$courseid));
582     }
584     $modinfo = get_fast_modinfo($course);
586     $cm = $modinfo->cms[$cmid];
588     $params = array();
589     if ($userid) {
590         $userselect = "AND (author.id = :authorid OR reviewer.id = :reviewerid)";
591         $params['authorid'] = $userid;
592         $params['reviewerid'] = $userid;
593     } else {
594         $userselect = "";
595     }
597     if ($groupid) {
598         $groupselect = "AND (authorgroupmembership.groupid = :authorgroupid OR reviewergroupmembership.groupid = :reviewergroupid)";
599         $groupjoin   = "LEFT JOIN {groups_members} authorgroupmembership ON authorgroupmembership.userid = author.id
600                         LEFT JOIN {groups_members} reviewergroupmembership ON reviewergroupmembership.userid = reviewer.id";
601         $params['authorgroupid'] = $groupid;
602         $params['reviewergroupid'] = $groupid;
603     } else {
604         $groupselect = "";
605         $groupjoin   = "";
606     }
608     $params['cminstance'] = $cm->instance;
609     $params['submissionmodified'] = $timestart;
610     $params['assessmentmodified'] = $timestart;
612     $sql = "SELECT s.id AS submissionid, s.title AS submissiontitle, s.timemodified AS submissionmodified,
613                    author.id AS authorid, author.lastname AS authorlastname, author.firstname AS authorfirstname,
614                    author.picture AS authorpicture, author.imagealt AS authorimagealt, author.email AS authoremail,
615                    a.id AS assessmentid, a.timemodified AS assessmentmodified,
616                    reviewer.id AS reviewerid, reviewer.lastname AS reviewerlastname, reviewer.firstname AS reviewerfirstname,
617                    reviewer.picture AS reviewerpicture, 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         = get_context_instance(CONTEXT_MODULE, $cm->id);
634     $grader          = has_capability('moodle/grade:viewall', $context);
635     $accessallgroups = has_capability('moodle/site:accessallgroups', $context);
636     $viewfullnames   = has_capability('moodle/site:viewfullnames', $context);
637     $viewauthors     = has_capability('mod/workshop:viewauthornames', $context);
638     $viewreviewers   = has_capability('mod/workshop:viewreviewernames', $context);
640     if (is_null($modinfo->groups)) {
641         $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
642     }
644     $submissions = array(); // recent submissions indexed by submission id
645     $assessments = array(); // recent assessments indexed by assessment id
646     $users       = array();
648     foreach ($rs as $activity) {
650         if ($viewfullnames) {
651             // remember all user names we can use later
652             if (empty($users[$activity->authorid])) {
653                 $u = new stdclass();
654                 $u->id = $activity->authorid;
655                 $u->lastname = $activity->authorlastname;
656                 $u->firstname = $activity->authorfirstname;
657                 $u->picture = $activity->authorpicture;
658                 $u->imagealt = $activity->authorimagealt;
659                 $u->email = $activity->authoremail;
660                 $users[$activity->authorid] = $u;
661             }
662             if ($activity->reviewerid and empty($users[$activity->reviewerid])) {
663                 $u = new stdclass();
664                 $u->id = $activity->reviewerid;
665                 $u->lastname = $activity->reviewerlastname;
666                 $u->firstname = $activity->reviewerfirstname;
667                 $u->picture = $activity->reviewerpicture;
668                 $u->imagealt = $activity->reviewerimagealt;
669                 $u->email = $activity->revieweremail;
670                 $users[$activity->reviewerid] = $u;
671             }
672         }
674         if ($activity->submissionmodified > $timestart and empty($submissions[$activity->submissionid])) {
675             $s = new stdclass();
676             $s->id = $activity->submissionid;
677             $s->title = $activity->submissiontitle;
678             $s->authorid = $activity->authorid;
679             $s->timemodified = $activity->submissionmodified;
680             if (has_capability('mod/workshop:viewauthornames', $context)) {
681                 $s->authornamevisible = true;
682             } else {
683                 $s->authornamevisible = false;
684             }
686             // the following do-while wrapper allows to break from deeply nested if-statements
687             do {
688                 if ($s->authorid === $USER->id) {
689                     // own submissions always visible
690                     $submissions[$activity->submissionid] = $s;
691                     break;
692                 }
694                 if (has_capability('mod/workshop:viewallsubmissions', $context)) {
695                     if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
696                         if (isguestuser()) {
697                             // shortcut - guest user does not belong into any group
698                             break;
699                         }
701                         // this might be slow - show only submissions by users who share group with me in this cm
702                         if (empty($modinfo->groups[$cm->id])) {
703                             break;
704                         }
705                         $authorsgroups = groups_get_all_groups($course->id, $s->authorid, $cm->groupingid);
706                         if (is_array($authorsgroups)) {
707                             $authorsgroups = array_keys($authorsgroups);
708                             $intersect = array_intersect($authorsgroups, $modinfo->groups[$cm->id]);
709                             if (empty($intersect)) {
710                                 break;
711                             } else {
712                                 // can see all submissions and shares a group with the author
713                                 $submissions[$activity->submissionid] = $s;
714                                 break;
715                             }
716                         }
718                     } else {
719                         // can see all submissions from all groups
720                         $submissions[$activity->submissionid] = $s;
721                     }
722                 }
723             } while (0);
724         }
726         if ($activity->assessmentmodified > $timestart and empty($assessments[$activity->assessmentid])) {
727             $a = new stdclass();
728             $a->id = $activity->assessmentid;
729             $a->submissionid = $activity->submissionid;
730             $a->submissiontitle = $activity->submissiontitle;
731             $a->reviewerid = $activity->reviewerid;
732             $a->timemodified = $activity->assessmentmodified;
733             if (has_capability('mod/workshop:viewreviewernames', $context)) {
734                 $a->reviewernamevisible = true;
735             } else {
736                 $a->reviewernamevisible = false;
737             }
739             // the following do-while wrapper allows to break from deeply nested if-statements
740             do {
741                 if ($a->reviewerid === $USER->id) {
742                     // own assessments always visible
743                     $assessments[$activity->assessmentid] = $a;
744                     break;
745                 }
747                 if (has_capability('mod/workshop:viewallassessments', $context)) {
748                     if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
749                         if (isguestuser()) {
750                             // shortcut - guest user does not belong into any group
751                             break;
752                         }
754                         // this might be slow - show only submissions by users who share group with me in this cm
755                         if (empty($modinfo->groups[$cm->id])) {
756                             break;
757                         }
758                         $reviewersgroups = groups_get_all_groups($course->id, $a->reviewerid, $cm->groupingid);
759                         if (is_array($reviewersgroups)) {
760                             $reviewersgroups = array_keys($reviewersgroups);
761                             $intersect = array_intersect($reviewersgroups, $modinfo->groups[$cm->id]);
762                             if (empty($intersect)) {
763                                 break;
764                             } else {
765                                 // can see all assessments and shares a group with the reviewer
766                                 $assessments[$activity->assessmentid] = $a;
767                                 break;
768                             }
769                         }
771                     } else {
772                         // can see all assessments from all groups
773                         $assessments[$activity->assessmentid] = $a;
774                     }
775                 }
776             } while (0);
777         }
778     }
779     $rs->close();
781     $workshopname = format_string($cm->name, true);
783     if ($grader) {
784         require_once($CFG->libdir.'/gradelib.php');
785         $grades = grade_get_grades($courseid, 'mod', 'workshop', $cm->instance, array_keys($users));
786     }
788     foreach ($submissions as $submission) {
789         $tmpactivity                = new stdclass();
790         $tmpactivity->type          = 'workshop';
791         $tmpactivity->cmid          = $cm->id;
792         $tmpactivity->name          = $workshopname;
793         $tmpactivity->sectionnum    = $cm->sectionnum;
794         $tmpactivity->timestamp     = $submission->timemodified;
795         $tmpactivity->subtype       = 'submission';
796         $tmpactivity->content       = $submission;
797         if ($grader) {
798             $tmpactivity->grade     = $grades->items[0]->grades[$submission->authorid]->str_long_grade;
799         }
800         if ($submission->authornamevisible and !empty($users[$submission->authorid])) {
801             $tmpactivity->user      = $users[$submission->authorid];
802         }
803         $activities[$index++]       = $tmpactivity;
804     }
806     foreach ($assessments as $assessment) {
807         $tmpactivity                = new stdclass();
808         $tmpactivity->type          = 'workshop';
809         $tmpactivity->cmid          = $cm->id;
810         $tmpactivity->name          = $workshopname;
811         $tmpactivity->sectionnum    = $cm->sectionnum;
812         $tmpactivity->timestamp     = $assessment->timemodified;
813         $tmpactivity->subtype       = 'assessment';
814         $tmpactivity->content       = $assessment;
815         if ($grader) {
816             $tmpactivity->grade     = $grades->items[1]->grades[$assessment->reviewerid]->str_long_grade;
817         }
818         if ($assessment->reviewernamevisible and !empty($users[$assessment->reviewerid])) {
819             $tmpactivity->user      = $users[$assessment->reviewerid];
820         }
821         $activities[$index++]       = $tmpactivity;
822     }
825 /**
826  * Print single activity item prepared by {@see workshop_get_recent_mod_activity()}
827  */
828 function workshop_print_recent_mod_activity($activity, $courseid, $detail, $modnames, $viewfullnames) {
829     global $CFG, $OUTPUT;
831     if (!empty($activity->user)) {
832         echo html_writer::tag('div', $OUTPUT->user_picture($activity->user, array('courseid'=>$courseid)),
833                 array('style' => 'float: left; padding: 7px;'));
834     }
836     if ($activity->subtype == 'submission') {
837         echo html_writer::start_tag('div', array('class'=>'submission', 'style'=>'padding: 7px; float:left;'));
839         if ($detail) {
840             echo html_writer::start_tag('h4', array('class'=>'workshop'));
841             $url = new moodle_url('/mod/workshop/view.php', array('id'=>$activity->cmid));
842             $name = s($activity->name);
843             echo html_writer::empty_tag('img', array('src'=>$OUTPUT->pix_url('icon', $activity->type), 'class'=>'icon', 'alt'=>$name));
844             echo ' ' . $modnames[$activity->type];
845             echo html_writer::link($url, $name, array('class'=>'name', 'style'=>'margin-left: 5px'));
846             echo html_writer::end_tag('h4');
847         }
849         echo html_writer::start_tag('div', array('class'=>'title'));
850         $url = new moodle_url('/mod/workshop/submission.php', array('cmid'=>$activity->cmid, 'id'=>$activity->content->id));
851         $name = s($activity->content->title);
852         echo html_writer::tag('strong', html_writer::link($url, $name));
853         echo html_writer::end_tag('div');
855         if (!empty($activity->user)) {
856             echo html_writer::start_tag('div', array('class'=>'user'));
857             $url = new moodle_url('/user/view.php', array('id'=>$activity->user->id, 'course'=>$courseid));
858             $name = fullname($activity->user);
859             $link = html_writer::link($url, $name);
860             echo get_string('submissionby', 'workshop', $link);
861             echo ' - '.userdate($activity->timestamp);
862             echo html_writer::end_tag('div');
863         } else {
864             echo html_writer::start_tag('div', array('class'=>'anonymous'));
865             echo get_string('submission', 'workshop');
866             echo ' - '.userdate($activity->timestamp);
867             echo html_writer::end_tag('div');
868         }
870         echo html_writer::end_tag('div');
871     }
873     if ($activity->subtype == 'assessment') {
874         echo html_writer::start_tag('div', array('class'=>'assessment', 'style'=>'padding: 7px; float:left;'));
876         if ($detail) {
877             echo html_writer::start_tag('h4', array('class'=>'workshop'));
878             $url = new moodle_url('/mod/workshop/view.php', array('id'=>$activity->cmid));
879             $name = s($activity->name);
880             echo html_writer::empty_tag('img', array('src'=>$OUTPUT->pix_url('icon', $activity->type), 'class'=>'icon', 'alt'=>$name));
881             echo ' ' . $modnames[$activity->type];
882             echo html_writer::link($url, $name, array('class'=>'name', 'style'=>'margin-left: 5px'));
883             echo html_writer::end_tag('h4');
884         }
886         echo html_writer::start_tag('div', array('class'=>'title'));
887         $url = new moodle_url('/mod/workshop/assessment.php', array('asid'=>$activity->content->id));
888         $name = s($activity->content->submissiontitle);
889         echo html_writer::tag('em', html_writer::link($url, $name));
890         echo html_writer::end_tag('div');
892         if (!empty($activity->user)) {
893             echo html_writer::start_tag('div', array('class'=>'user'));
894             $url = new moodle_url('/user/view.php', array('id'=>$activity->user->id, 'course'=>$courseid));
895             $name = fullname($activity->user);
896             $link = html_writer::link($url, $name);
897             echo get_string('assessmentbyfullname', 'workshop', $link);
898             echo ' - '.userdate($activity->timestamp);
899             echo html_writer::end_tag('div');
900         } else {
901             echo html_writer::start_tag('div', array('class'=>'anonymous'));
902             echo get_string('assessment', 'workshop');
903             echo ' - '.userdate($activity->timestamp);
904             echo html_writer::end_tag('div');
905         }
907         echo html_writer::end_tag('div');
908     }
910     echo html_writer::empty_tag('br', array('style'=>'clear:both'));
913 /**
914  * Regular jobs to execute via cron
915  *
916  * @return boolean true on success, false otherwise
917  */
918 function workshop_cron() {
919     global $CFG, $DB;
921     $now = time();
923     mtrace(' processing workshop subplugins ...');
924     cron_execute_plugin_type('workshopallocation', 'workshop allocation methods');
926     // now when the scheduled allocator had a chance to do its job, check if there
927     // are some workshops to switch into the assessment phase
928     $workshops = $DB->get_records_select("workshop",
929         "phase = 20 AND phaseswitchassessment = 1 AND submissionend > 0 AND submissionend < ?", array($now));
931     if (!empty($workshops)) {
932         mtrace('Processing automatic assessment phase switch in '.count($workshops).' workshop(s) ... ', '');
933         require_once($CFG->dirroot.'/mod/workshop/locallib.php');
934         foreach ($workshops as $workshop) {
935             $cm = get_coursemodule_from_instance('workshop', $workshop->id, $workshop->course, false, MUST_EXIST);
936             $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
937             $workshop = new workshop($workshop, $cm, $course);
938             $workshop->switch_phase(workshop::PHASE_ASSESSMENT);
939             $workshop->log('update switch phase', $workshop->view_url(), $workshop->phase);
940             // disable the automatic switching now so that it is not executed again by accident
941             // if the teacher changes the phase back to the submission one
942             $DB->set_field('workshop', 'phaseswitchassessment', 0, array('id' => $workshop->id));
944             // todo inform the teachers
945         }
946         mtrace('done');
947     }
949     return true;
952 /**
953  * Is a given scale used by the instance of workshop?
954  *
955  * The function asks all installed grading strategy subplugins. The workshop
956  * core itself does not use scales. Both grade for submission and grade for
957  * assessments do not use scales.
958  *
959  * @param int $workshopid id of workshop instance
960  * @param int $scaleid id of the scale to check
961  * @return bool
962  */
963 function workshop_scale_used($workshopid, $scaleid) {
964     global $CFG; // other files included from here
966     $strategies = get_plugin_list('workshopform');
967     foreach ($strategies as $strategy => $strategypath) {
968         $strategylib = $strategypath . '/lib.php';
969         if (is_readable($strategylib)) {
970             require_once($strategylib);
971         } else {
972             throw new coding_exception('the grading forms subplugin must contain library ' . $strategylib);
973         }
974         $classname = 'workshop_' . $strategy . '_strategy';
975         if (method_exists($classname, 'scale_used')) {
976             if (call_user_func_array(array($classname, 'scale_used'), array($scaleid, $workshopid))) {
977                 // no need to include any other files - scale is used
978                 return true;
979             }
980         }
981     }
983     return false;
986 /**
987  * Is a given scale used by any instance of workshop?
988  *
989  * The function asks all installed grading strategy subplugins. The workshop
990  * core itself does not use scales. Both grade for submission and grade for
991  * assessments do not use scales.
992  *
993  * @param int $scaleid id of the scale to check
994  * @return bool
995  */
996 function workshop_scale_used_anywhere($scaleid) {
997     global $CFG; // other files included from here
999     $strategies = get_plugin_list('workshopform');
1000     foreach ($strategies as $strategy => $strategypath) {
1001         $strategylib = $strategypath . '/lib.php';
1002         if (is_readable($strategylib)) {
1003             require_once($strategylib);
1004         } else {
1005             throw new coding_exception('the grading forms subplugin must contain library ' . $strategylib);
1006         }
1007         $classname = 'workshop_' . $strategy . '_strategy';
1008         if (method_exists($classname, 'scale_used')) {
1009             if (call_user_func(array($classname, 'scale_used'), $scaleid)) {
1010                 // no need to include any other files - scale is used
1011                 return true;
1012             }
1013         }
1014     }
1016     return false;
1019 /**
1020  * Returns all other caps used in the module
1021  *
1022  * @return array
1023  */
1024 function workshop_get_extra_capabilities() {
1025     return array('moodle/site:accessallgroups');
1028 ////////////////////////////////////////////////////////////////////////////////
1029 // Gradebook API                                                              //
1030 ////////////////////////////////////////////////////////////////////////////////
1032 /**
1033  * Creates or updates grade items for the give workshop instance
1034  *
1035  * Needed by grade_update_mod_grades() in lib/gradelib.php. Also used by
1036  * {@link workshop_update_grades()}.
1037  *
1038  * @param stdClass $workshop instance object with extra cmidnumber property
1039  * @param stdClass $submissiongrades data for the first grade item
1040  * @param stdClass $assessmentgrades data for the second grade item
1041  * @return void
1042  */
1043 function workshop_grade_item_update(stdclass $workshop, $submissiongrades=null, $assessmentgrades=null) {
1044     global $CFG;
1045     require_once($CFG->libdir.'/gradelib.php');
1047     $a = new stdclass();
1048     $a->workshopname = clean_param($workshop->name, PARAM_NOTAGS);
1050     $item = array();
1051     $item['itemname'] = get_string('gradeitemsubmission', 'workshop', $a);
1052     $item['gradetype'] = GRADE_TYPE_VALUE;
1053     $item['grademax']  = $workshop->grade;
1054     $item['grademin']  = 0;
1055     grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 0, $submissiongrades , $item);
1057     $item = array();
1058     $item['itemname'] = get_string('gradeitemassessment', 'workshop', $a);
1059     $item['gradetype'] = GRADE_TYPE_VALUE;
1060     $item['grademax']  = $workshop->gradinggrade;
1061     $item['grademin']  = 0;
1062     grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 1, $assessmentgrades, $item);
1065 /**
1066  * Update workshop grades in the gradebook
1067  *
1068  * Needed by grade_update_mod_grades() in lib/gradelib.php
1069  *
1070  * @category grade
1071  * @param stdClass $workshop instance object with extra cmidnumber and modname property
1072  * @param int $userid        update grade of specific user only, 0 means all participants
1073  * @return void
1074  */
1075 function workshop_update_grades(stdclass $workshop, $userid=0) {
1076     global $CFG, $DB;
1077     require_once($CFG->libdir.'/gradelib.php');
1079     $whereuser = $userid ? ' AND authorid = :userid' : '';
1080     $params = array('workshopid' => $workshop->id, 'userid' => $userid);
1081     $sql = 'SELECT authorid, grade, gradeover, gradeoverby, feedbackauthor, feedbackauthorformat, timemodified, timegraded
1082               FROM {workshop_submissions}
1083              WHERE workshopid = :workshopid AND example=0' . $whereuser;
1084     $records = $DB->get_records_sql($sql, $params);
1085     $submissiongrades = array();
1086     foreach ($records as $record) {
1087         $grade = new stdclass();
1088         $grade->userid = $record->authorid;
1089         if (!is_null($record->gradeover)) {
1090             $grade->rawgrade = grade_floatval($workshop->grade * $record->gradeover / 100);
1091             $grade->usermodified = $record->gradeoverby;
1092         } else {
1093             $grade->rawgrade = grade_floatval($workshop->grade * $record->grade / 100);
1094         }
1095         $grade->feedback = $record->feedbackauthor;
1096         $grade->feedbackformat = $record->feedbackauthorformat;
1097         $grade->datesubmitted = $record->timemodified;
1098         $grade->dategraded = $record->timegraded;
1099         $submissiongrades[$record->authorid] = $grade;
1100     }
1102     $whereuser = $userid ? ' AND userid = :userid' : '';
1103     $params = array('workshopid' => $workshop->id, 'userid' => $userid);
1104     $sql = 'SELECT userid, gradinggrade, timegraded
1105               FROM {workshop_aggregations}
1106              WHERE workshopid = :workshopid' . $whereuser;
1107     $records = $DB->get_records_sql($sql, $params);
1108     $assessmentgrades = array();
1109     foreach ($records as $record) {
1110         $grade = new stdclass();
1111         $grade->userid = $record->userid;
1112         $grade->rawgrade = grade_floatval($workshop->gradinggrade * $record->gradinggrade / 100);
1113         $grade->dategraded = $record->timegraded;
1114         $assessmentgrades[$record->userid] = $grade;
1115     }
1117     workshop_grade_item_update($workshop, $submissiongrades, $assessmentgrades);
1120 /**
1121  * Update the grade items categories if they are changed via mod_form.php
1122  *
1123  * We must do it manually here in the workshop module because modedit supports only
1124  * single grade item while we use two.
1125  *
1126  * @param stdClass $workshop An object from the form in mod_form.php
1127  */
1128 function workshop_grade_item_category_update($workshop) {
1130     $gradeitems = grade_item::fetch_all(array(
1131         'itemtype'      => 'mod',
1132         'itemmodule'    => 'workshop',
1133         'iteminstance'  => $workshop->id,
1134         'courseid'      => $workshop->course));
1136     if (!empty($gradeitems)) {
1137         foreach ($gradeitems as $gradeitem) {
1138             if ($gradeitem->itemnumber == 0) {
1139                 if ($gradeitem->categoryid != $workshop->gradecategory) {
1140                     $gradeitem->set_parent($workshop->gradecategory);
1141                 }
1142             } else if ($gradeitem->itemnumber == 1) {
1143                 if ($gradeitem->categoryid != $workshop->gradinggradecategory) {
1144                     $gradeitem->set_parent($workshop->gradinggradecategory);
1145                 }
1146             }
1147         }
1148     }
1151 ////////////////////////////////////////////////////////////////////////////////
1152 // File API                                                                   //
1153 ////////////////////////////////////////////////////////////////////////////////
1155 /**
1156  * Returns the lists of all browsable file areas within the given module context
1157  *
1158  * The file area workshop_intro for the activity introduction field is added automatically
1159  * by {@link file_browser::get_file_info_context_module()}
1160  *
1161  * @package  mod_workshop
1162  * @category files
1163  *
1164  * @param stdClass $course
1165  * @param stdClass $cm
1166  * @param stdClass $context
1167  * @return array of [(string)filearea] => (string)description
1168  */
1169 function workshop_get_file_areas($course, $cm, $context) {
1170     $areas = array();
1171     $areas['instructauthors']          = get_string('areainstructauthors', 'workshop');
1172     $areas['instructreviewers']        = get_string('areainstructreviewers', 'workshop');
1173     $areas['submission_content']       = get_string('areasubmissioncontent', 'workshop');
1174     $areas['submission_attachment']    = get_string('areasubmissionattachment', 'workshop');
1176     return $areas;
1179 /**
1180  * Serves the files from the workshop file areas
1181  *
1182  * Apart from module intro (handled by pluginfile.php automatically), workshop files may be
1183  * media inserted into submission content (like images) and submission attachments. For these two,
1184  * the fileareas submission_content and submission_attachment are used.
1185  * Besides that, areas instructauthors and instructreviewers contain the media
1186  * embedded using the mod_form.php.
1187  *
1188  * @package  mod_workshop
1189  * @category files
1190  *
1191  * @param stdClass $course the course object
1192  * @param stdClass $cm the course module object
1193  * @param stdClass $context the workshop's context
1194  * @param string $filearea the name of the file area
1195  * @param array $args extra arguments (itemid, path)
1196  * @param bool $forcedownload whether or not force download
1197  * @param array $options additional options affecting the file serving
1198  * @return bool false if the file not found, just send the file otherwise and do not return anything
1199  */
1200 function workshop_pluginfile($course, $cm, $context, $filearea, array $args, $forcedownload, array $options=array()) {
1201     global $DB, $CFG, $USER;
1203     if ($context->contextlevel != CONTEXT_MODULE) {
1204         return false;
1205     }
1207     require_login($course, true, $cm);
1209     if ($filearea === 'instructauthors') {
1210         array_shift($args); // itemid is ignored here
1211         $relativepath = implode('/', $args);
1212         $fullpath = "/$context->id/mod_workshop/$filearea/0/$relativepath";
1214         $fs = get_file_storage();
1215         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1216             send_file_not_found();
1217         }
1219         $lifetime = isset($CFG->filelifetime) ? $CFG->filelifetime : 86400;
1221         // finally send the file
1222         send_stored_file($file, $lifetime, 0, $forcedownload, $options);
1223     }
1225     if ($filearea === 'instructreviewers') {
1226         array_shift($args); // itemid is ignored here
1227         $relativepath = implode('/', $args);
1228         $fullpath = "/$context->id/mod_workshop/$filearea/0/$relativepath";
1230         $fs = get_file_storage();
1231         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1232             send_file_not_found();
1233         }
1235         $lifetime = isset($CFG->filelifetime) ? $CFG->filelifetime : 86400;
1237         // finally send the file
1238         send_stored_file($file, $lifetime, 0, $forcedownload, $options);
1240     } else if ($filearea === 'submission_content' or $filearea === 'submission_attachment') {
1241         $itemid = (int)array_shift($args);
1242         if (!$workshop = $DB->get_record('workshop', array('id' => $cm->instance))) {
1243             return false;
1244         }
1245         if (!$submission = $DB->get_record('workshop_submissions', array('id' => $itemid, 'workshopid' => $workshop->id))) {
1246             return false;
1247         }
1249         // make sure the user is allowed to see the file
1250         if (empty($submission->example)) {
1251             if ($USER->id != $submission->authorid) {
1252                 if (!$DB->record_exists('workshop_assessments', array('submissionid' => $submission->id, 'reviewerid' => $USER->id))) {
1253                     if (!has_capability('mod/workshop:viewallsubmissions', $context)) {
1254                         send_file_not_found();
1255                     } else {
1256                         $gmode = groups_get_activity_groupmode($cm, $course);
1257                         if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1258                             // check there is at least one common group with both the $USER
1259                             // and the submission author
1260                             $sql = "SELECT 'x'
1261                                       FROM {workshop_submissions} s
1262                                       JOIN {user} a ON (a.id = s.authorid)
1263                                       JOIN {groups_members} agm ON (a.id = agm.userid)
1264                                       JOIN {user} u ON (u.id = ?)
1265                                       JOIN {groups_members} ugm ON (u.id = ugm.userid)
1266                                      WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1267                             $params = array($USER->id, $workshop->id, $submission->id);
1268                             if (!$DB->record_exists_sql($sql, $params)) {
1269                                 send_file_not_found();
1270                             }
1271                         }
1272                     }
1273                 }
1274             }
1275         }
1277         $fs = get_file_storage();
1278         $relativepath = implode('/', $args);
1279         $fullpath = "/$context->id/mod_workshop/$filearea/$itemid/$relativepath";
1280         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1281             return false;
1282         }
1283         // finally send the file
1284         // these files are uploaded by students - forcing download for security reasons
1285         send_stored_file($file, 0, 0, true, $options);
1286     }
1288     return false;
1291 /**
1292  * File browsing support for workshop file areas
1293  *
1294  * @package  mod_workshop
1295  * @category files
1296  *
1297  * @param file_browser $browser
1298  * @param array $areas
1299  * @param stdClass $course
1300  * @param stdClass $cm
1301  * @param stdClass $context
1302  * @param string $filearea
1303  * @param int $itemid
1304  * @param string $filepath
1305  * @param string $filename
1306  * @return file_info instance or null if not found
1307  */
1308 function workshop_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
1309     global $CFG, $DB, $USER;
1310     /** @var array internal cache for author names */
1311     static $submissionauthors = array();
1313     $fs = get_file_storage();
1315     if ($filearea === 'submission_content' or $filearea === 'submission_attachment') {
1317         if (!has_capability('mod/workshop:viewallsubmissions', $context)) {
1318             return null;
1319         }
1321         if (is_null($itemid)) {
1322             // no itemid (submissionid) passed, display the list of all submissions
1323             require_once($CFG->dirroot . '/mod/workshop/fileinfolib.php');
1324             return new workshop_file_info_submissions_container($browser, $course, $cm, $context, $areas, $filearea);
1325         }
1327         // make sure the user can see the particular submission in separate groups mode
1328         $gmode = groups_get_activity_groupmode($cm, $course);
1330         if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1331             // check there is at least one common group with both the $USER
1332             // and the submission author (this is not expected to be a frequent
1333             // usecase so we can live with pretty ineffective one query per submission here...)
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, $cm->instance, $itemid);
1342             if (!$DB->record_exists_sql($sql, $params)) {
1343                 return null;
1344             }
1345         }
1347         // we are inside some particular submission container
1349         $filepath = is_null($filepath) ? '/' : $filepath;
1350         $filename = is_null($filename) ? '.' : $filename;
1352         if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, $itemid, $filepath, $filename)) {
1353             if ($filepath === '/' and $filename === '.') {
1354                 $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, $itemid);
1355             } else {
1356                 // not found
1357                 return null;
1358             }
1359         }
1361         // Checks to see if the user can manage files or is the owner.
1362         // TODO MDL-33805 - Do not use userid here and move the capability check above.
1363         if (!has_capability('moodle/course:managefiles', $context) && $storedfile->get_userid() != $USER->id) {
1364             return null;
1365         }
1367         // let us display the author's name instead of itemid (submission id)
1369         if (isset($submissionauthors[$itemid])) {
1370             $topvisiblename = $submissionauthors[$itemid];
1372         } else {
1374             $sql = "SELECT s.id, u.lastname, u.firstname
1375                       FROM {workshop_submissions} s
1376                       JOIN {user} u ON (s.authorid = u.id)
1377                      WHERE s.example = 0 AND s.workshopid = ?";
1378             $params = array($cm->instance);
1379             $rs = $DB->get_recordset_sql($sql, $params);
1381             foreach ($rs as $submissionauthor) {
1382                 $title = s(fullname($submissionauthor)); // this is generally not unique...
1383                 $submissionauthors[$submissionauthor->id] = $title;
1384             }
1385             $rs->close();
1387             if (!isset($submissionauthors[$itemid])) {
1388                 // should not happen
1389                 return null;
1390             } else {
1391                 $topvisiblename = $submissionauthors[$itemid];
1392             }
1393         }
1395         $urlbase = $CFG->wwwroot . '/pluginfile.php';
1396         // do not allow manual modification of any files!
1397         return new file_info_stored($browser, $context, $storedfile, $urlbase, $topvisiblename, true, true, false, false);
1398     }
1400     if ($filearea == 'instructauthors' or $filearea == 'instructreviewers') {
1401         // always only itemid 0
1403         $filepath = is_null($filepath) ? '/' : $filepath;
1404         $filename = is_null($filename) ? '.' : $filename;
1406         $urlbase = $CFG->wwwroot.'/pluginfile.php';
1407         if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, 0, $filepath, $filename)) {
1408             if ($filepath === '/' and $filename === '.') {
1409                 $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, 0);
1410             } else {
1411                 // not found
1412                 return null;
1413             }
1414         }
1415         return new file_info_stored($browser, $context, $storedfile, $urlbase, $areas[$filearea], false, true, true, false);
1416     }
1419 ////////////////////////////////////////////////////////////////////////////////
1420 // Navigation API                                                             //
1421 ////////////////////////////////////////////////////////////////////////////////
1423 /**
1424  * Extends the global navigation tree by adding workshop nodes if there is a relevant content
1425  *
1426  * This can be called by an AJAX request so do not rely on $PAGE as it might not be set up properly.
1427  *
1428  * @param navigation_node $navref An object representing the navigation tree node of the workshop module instance
1429  * @param stdClass $course
1430  * @param stdClass $module
1431  * @param cm_info $cm
1432  */
1433 function workshop_extend_navigation(navigation_node $navref, stdclass $course, stdclass $module, cm_info $cm) {
1434     global $CFG;
1436     if (has_capability('mod/workshop:submit', get_context_instance(CONTEXT_MODULE, $cm->id))) {
1437         $url = new moodle_url('/mod/workshop/submission.php', array('cmid' => $cm->id));
1438         $mysubmission = $navref->add(get_string('mysubmission', 'workshop'), $url);
1439         $mysubmission->mainnavonly = true;
1440     }
1443 /**
1444  * Extends the settings navigation with the Workshop settings
1446  * This function is called when the context for the page is a workshop module. This is not called by AJAX
1447  * so it is safe to rely on the $PAGE.
1448  *
1449  * @param settings_navigation $settingsnav {@link settings_navigation}
1450  * @param navigation_node $workshopnode {@link navigation_node}
1451  */
1452 function workshop_extend_settings_navigation(settings_navigation $settingsnav, navigation_node $workshopnode=null) {
1453     global $PAGE;
1455     //$workshopobject = $DB->get_record("workshop", array("id" => $PAGE->cm->instance));
1457     if (has_capability('mod/workshop:editdimensions', $PAGE->cm->context)) {
1458         $url = new moodle_url('/mod/workshop/editform.php', array('cmid' => $PAGE->cm->id));
1459         $workshopnode->add(get_string('editassessmentform', 'workshop'), $url, settings_navigation::TYPE_SETTING);
1460     }
1461     if (has_capability('mod/workshop:allocate', $PAGE->cm->context)) {
1462         $url = new moodle_url('/mod/workshop/allocation.php', array('cmid' => $PAGE->cm->id));
1463         $workshopnode->add(get_string('allocate', 'workshop'), $url, settings_navigation::TYPE_SETTING);
1464     }
1467 /**
1468  * Return a list of page types
1469  * @param string $pagetype current page type
1470  * @param stdClass $parentcontext Block's parent context
1471  * @param stdClass $currentcontext Current context of block
1472  */
1473 function workshop_page_type_list($pagetype, $parentcontext, $currentcontext) {
1474     $module_pagetype = array('mod-workshop-*'=>get_string('page-mod-workshop-x', 'workshop'));
1475     return $module_pagetype;
1478 ////////////////////////////////////////////////////////////////////////////////
1479 // Calendar API                                                               //
1480 ////////////////////////////////////////////////////////////////////////////////
1482 /**
1483  * Updates the calendar events associated to the given workshop
1484  *
1485  * @param stdClass $workshop the workshop instance record
1486  * @param int $cmid course module id
1487  */
1488 function workshop_calendar_update(stdClass $workshop, $cmid) {
1489     global $DB;
1491     // get the currently registered events so that we can re-use their ids
1492     $currentevents = $DB->get_records('event', array('modulename' => 'workshop', 'instance' => $workshop->id));
1494     // the common properties for all events
1495     $base = new stdClass();
1496     $base->description  = format_module_intro('workshop', $workshop, $cmid, false);
1497     $base->courseid     = $workshop->course;
1498     $base->groupid      = 0;
1499     $base->userid       = 0;
1500     $base->modulename   = 'workshop';
1501     $base->eventtype    = 'pluginname';
1502     $base->instance     = $workshop->id;
1503     $base->visible      = instance_is_visible('workshop', $workshop);
1504     $base->timeduration = 0;
1506     if ($workshop->submissionstart) {
1507         $event = clone($base);
1508         $event->name = get_string('submissionstartevent', 'mod_workshop', $workshop->name);
1509         $event->timestart = $workshop->submissionstart;
1510         if ($reusedevent = array_shift($currentevents)) {
1511             $event->id = $reusedevent->id;
1512         } else {
1513             // should not be set but just in case
1514             unset($event->id);
1515         }
1516         // update() will reuse a db record if the id field is set
1517         $eventobj = new calendar_event($event);
1518         $eventobj->update($event, false);
1519     }
1521     if ($workshop->submissionend) {
1522         $event = clone($base);
1523         $event->name = get_string('submissionendevent', 'mod_workshop', $workshop->name);
1524         $event->timestart = $workshop->submissionend;
1525         if ($reusedevent = array_shift($currentevents)) {
1526             $event->id = $reusedevent->id;
1527         } else {
1528             // should not be set but just in case
1529             unset($event->id);
1530         }
1531         // update() will reuse a db record if the id field is set
1532         $eventobj = new calendar_event($event);
1533         $eventobj->update($event, false);
1534     }
1536     if ($workshop->assessmentstart) {
1537         $event = clone($base);
1538         $event->name = get_string('assessmentstartevent', 'mod_workshop', $workshop->name);
1539         $event->timestart = $workshop->assessmentstart;
1540         if ($reusedevent = array_shift($currentevents)) {
1541             $event->id = $reusedevent->id;
1542         } else {
1543             // should not be set but just in case
1544             unset($event->id);
1545         }
1546         // update() will reuse a db record if the id field is set
1547         $eventobj = new calendar_event($event);
1548         $eventobj->update($event, false);
1549     }
1551     if ($workshop->assessmentend) {
1552         $event = clone($base);
1553         $event->name = get_string('assessmentendevent', 'mod_workshop', $workshop->name);
1554         $event->timestart = $workshop->assessmentend;
1555         if ($reusedevent = array_shift($currentevents)) {
1556             $event->id = $reusedevent->id;
1557         } else {
1558             // should not be set but just in case
1559             unset($event->id);
1560         }
1561         // update() will reuse a db record if the id field is set
1562         $eventobj = new calendar_event($event);
1563         $eventobj->update($event, false);
1564     }
1566     // delete any leftover events
1567     foreach ($currentevents as $oldevent) {
1568         $oldevent = calendar_event::load($oldevent);
1569         $oldevent->delete();
1570     }