616bc4e104fa94b6bfa9c696013428fc22dd743b
[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_GROUPMEMBERSONLY:  return true;
50         case FEATURE_MOD_INTRO:         return true;
51         case FEATURE_BACKUP_MOODLE2:    return true;
52         case FEATURE_COMPLETION_TRACKS_VIEWS:
53             return true;
54         case FEATURE_SHOW_DESCRIPTION:  return true;
55         case FEATURE_PLAGIARISM:        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     = 1;
80     $workshop->useselfassessment     = (int)!empty($workshop->useselfassessment);
81     $workshop->latesubmissions       = (int)!empty($workshop->latesubmissions);
82     $workshop->phaseswitchassessment = (int)!empty($workshop->phaseswitchassessment);
83     $workshop->evaluation            = 'best';
85     // insert the new record so we get the id
86     $workshop->id = $DB->insert_record('workshop', $workshop);
88     // we need to use context now, so we need to make sure all needed info is already in db
89     $cmid = $workshop->coursemodule;
90     $DB->set_field('course_modules', 'instance', $workshop->id, array('id' => $cmid));
91     $context = context_module::instance($cmid);
93     // process the custom wysiwyg editors
94     if ($draftitemid = $workshop->instructauthorseditor['itemid']) {
95         $workshop->instructauthors = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructauthors',
96                 0, workshop::instruction_editors_options($context), $workshop->instructauthorseditor['text']);
97         $workshop->instructauthorsformat = $workshop->instructauthorseditor['format'];
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     if ($draftitemid = $workshop->conclusioneditor['itemid']) {
107         $workshop->conclusion = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'conclusion',
108                 0, workshop::instruction_editors_options($context), $workshop->conclusioneditor['text']);
109         $workshop->conclusionformat = $workshop->conclusioneditor['format'];
110     }
112     // re-save the record with the replaced URLs in editor fields
113     $DB->update_record('workshop', $workshop);
115     // create gradebook items
116     workshop_grade_item_update($workshop);
117     workshop_grade_item_category_update($workshop);
119     // create calendar events
120     workshop_calendar_update($workshop, $workshop->coursemodule);
122     return $workshop->id;
125 /**
126  * Given an object containing all the necessary data,
127  * (defined by the form in mod_form.php) this function
128  * will update an existing instance with new data.
129  *
130  * @param stdClass $workshop An object from the form in mod_form.php
131  * @return bool success
132  */
133 function workshop_update_instance(stdclass $workshop) {
134     global $CFG, $DB;
135     require_once(dirname(__FILE__) . '/locallib.php');
137     $workshop->timemodified          = time();
138     $workshop->id                    = $workshop->instance;
139     $workshop->useexamples           = (int)!empty($workshop->useexamples);
140     $workshop->usepeerassessment     = 1;
141     $workshop->useselfassessment     = (int)!empty($workshop->useselfassessment);
142     $workshop->latesubmissions       = (int)!empty($workshop->latesubmissions);
143     $workshop->phaseswitchassessment = (int)!empty($workshop->phaseswitchassessment);
145     // todo - if the grading strategy is being changed, we may want to replace all aggregated peer grades with nulls
147     $DB->update_record('workshop', $workshop);
148     $context = context_module::instance($workshop->coursemodule);
150     // process the custom wysiwyg editors
151     if ($draftitemid = $workshop->instructauthorseditor['itemid']) {
152         $workshop->instructauthors = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructauthors',
153                 0, workshop::instruction_editors_options($context), $workshop->instructauthorseditor['text']);
154         $workshop->instructauthorsformat = $workshop->instructauthorseditor['format'];
155     }
157     if ($draftitemid = $workshop->instructreviewerseditor['itemid']) {
158         $workshop->instructreviewers = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructreviewers',
159                 0, workshop::instruction_editors_options($context), $workshop->instructreviewerseditor['text']);
160         $workshop->instructreviewersformat = $workshop->instructreviewerseditor['format'];
161     }
163     if ($draftitemid = $workshop->conclusioneditor['itemid']) {
164         $workshop->conclusion = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'conclusion',
165                 0, workshop::instruction_editors_options($context), $workshop->conclusioneditor['text']);
166         $workshop->conclusionformat = $workshop->conclusioneditor['format'];
167     }
169     // re-save the record with the replaced URLs in editor fields
170     $DB->update_record('workshop', $workshop);
172     // update gradebook items
173     workshop_grade_item_update($workshop);
174     workshop_grade_item_category_update($workshop);
176     // update calendar events
177     workshop_calendar_update($workshop, $workshop->coursemodule);
179     return true;
182 /**
183  * Given an ID of an instance of this module,
184  * this function will permanently delete the instance
185  * and any data that depends on it.
186  *
187  * @param int $id Id of the module instance
188  * @return boolean Success/Failure
189  */
190 function workshop_delete_instance($id) {
191     global $CFG, $DB;
192     require_once($CFG->libdir.'/gradelib.php');
194     if (! $workshop = $DB->get_record('workshop', array('id' => $id))) {
195         return false;
196     }
198     // delete all associated aggregations
199     $DB->delete_records('workshop_aggregations', array('workshopid' => $workshop->id));
201     // get the list of ids of all submissions
202     $submissions = $DB->get_records('workshop_submissions', array('workshopid' => $workshop->id), '', 'id');
204     // get the list of all allocated assessments
205     $assessments = $DB->get_records_list('workshop_assessments', 'submissionid', array_keys($submissions), '', 'id');
207     // delete the associated records from the workshop core tables
208     $DB->delete_records_list('workshop_grades', 'assessmentid', array_keys($assessments));
209     $DB->delete_records_list('workshop_assessments', 'id', array_keys($assessments));
210     $DB->delete_records_list('workshop_submissions', 'id', array_keys($submissions));
212     // call the static clean-up methods of all available subplugins
213     $strategies = core_component::get_plugin_list('workshopform');
214     foreach ($strategies as $strategy => $path) {
215         require_once($path.'/lib.php');
216         $classname = 'workshop_'.$strategy.'_strategy';
217         call_user_func($classname.'::delete_instance', $workshop->id);
218     }
220     $allocators = core_component::get_plugin_list('workshopallocation');
221     foreach ($allocators as $allocator => $path) {
222         require_once($path.'/lib.php');
223         $classname = 'workshop_'.$allocator.'_allocator';
224         call_user_func($classname.'::delete_instance', $workshop->id);
225     }
227     $evaluators = core_component::get_plugin_list('workshopeval');
228     foreach ($evaluators as $evaluator => $path) {
229         require_once($path.'/lib.php');
230         $classname = 'workshop_'.$evaluator.'_evaluation';
231         call_user_func($classname.'::delete_instance', $workshop->id);
232     }
234     // delete the calendar events
235     $events = $DB->get_records('event', array('modulename' => 'workshop', 'instance' => $workshop->id));
236     foreach ($events as $event) {
237         $event = calendar_event::load($event);
238         $event->delete();
239     }
241     // finally remove the workshop record itself
242     $DB->delete_records('workshop', array('id' => $workshop->id));
244     // gradebook cleanup
245     grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 0, null, array('deleted' => true));
246     grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 1, null, array('deleted' => true));
248     return true;
251 /**
252  * Return a small object with summary information about what a
253  * user has done with a given particular instance of this module
254  * Used for user activity reports.
255  * $return->time = the time they did it
256  * $return->info = a short text description
257  *
258  * @return stdclass|null
259  */
260 function workshop_user_outline($course, $user, $mod, $workshop) {
261     global $CFG, $DB;
262     require_once($CFG->libdir.'/gradelib.php');
264     $grades = grade_get_grades($course->id, 'mod', 'workshop', $workshop->id, $user->id);
266     $submissiongrade = null;
267     $assessmentgrade = null;
269     $info = '';
270     $time = 0;
272     if (!empty($grades->items[0]->grades)) {
273         $submissiongrade = reset($grades->items[0]->grades);
274         $info .= get_string('submissiongrade', 'workshop') . ': ' . $submissiongrade->str_long_grade . html_writer::empty_tag('br');
275         $time = max($time, $submissiongrade->dategraded);
276     }
277     if (!empty($grades->items[1]->grades)) {
278         $assessmentgrade = reset($grades->items[1]->grades);
279         $info .= get_string('gradinggrade', 'workshop') . ': ' . $assessmentgrade->str_long_grade;
280         $time = max($time, $assessmentgrade->dategraded);
281     }
283     if (!empty($info) and !empty($time)) {
284         $return = new stdclass();
285         $return->time = $time;
286         $return->info = $info;
287         return $return;
288     }
290     return null;
293 /**
294  * Print a detailed representation of what a user has done with
295  * a given particular instance of this module, for user activity reports.
296  *
297  * @return string HTML
298  */
299 function workshop_user_complete($course, $user, $mod, $workshop) {
300     global $CFG, $DB, $OUTPUT;
301     require_once(dirname(__FILE__).'/locallib.php');
302     require_once($CFG->libdir.'/gradelib.php');
304     $workshop   = new workshop($workshop, $mod, $course);
305     $grades     = grade_get_grades($course->id, 'mod', 'workshop', $workshop->id, $user->id);
307     if (!empty($grades->items[0]->grades)) {
308         $submissiongrade = reset($grades->items[0]->grades);
309         $info = get_string('submissiongrade', 'workshop') . ': ' . $submissiongrade->str_long_grade;
310         echo html_writer::tag('li', $info, array('class'=>'submissiongrade'));
311     }
312     if (!empty($grades->items[1]->grades)) {
313         $assessmentgrade = reset($grades->items[1]->grades);
314         $info = get_string('gradinggrade', 'workshop') . ': ' . $assessmentgrade->str_long_grade;
315         echo html_writer::tag('li', $info, array('class'=>'gradinggrade'));
316     }
318     if (has_capability('mod/workshop:viewallsubmissions', $workshop->context)) {
319         $canviewsubmission = true;
320         if (groups_get_activity_groupmode($workshop->cm) == SEPARATEGROUPS) {
321             // user must have accessallgroups or share at least one group with the submission author
322             if (!has_capability('moodle/site:accessallgroups', $workshop->context)) {
323                 $usersgroups = groups_get_activity_allowed_groups($workshop->cm);
324                 $authorsgroups = groups_get_all_groups($workshop->course->id, $user->id, $workshop->cm->groupingid, 'g.id');
325                 $sharedgroups = array_intersect_key($usersgroups, $authorsgroups);
326                 if (empty($sharedgroups)) {
327                     $canviewsubmission = false;
328                 }
329             }
330         }
331         if ($canviewsubmission and $submission = $workshop->get_submission_by_author($user->id)) {
332             $title      = format_string($submission->title);
333             $url        = $workshop->submission_url($submission->id);
334             $link       = html_writer::link($url, $title);
335             $info       = get_string('submission', 'workshop').': '.$link;
336             echo html_writer::tag('li', $info, array('class'=>'submission'));
337         }
338     }
340     if (has_capability('mod/workshop:viewallassessments', $workshop->context)) {
341         if ($assessments = $workshop->get_assessments_by_reviewer($user->id)) {
342             foreach ($assessments as $assessment) {
343                 $a = new stdclass();
344                 $a->submissionurl = $workshop->submission_url($assessment->submissionid)->out();
345                 $a->assessmenturl = $workshop->assess_url($assessment->id)->out();
346                 $a->submissiontitle = s($assessment->submissiontitle);
347                 echo html_writer::tag('li', get_string('assessmentofsubmission', 'workshop', $a));
348             }
349         }
350     }
353 /**
354  * Given a course and a time, this module should find recent activity
355  * that has occurred in workshop activities and print it out.
356  * Return true if there was output, or false is there was none.
357  *
358  * @param stdClass $course
359  * @param bool $viewfullnames
360  * @param int $timestart
361  * @return boolean
362  */
363 function workshop_print_recent_activity($course, $viewfullnames, $timestart) {
364     global $CFG, $USER, $DB, $OUTPUT;
366     $authoramefields = get_all_user_name_fields(true, 'author', null, 'author');
367     $reviewerfields = get_all_user_name_fields(true, 'reviewer', null, 'reviewer');
369     $sql = "SELECT s.id AS submissionid, s.title AS submissiontitle, s.timemodified AS submissionmodified,
370                    author.id AS authorid, $authoramefields, a.id AS assessmentid, a.timemodified AS assessmentmodified,
371                    reviewer.id AS reviewerid, $reviewerfields, cm.id AS cmid
372               FROM {workshop} w
373         INNER JOIN {course_modules} cm ON cm.instance = w.id
374         INNER JOIN {modules} md ON md.id = cm.module
375         INNER JOIN {workshop_submissions} s ON s.workshopid = w.id
376         INNER JOIN {user} author ON s.authorid = author.id
377          LEFT JOIN {workshop_assessments} a ON a.submissionid = s.id
378          LEFT JOIN {user} reviewer ON a.reviewerid = reviewer.id
379              WHERE cm.course = ?
380                    AND md.name = 'workshop'
381                    AND s.example = 0
382                    AND (s.timemodified > ? OR a.timemodified > ?)
383           ORDER BY s.timemodified";
385     $rs = $DB->get_recordset_sql($sql, array($course->id, $timestart, $timestart));
387     $modinfo = get_fast_modinfo($course); // reference needed because we might load the groups
389     $submissions = array(); // recent submissions indexed by submission id
390     $assessments = array(); // recent assessments indexed by assessment id
391     $users       = array();
393     foreach ($rs as $activity) {
394         if (!array_key_exists($activity->cmid, $modinfo->cms)) {
395             // this should not happen but just in case
396             continue;
397         }
399         $cm = $modinfo->cms[$activity->cmid];
400         if (!$cm->uservisible) {
401             continue;
402         }
404         // remember all user names we can use later
405         if (empty($users[$activity->authorid])) {
406             $u = new stdclass();
407             $users[$activity->authorid] = username_load_fields_from_object($u, $activity, 'author');
408         }
409         if ($activity->reviewerid and empty($users[$activity->reviewerid])) {
410             $u = new stdclass();
411             $users[$activity->reviewerid] = username_load_fields_from_object($u, $activity, 'reviewer');
412         }
414         $context = context_module::instance($cm->id);
415         $groupmode = groups_get_activity_groupmode($cm, $course);
417         if ($activity->submissionmodified > $timestart and empty($submissions[$activity->submissionid])) {
418             $s = new stdclass();
419             $s->title = $activity->submissiontitle;
420             $s->authorid = $activity->authorid;
421             $s->timemodified = $activity->submissionmodified;
422             $s->cmid = $activity->cmid;
423             if ($activity->authorid == $USER->id || has_capability('mod/workshop:viewauthornames', $context)) {
424                 $s->authornamevisible = true;
425             } else {
426                 $s->authornamevisible = false;
427             }
429             // the following do-while wrapper allows to break from deeply nested if-statements
430             do {
431                 if ($s->authorid === $USER->id) {
432                     // own submissions always visible
433                     $submissions[$activity->submissionid] = $s;
434                     break;
435                 }
437                 if (has_capability('mod/workshop:viewallsubmissions', $context)) {
438                     if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
439                         if (isguestuser()) {
440                             // shortcut - guest user does not belong into any group
441                             break;
442                         }
444                         // this might be slow - show only submissions by users who share group with me in this cm
445                         if (!$modinfo->get_groups($cm->groupingid)) {
446                             break;
447                         }
448                         $authorsgroups = groups_get_all_groups($course->id, $s->authorid, $cm->groupingid);
449                         if (is_array($authorsgroups)) {
450                             $authorsgroups = array_keys($authorsgroups);
451                             $intersect = array_intersect($authorsgroups, $modinfo->get_groups($cm->groupingid));
452                             if (empty($intersect)) {
453                                 break;
454                             } else {
455                                 // can see all submissions and shares a group with the author
456                                 $submissions[$activity->submissionid] = $s;
457                                 break;
458                             }
459                         }
461                     } else {
462                         // can see all submissions from all groups
463                         $submissions[$activity->submissionid] = $s;
464                     }
465                 }
466             } while (0);
467         }
469         if ($activity->assessmentmodified > $timestart and empty($assessments[$activity->assessmentid])) {
470             $a = new stdclass();
471             $a->submissionid = $activity->submissionid;
472             $a->submissiontitle = $activity->submissiontitle;
473             $a->reviewerid = $activity->reviewerid;
474             $a->timemodified = $activity->assessmentmodified;
475             $a->cmid = $activity->cmid;
476             if ($activity->reviewerid == $USER->id || has_capability('mod/workshop:viewreviewernames', $context)) {
477                 $a->reviewernamevisible = true;
478             } else {
479                 $a->reviewernamevisible = false;
480             }
482             // the following do-while wrapper allows to break from deeply nested if-statements
483             do {
484                 if ($a->reviewerid === $USER->id) {
485                     // own assessments always visible
486                     $assessments[$activity->assessmentid] = $a;
487                     break;
488                 }
490                 if (has_capability('mod/workshop:viewallassessments', $context)) {
491                     if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
492                         if (isguestuser()) {
493                             // shortcut - guest user does not belong into any group
494                             break;
495                         }
497                         // this might be slow - show only submissions by users who share group with me in this cm
498                         if (!$modinfo->get_groups($cm->groupingid)) {
499                             break;
500                         }
501                         $reviewersgroups = groups_get_all_groups($course->id, $a->reviewerid, $cm->groupingid);
502                         if (is_array($reviewersgroups)) {
503                             $reviewersgroups = array_keys($reviewersgroups);
504                             $intersect = array_intersect($reviewersgroups, $modinfo->get_groups($cm->groupingid));
505                             if (empty($intersect)) {
506                                 break;
507                             } else {
508                                 // can see all assessments and shares a group with the reviewer
509                                 $assessments[$activity->assessmentid] = $a;
510                                 break;
511                             }
512                         }
514                     } else {
515                         // can see all assessments from all groups
516                         $assessments[$activity->assessmentid] = $a;
517                     }
518                 }
519             } while (0);
520         }
521     }
522     $rs->close();
524     $shown = false;
526     if (!empty($submissions)) {
527         $shown = true;
528         echo $OUTPUT->heading(get_string('recentsubmissions', 'workshop'), 3);
529         foreach ($submissions as $id => $submission) {
530             $link = new moodle_url('/mod/workshop/submission.php', array('id'=>$id, 'cmid'=>$submission->cmid));
531             if ($submission->authornamevisible) {
532                 $author = $users[$submission->authorid];
533             } else {
534                 $author = null;
535             }
536             print_recent_activity_note($submission->timemodified, $author, $submission->title, $link->out(), false, $viewfullnames);
537         }
538     }
540     if (!empty($assessments)) {
541         $shown = true;
542         echo $OUTPUT->heading(get_string('recentassessments', 'workshop'), 3);
543         core_collator::asort_objects_by_property($assessments, 'timemodified');
544         foreach ($assessments as $id => $assessment) {
545             $link = new moodle_url('/mod/workshop/assessment.php', array('asid' => $id));
546             if ($assessment->reviewernamevisible) {
547                 $reviewer = $users[$assessment->reviewerid];
548             } else {
549                 $reviewer = null;
550             }
551             print_recent_activity_note($assessment->timemodified, $reviewer, $assessment->submissiontitle, $link->out(), false, $viewfullnames);
552         }
553     }
555     if ($shown) {
556         return true;
557     }
559     return false;
562 /**
563  * Returns all activity in course workshops since a given time
564  *
565  * @param array $activities sequentially indexed array of objects
566  * @param int $index
567  * @param int $timestart
568  * @param int $courseid
569  * @param int $cmid
570  * @param int $userid defaults to 0
571  * @param int $groupid defaults to 0
572  * @return void adds items into $activities and increases $index
573  */
574 function workshop_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid=0, $groupid=0) {
575     global $CFG, $COURSE, $USER, $DB;
577     if ($COURSE->id == $courseid) {
578         $course = $COURSE;
579     } else {
580         $course = $DB->get_record('course', array('id'=>$courseid));
581     }
583     $modinfo = get_fast_modinfo($course);
585     $cm = $modinfo->cms[$cmid];
587     $params = array();
588     if ($userid) {
589         $userselect = "AND (author.id = :authorid OR reviewer.id = :reviewerid)";
590         $params['authorid'] = $userid;
591         $params['reviewerid'] = $userid;
592     } else {
593         $userselect = "";
594     }
596     if ($groupid) {
597         $groupselect = "AND (authorgroupmembership.groupid = :authorgroupid OR reviewergroupmembership.groupid = :reviewergroupid)";
598         $groupjoin   = "LEFT JOIN {groups_members} authorgroupmembership ON authorgroupmembership.userid = author.id
599                         LEFT JOIN {groups_members} reviewergroupmembership ON reviewergroupmembership.userid = reviewer.id";
600         $params['authorgroupid'] = $groupid;
601         $params['reviewergroupid'] = $groupid;
602     } else {
603         $groupselect = "";
604         $groupjoin   = "";
605     }
607     $params['cminstance'] = $cm->instance;
608     $params['submissionmodified'] = $timestart;
609     $params['assessmentmodified'] = $timestart;
611     $authornamefields = get_all_user_name_fields(true, 'author', null, 'author');
612     $reviewerfields = get_all_user_name_fields(true, 'reviewer', null, 'reviewer');
614     $sql = "SELECT s.id AS submissionid, s.title AS submissiontitle, s.timemodified AS submissionmodified,
615                    author.id AS authorid, $authornamefields, author.picture AS authorpicture, author.imagealt AS authorimagealt,
616                    author.email AS authoremail, a.id AS assessmentid, a.timemodified AS assessmentmodified,
617                    reviewer.id AS reviewerid, $reviewerfields, reviewer.picture AS reviewerpicture,
618                    reviewer.imagealt AS reviewerimagealt, reviewer.email AS revieweremail
619               FROM {workshop_submissions} s
620         INNER JOIN {workshop} w ON s.workshopid = w.id
621         INNER JOIN {user} author ON s.authorid = author.id
622          LEFT JOIN {workshop_assessments} a ON a.submissionid = s.id
623          LEFT JOIN {user} reviewer ON a.reviewerid = reviewer.id
624         $groupjoin
625              WHERE w.id = :cminstance
626                    AND s.example = 0
627                    $userselect $groupselect
628                    AND (s.timemodified > :submissionmodified OR a.timemodified > :assessmentmodified)
629           ORDER BY s.timemodified ASC, a.timemodified ASC";
631     $rs = $DB->get_recordset_sql($sql, $params);
633     $groupmode       = groups_get_activity_groupmode($cm, $course);
634     $context         = context_module::instance($cm->id);
635     $grader          = has_capability('moodle/grade:viewall', $context);
636     $accessallgroups = has_capability('moodle/site:accessallgroups', $context);
637     $viewauthors     = has_capability('mod/workshop:viewauthornames', $context);
638     $viewreviewers   = has_capability('mod/workshop:viewreviewernames', $context);
640     $submissions = array(); // recent submissions indexed by submission id
641     $assessments = array(); // recent assessments indexed by assessment id
642     $users       = array();
644     foreach ($rs as $activity) {
646         // remember all user names we can use later
647         if (empty($users[$activity->authorid])) {
648             $u = new stdclass();
649             $additionalfields = explode(',', user_picture::fields());
650             $u = username_load_fields_from_object($u, $activity, 'author', $additionalfields);
651             $users[$activity->authorid] = $u;
652         }
653         if ($activity->reviewerid and empty($users[$activity->reviewerid])) {
654             $u = new stdclass();
655             $additionalfields = explode(',', user_picture::fields());
656             $u = username_load_fields_from_object($u, $activity, 'reviewer', $additionalfields);
657             $users[$activity->reviewerid] = $u;
658         }
660         if ($activity->submissionmodified > $timestart and empty($submissions[$activity->submissionid])) {
661             $s = new stdclass();
662             $s->id = $activity->submissionid;
663             $s->title = $activity->submissiontitle;
664             $s->authorid = $activity->authorid;
665             $s->timemodified = $activity->submissionmodified;
666             if ($activity->authorid == $USER->id || has_capability('mod/workshop:viewauthornames', $context)) {
667                 $s->authornamevisible = true;
668             } else {
669                 $s->authornamevisible = false;
670             }
672             // the following do-while wrapper allows to break from deeply nested if-statements
673             do {
674                 if ($s->authorid === $USER->id) {
675                     // own submissions always visible
676                     $submissions[$activity->submissionid] = $s;
677                     break;
678                 }
680                 if (has_capability('mod/workshop:viewallsubmissions', $context)) {
681                     if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
682                         if (isguestuser()) {
683                             // shortcut - guest user does not belong into any group
684                             break;
685                         }
687                         // this might be slow - show only submissions by users who share group with me in this cm
688                         if (!$modinfo->get_groups($cm->groupingid)) {
689                             break;
690                         }
691                         $authorsgroups = groups_get_all_groups($course->id, $s->authorid, $cm->groupingid);
692                         if (is_array($authorsgroups)) {
693                             $authorsgroups = array_keys($authorsgroups);
694                             $intersect = array_intersect($authorsgroups, $modinfo->get_groups($cm->groupingid));
695                             if (empty($intersect)) {
696                                 break;
697                             } else {
698                                 // can see all submissions and shares a group with the author
699                                 $submissions[$activity->submissionid] = $s;
700                                 break;
701                             }
702                         }
704                     } else {
705                         // can see all submissions from all groups
706                         $submissions[$activity->submissionid] = $s;
707                     }
708                 }
709             } while (0);
710         }
712         if ($activity->assessmentmodified > $timestart and empty($assessments[$activity->assessmentid])) {
713             $a = new stdclass();
714             $a->id = $activity->assessmentid;
715             $a->submissionid = $activity->submissionid;
716             $a->submissiontitle = $activity->submissiontitle;
717             $a->reviewerid = $activity->reviewerid;
718             $a->timemodified = $activity->assessmentmodified;
719             if ($activity->reviewerid == $USER->id || has_capability('mod/workshop:viewreviewernames', $context)) {
720                 $a->reviewernamevisible = true;
721             } else {
722                 $a->reviewernamevisible = false;
723             }
725             // the following do-while wrapper allows to break from deeply nested if-statements
726             do {
727                 if ($a->reviewerid === $USER->id) {
728                     // own assessments always visible
729                     $assessments[$activity->assessmentid] = $a;
730                     break;
731                 }
733                 if (has_capability('mod/workshop:viewallassessments', $context)) {
734                     if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
735                         if (isguestuser()) {
736                             // shortcut - guest user does not belong into any group
737                             break;
738                         }
740                         // this might be slow - show only submissions by users who share group with me in this cm
741                         if (!$modinfo->get_groups($cm->groupingid)) {
742                             break;
743                         }
744                         $reviewersgroups = groups_get_all_groups($course->id, $a->reviewerid, $cm->groupingid);
745                         if (is_array($reviewersgroups)) {
746                             $reviewersgroups = array_keys($reviewersgroups);
747                             $intersect = array_intersect($reviewersgroups, $modinfo->get_groups($cm->groupingid));
748                             if (empty($intersect)) {
749                                 break;
750                             } else {
751                                 // can see all assessments and shares a group with the reviewer
752                                 $assessments[$activity->assessmentid] = $a;
753                                 break;
754                             }
755                         }
757                     } else {
758                         // can see all assessments from all groups
759                         $assessments[$activity->assessmentid] = $a;
760                     }
761                 }
762             } while (0);
763         }
764     }
765     $rs->close();
767     $workshopname = format_string($cm->name, true);
769     if ($grader) {
770         require_once($CFG->libdir.'/gradelib.php');
771         $grades = grade_get_grades($courseid, 'mod', 'workshop', $cm->instance, array_keys($users));
772     }
774     foreach ($submissions as $submission) {
775         $tmpactivity                = new stdclass();
776         $tmpactivity->type          = 'workshop';
777         $tmpactivity->cmid          = $cm->id;
778         $tmpactivity->name          = $workshopname;
779         $tmpactivity->sectionnum    = $cm->sectionnum;
780         $tmpactivity->timestamp     = $submission->timemodified;
781         $tmpactivity->subtype       = 'submission';
782         $tmpactivity->content       = $submission;
783         if ($grader) {
784             $tmpactivity->grade     = $grades->items[0]->grades[$submission->authorid]->str_long_grade;
785         }
786         if ($submission->authornamevisible and !empty($users[$submission->authorid])) {
787             $tmpactivity->user      = $users[$submission->authorid];
788         }
789         $activities[$index++]       = $tmpactivity;
790     }
792     foreach ($assessments as $assessment) {
793         $tmpactivity                = new stdclass();
794         $tmpactivity->type          = 'workshop';
795         $tmpactivity->cmid          = $cm->id;
796         $tmpactivity->name          = $workshopname;
797         $tmpactivity->sectionnum    = $cm->sectionnum;
798         $tmpactivity->timestamp     = $assessment->timemodified;
799         $tmpactivity->subtype       = 'assessment';
800         $tmpactivity->content       = $assessment;
801         if ($grader) {
802             $tmpactivity->grade     = $grades->items[1]->grades[$assessment->reviewerid]->str_long_grade;
803         }
804         if ($assessment->reviewernamevisible and !empty($users[$assessment->reviewerid])) {
805             $tmpactivity->user      = $users[$assessment->reviewerid];
806         }
807         $activities[$index++]       = $tmpactivity;
808     }
811 /**
812  * Print single activity item prepared by {@see workshop_get_recent_mod_activity()}
813  */
814 function workshop_print_recent_mod_activity($activity, $courseid, $detail, $modnames, $viewfullnames) {
815     global $CFG, $OUTPUT;
817     if (!empty($activity->user)) {
818         echo html_writer::tag('div', $OUTPUT->user_picture($activity->user, array('courseid'=>$courseid)),
819                 array('style' => 'float: left; padding: 7px;'));
820     }
822     if ($activity->subtype == 'submission') {
823         echo html_writer::start_tag('div', array('class'=>'submission', 'style'=>'padding: 7px; float:left;'));
825         if ($detail) {
826             echo html_writer::start_tag('h4', array('class'=>'workshop'));
827             $url = new moodle_url('/mod/workshop/view.php', array('id'=>$activity->cmid));
828             $name = s($activity->name);
829             echo html_writer::empty_tag('img', array('src'=>$OUTPUT->pix_url('icon', $activity->type), 'class'=>'icon', 'alt'=>$name));
830             echo ' ' . $modnames[$activity->type];
831             echo html_writer::link($url, $name, array('class'=>'name', 'style'=>'margin-left: 5px'));
832             echo html_writer::end_tag('h4');
833         }
835         echo html_writer::start_tag('div', array('class'=>'title'));
836         $url = new moodle_url('/mod/workshop/submission.php', array('cmid'=>$activity->cmid, 'id'=>$activity->content->id));
837         $name = s($activity->content->title);
838         echo html_writer::tag('strong', html_writer::link($url, $name));
839         echo html_writer::end_tag('div');
841         if (!empty($activity->user)) {
842             echo html_writer::start_tag('div', array('class'=>'user'));
843             $url = new moodle_url('/user/view.php', array('id'=>$activity->user->id, 'course'=>$courseid));
844             $name = fullname($activity->user);
845             $link = html_writer::link($url, $name);
846             echo get_string('submissionby', 'workshop', $link);
847             echo ' - '.userdate($activity->timestamp);
848             echo html_writer::end_tag('div');
849         } else {
850             echo html_writer::start_tag('div', array('class'=>'anonymous'));
851             echo get_string('submission', 'workshop');
852             echo ' - '.userdate($activity->timestamp);
853             echo html_writer::end_tag('div');
854         }
856         echo html_writer::end_tag('div');
857     }
859     if ($activity->subtype == 'assessment') {
860         echo html_writer::start_tag('div', array('class'=>'assessment', 'style'=>'padding: 7px; float:left;'));
862         if ($detail) {
863             echo html_writer::start_tag('h4', array('class'=>'workshop'));
864             $url = new moodle_url('/mod/workshop/view.php', array('id'=>$activity->cmid));
865             $name = s($activity->name);
866             echo html_writer::empty_tag('img', array('src'=>$OUTPUT->pix_url('icon', $activity->type), 'class'=>'icon', 'alt'=>$name));
867             echo ' ' . $modnames[$activity->type];
868             echo html_writer::link($url, $name, array('class'=>'name', 'style'=>'margin-left: 5px'));
869             echo html_writer::end_tag('h4');
870         }
872         echo html_writer::start_tag('div', array('class'=>'title'));
873         $url = new moodle_url('/mod/workshop/assessment.php', array('asid'=>$activity->content->id));
874         $name = s($activity->content->submissiontitle);
875         echo html_writer::tag('em', html_writer::link($url, $name));
876         echo html_writer::end_tag('div');
878         if (!empty($activity->user)) {
879             echo html_writer::start_tag('div', array('class'=>'user'));
880             $url = new moodle_url('/user/view.php', array('id'=>$activity->user->id, 'course'=>$courseid));
881             $name = fullname($activity->user);
882             $link = html_writer::link($url, $name);
883             echo get_string('assessmentbyfullname', 'workshop', $link);
884             echo ' - '.userdate($activity->timestamp);
885             echo html_writer::end_tag('div');
886         } else {
887             echo html_writer::start_tag('div', array('class'=>'anonymous'));
888             echo get_string('assessment', 'workshop');
889             echo ' - '.userdate($activity->timestamp);
890             echo html_writer::end_tag('div');
891         }
893         echo html_writer::end_tag('div');
894     }
896     echo html_writer::empty_tag('br', array('style'=>'clear:both'));
899 /**
900  * Regular jobs to execute via cron
901  *
902  * @return boolean true on success, false otherwise
903  */
904 function workshop_cron() {
905     global $CFG, $DB;
907     $now = time();
909     mtrace(' processing workshop subplugins ...');
910     cron_execute_plugin_type('workshopallocation', 'workshop allocation methods');
912     // now when the scheduled allocator had a chance to do its job, check if there
913     // are some workshops to switch into the assessment phase
914     $workshops = $DB->get_records_select("workshop",
915         "phase = 20 AND phaseswitchassessment = 1 AND submissionend > 0 AND submissionend < ?", array($now));
917     if (!empty($workshops)) {
918         mtrace('Processing automatic assessment phase switch in '.count($workshops).' workshop(s) ... ', '');
919         require_once($CFG->dirroot.'/mod/workshop/locallib.php');
920         foreach ($workshops as $workshop) {
921             $cm = get_coursemodule_from_instance('workshop', $workshop->id, $workshop->course, false, MUST_EXIST);
922             $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
923             $workshop = new workshop($workshop, $cm, $course);
924             $workshop->switch_phase(workshop::PHASE_ASSESSMENT);
926             $params = array(
927                 'objectid' => $workshop->id,
928                 'context' => $workshop->context,
929                 'courseid' => $workshop->course->id,
930                 'other' => array(
931                     'workshopphase' => $workshop->phase
932                 )
933             );
934             $event = \mod_workshop\event\phase_switched::create($params);
935             $event->trigger();
937             // disable the automatic switching now so that it is not executed again by accident
938             // if the teacher changes the phase back to the submission one
939             $DB->set_field('workshop', 'phaseswitchassessment', 0, array('id' => $workshop->id));
941             // todo inform the teachers
942         }
943         mtrace('done');
944     }
946     return true;
949 /**
950  * Is a given scale used by the instance of workshop?
951  *
952  * The function asks all installed grading strategy subplugins. The workshop
953  * core itself does not use scales. Both grade for submission and grade for
954  * assessments do not use scales.
955  *
956  * @param int $workshopid id of workshop instance
957  * @param int $scaleid id of the scale to check
958  * @return bool
959  */
960 function workshop_scale_used($workshopid, $scaleid) {
961     global $CFG; // other files included from here
963     $strategies = core_component::get_plugin_list('workshopform');
964     foreach ($strategies as $strategy => $strategypath) {
965         $strategylib = $strategypath . '/lib.php';
966         if (is_readable($strategylib)) {
967             require_once($strategylib);
968         } else {
969             throw new coding_exception('the grading forms subplugin must contain library ' . $strategylib);
970         }
971         $classname = 'workshop_' . $strategy . '_strategy';
972         if (method_exists($classname, 'scale_used')) {
973             if (call_user_func_array(array($classname, 'scale_used'), array($scaleid, $workshopid))) {
974                 // no need to include any other files - scale is used
975                 return true;
976             }
977         }
978     }
980     return false;
983 /**
984  * Is a given scale used by any instance of workshop?
985  *
986  * The function asks all installed grading strategy subplugins. The workshop
987  * core itself does not use scales. Both grade for submission and grade for
988  * assessments do not use scales.
989  *
990  * @param int $scaleid id of the scale to check
991  * @return bool
992  */
993 function workshop_scale_used_anywhere($scaleid) {
994     global $CFG; // other files included from here
996     $strategies = core_component::get_plugin_list('workshopform');
997     foreach ($strategies as $strategy => $strategypath) {
998         $strategylib = $strategypath . '/lib.php';
999         if (is_readable($strategylib)) {
1000             require_once($strategylib);
1001         } else {
1002             throw new coding_exception('the grading forms subplugin must contain library ' . $strategylib);
1003         }
1004         $classname = 'workshop_' . $strategy . '_strategy';
1005         if (method_exists($classname, 'scale_used')) {
1006             if (call_user_func(array($classname, 'scale_used'), $scaleid)) {
1007                 // no need to include any other files - scale is used
1008                 return true;
1009             }
1010         }
1011     }
1013     return false;
1016 /**
1017  * Returns all other caps used in the module
1018  *
1019  * @return array
1020  */
1021 function workshop_get_extra_capabilities() {
1022     return array('moodle/site:accessallgroups');
1025 ////////////////////////////////////////////////////////////////////////////////
1026 // Gradebook API                                                              //
1027 ////////////////////////////////////////////////////////////////////////////////
1029 /**
1030  * Creates or updates grade items for the give workshop instance
1031  *
1032  * Needed by grade_update_mod_grades() in lib/gradelib.php. Also used by
1033  * {@link workshop_update_grades()}.
1034  *
1035  * @param stdClass $workshop instance object with extra cmidnumber property
1036  * @param stdClass $submissiongrades data for the first grade item
1037  * @param stdClass $assessmentgrades data for the second grade item
1038  * @return void
1039  */
1040 function workshop_grade_item_update(stdclass $workshop, $submissiongrades=null, $assessmentgrades=null) {
1041     global $CFG;
1042     require_once($CFG->libdir.'/gradelib.php');
1044     $a = new stdclass();
1045     $a->workshopname = clean_param($workshop->name, PARAM_NOTAGS);
1047     $item = array();
1048     $item['itemname'] = get_string('gradeitemsubmission', 'workshop', $a);
1049     $item['gradetype'] = GRADE_TYPE_VALUE;
1050     $item['grademax']  = $workshop->grade;
1051     $item['grademin']  = 0;
1052     grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 0, $submissiongrades , $item);
1054     $item = array();
1055     $item['itemname'] = get_string('gradeitemassessment', 'workshop', $a);
1056     $item['gradetype'] = GRADE_TYPE_VALUE;
1057     $item['grademax']  = $workshop->gradinggrade;
1058     $item['grademin']  = 0;
1059     grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 1, $assessmentgrades, $item);
1062 /**
1063  * Update workshop grades in the gradebook
1064  *
1065  * Needed by grade_update_mod_grades() in lib/gradelib.php
1066  *
1067  * @category grade
1068  * @param stdClass $workshop instance object with extra cmidnumber and modname property
1069  * @param int $userid        update grade of specific user only, 0 means all participants
1070  * @return void
1071  */
1072 function workshop_update_grades(stdclass $workshop, $userid=0) {
1073     global $CFG, $DB;
1074     require_once($CFG->libdir.'/gradelib.php');
1076     $whereuser = $userid ? ' AND authorid = :userid' : '';
1077     $params = array('workshopid' => $workshop->id, 'userid' => $userid);
1078     $sql = 'SELECT authorid, grade, gradeover, gradeoverby, feedbackauthor, feedbackauthorformat, timemodified, timegraded
1079               FROM {workshop_submissions}
1080              WHERE workshopid = :workshopid AND example=0' . $whereuser;
1081     $records = $DB->get_records_sql($sql, $params);
1082     $submissiongrades = array();
1083     foreach ($records as $record) {
1084         $grade = new stdclass();
1085         $grade->userid = $record->authorid;
1086         if (!is_null($record->gradeover)) {
1087             $grade->rawgrade = grade_floatval($workshop->grade * $record->gradeover / 100);
1088             $grade->usermodified = $record->gradeoverby;
1089         } else {
1090             $grade->rawgrade = grade_floatval($workshop->grade * $record->grade / 100);
1091         }
1092         $grade->feedback = $record->feedbackauthor;
1093         $grade->feedbackformat = $record->feedbackauthorformat;
1094         $grade->datesubmitted = $record->timemodified;
1095         $grade->dategraded = $record->timegraded;
1096         $submissiongrades[$record->authorid] = $grade;
1097     }
1099     $whereuser = $userid ? ' AND userid = :userid' : '';
1100     $params = array('workshopid' => $workshop->id, 'userid' => $userid);
1101     $sql = 'SELECT userid, gradinggrade, timegraded
1102               FROM {workshop_aggregations}
1103              WHERE workshopid = :workshopid' . $whereuser;
1104     $records = $DB->get_records_sql($sql, $params);
1105     $assessmentgrades = array();
1106     foreach ($records as $record) {
1107         $grade = new stdclass();
1108         $grade->userid = $record->userid;
1109         $grade->rawgrade = grade_floatval($workshop->gradinggrade * $record->gradinggrade / 100);
1110         $grade->dategraded = $record->timegraded;
1111         $assessmentgrades[$record->userid] = $grade;
1112     }
1114     workshop_grade_item_update($workshop, $submissiongrades, $assessmentgrades);
1117 /**
1118  * Update the grade items categories if they are changed via mod_form.php
1119  *
1120  * We must do it manually here in the workshop module because modedit supports only
1121  * single grade item while we use two.
1122  *
1123  * @param stdClass $workshop An object from the form in mod_form.php
1124  */
1125 function workshop_grade_item_category_update($workshop) {
1127     $gradeitems = grade_item::fetch_all(array(
1128         'itemtype'      => 'mod',
1129         'itemmodule'    => 'workshop',
1130         'iteminstance'  => $workshop->id,
1131         'courseid'      => $workshop->course));
1133     if (!empty($gradeitems)) {
1134         foreach ($gradeitems as $gradeitem) {
1135             if ($gradeitem->itemnumber == 0) {
1136                 if ($gradeitem->categoryid != $workshop->gradecategory) {
1137                     $gradeitem->set_parent($workshop->gradecategory);
1138                 }
1139             } else if ($gradeitem->itemnumber == 1) {
1140                 if ($gradeitem->categoryid != $workshop->gradinggradecategory) {
1141                     $gradeitem->set_parent($workshop->gradinggradecategory);
1142                 }
1143             }
1144         }
1145     }
1148 ////////////////////////////////////////////////////////////////////////////////
1149 // File API                                                                   //
1150 ////////////////////////////////////////////////////////////////////////////////
1152 /**
1153  * Returns the lists of all browsable file areas within the given module context
1154  *
1155  * The file area workshop_intro for the activity introduction field is added automatically
1156  * by {@link file_browser::get_file_info_context_module()}
1157  *
1158  * @package  mod_workshop
1159  * @category files
1160  *
1161  * @param stdClass $course
1162  * @param stdClass $cm
1163  * @param stdClass $context
1164  * @return array of [(string)filearea] => (string)description
1165  */
1166 function workshop_get_file_areas($course, $cm, $context) {
1167     $areas = array();
1168     $areas['instructauthors']          = get_string('areainstructauthors', 'workshop');
1169     $areas['instructreviewers']        = get_string('areainstructreviewers', 'workshop');
1170     $areas['submission_content']       = get_string('areasubmissioncontent', 'workshop');
1171     $areas['submission_attachment']    = get_string('areasubmissionattachment', 'workshop');
1172     $areas['conclusion']               = get_string('areaconclusion', 'workshop');
1173     $areas['overallfeedback_content']  = get_string('areaoverallfeedbackcontent', 'workshop');
1174     $areas['overallfeedback_attachment'] = get_string('areaoverallfeedbackattachment', 'workshop');
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, instructreviewers and conclusion 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         // finally send the file
1220         send_stored_file($file, null, 0, $forcedownload, $options);
1222     } else if ($filearea === 'instructreviewers') {
1223         array_shift($args); // itemid is ignored here
1224         $relativepath = implode('/', $args);
1225         $fullpath = "/$context->id/mod_workshop/$filearea/0/$relativepath";
1227         $fs = get_file_storage();
1228         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1229             send_file_not_found();
1230         }
1232         // finally send the file
1233         send_stored_file($file, null, 0, $forcedownload, $options);
1235     } else if ($filearea === 'conclusion') {
1236         array_shift($args); // itemid is ignored here
1237         $relativepath = implode('/', $args);
1238         $fullpath = "/$context->id/mod_workshop/$filearea/0/$relativepath";
1240         $fs = get_file_storage();
1241         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1242             send_file_not_found();
1243         }
1245         // finally send the file
1246         send_stored_file($file, null, 0, $forcedownload, $options);
1248     } else if ($filearea === 'submission_content' or $filearea === 'submission_attachment') {
1249         $itemid = (int)array_shift($args);
1250         if (!$workshop = $DB->get_record('workshop', array('id' => $cm->instance))) {
1251             return false;
1252         }
1253         if (!$submission = $DB->get_record('workshop_submissions', array('id' => $itemid, 'workshopid' => $workshop->id))) {
1254             return false;
1255         }
1257         // make sure the user is allowed to see the file
1258         if (empty($submission->example)) {
1259             if ($USER->id != $submission->authorid) {
1260                 if ($submission->published == 1 and $workshop->phase == 50
1261                         and has_capability('mod/workshop:viewpublishedsubmissions', $context)) {
1262                     // Published submission, we can go (workshop does not take the group mode
1263                     // into account in this case yet).
1264                 } else if (!$DB->record_exists('workshop_assessments', array('submissionid' => $submission->id, 'reviewerid' => $USER->id))) {
1265                     if (!has_capability('mod/workshop:viewallsubmissions', $context)) {
1266                         send_file_not_found();
1267                     } else {
1268                         $gmode = groups_get_activity_groupmode($cm, $course);
1269                         if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1270                             // check there is at least one common group with both the $USER
1271                             // and the submission author
1272                             $sql = "SELECT 'x'
1273                                       FROM {workshop_submissions} s
1274                                       JOIN {user} a ON (a.id = s.authorid)
1275                                       JOIN {groups_members} agm ON (a.id = agm.userid)
1276                                       JOIN {user} u ON (u.id = ?)
1277                                       JOIN {groups_members} ugm ON (u.id = ugm.userid)
1278                                      WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1279                             $params = array($USER->id, $workshop->id, $submission->id);
1280                             if (!$DB->record_exists_sql($sql, $params)) {
1281                                 send_file_not_found();
1282                             }
1283                         }
1284                     }
1285                 }
1286             }
1287         }
1289         $fs = get_file_storage();
1290         $relativepath = implode('/', $args);
1291         $fullpath = "/$context->id/mod_workshop/$filearea/$itemid/$relativepath";
1292         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1293             return false;
1294         }
1295         // finally send the file
1296         // these files are uploaded by students - forcing download for security reasons
1297         send_stored_file($file, 0, 0, true, $options);
1299     } else if ($filearea === 'overallfeedback_content' or $filearea === 'overallfeedback_attachment') {
1300         $itemid = (int)array_shift($args);
1301         if (!$workshop = $DB->get_record('workshop', array('id' => $cm->instance))) {
1302             return false;
1303         }
1304         if (!$assessment = $DB->get_record('workshop_assessments', array('id' => $itemid))) {
1305             return false;
1306         }
1307         if (!$submission = $DB->get_record('workshop_submissions', array('id' => $assessment->submissionid, 'workshopid' => $workshop->id))) {
1308             return false;
1309         }
1311         if ($USER->id == $assessment->reviewerid) {
1312             // Reviewers can always see their own files.
1313         } else if ($USER->id == $submission->authorid and $workshop->phase == 50) {
1314             // Authors can see the feedback once the workshop is closed.
1315         } else if (!empty($submission->example) and $assessment->weight == 1) {
1316             // Reference assessments of example submissions can be displayed.
1317         } else if (!has_capability('mod/workshop:viewallassessments', $context)) {
1318             send_file_not_found();
1319         } else {
1320             $gmode = groups_get_activity_groupmode($cm, $course);
1321             if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1322                 // Check there is at least one common group with both the $USER
1323                 // and the submission author.
1324                 $sql = "SELECT 'x'
1325                           FROM {workshop_submissions} s
1326                           JOIN {user} a ON (a.id = s.authorid)
1327                           JOIN {groups_members} agm ON (a.id = agm.userid)
1328                           JOIN {user} u ON (u.id = ?)
1329                           JOIN {groups_members} ugm ON (u.id = ugm.userid)
1330                          WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1331                 $params = array($USER->id, $workshop->id, $submission->id);
1332                 if (!$DB->record_exists_sql($sql, $params)) {
1333                     send_file_not_found();
1334                 }
1335             }
1336         }
1338         $fs = get_file_storage();
1339         $relativepath = implode('/', $args);
1340         $fullpath = "/$context->id/mod_workshop/$filearea/$itemid/$relativepath";
1341         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1342             return false;
1343         }
1344         // finally send the file
1345         // these files are uploaded by students - forcing download for security reasons
1346         send_stored_file($file, 0, 0, true, $options);
1347     }
1349     return false;
1352 /**
1353  * File browsing support for workshop file areas
1354  *
1355  * @package  mod_workshop
1356  * @category files
1357  *
1358  * @param file_browser $browser
1359  * @param array $areas
1360  * @param stdClass $course
1361  * @param stdClass $cm
1362  * @param stdClass $context
1363  * @param string $filearea
1364  * @param int $itemid
1365  * @param string $filepath
1366  * @param string $filename
1367  * @return file_info instance or null if not found
1368  */
1369 function workshop_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
1370     global $CFG, $DB, $USER;
1372     /** @var array internal cache for author names */
1373     static $submissionauthors = array();
1375     $fs = get_file_storage();
1377     if ($filearea === 'submission_content' or $filearea === 'submission_attachment') {
1379         if (!has_capability('mod/workshop:viewallsubmissions', $context)) {
1380             return null;
1381         }
1383         if (is_null($itemid)) {
1384             // no itemid (submissionid) passed, display the list of all submissions
1385             require_once($CFG->dirroot . '/mod/workshop/fileinfolib.php');
1386             return new workshop_file_info_submissions_container($browser, $course, $cm, $context, $areas, $filearea);
1387         }
1389         // make sure the user can see the particular submission in separate groups mode
1390         $gmode = groups_get_activity_groupmode($cm, $course);
1392         if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1393             // check there is at least one common group with both the $USER
1394             // and the submission author (this is not expected to be a frequent
1395             // usecase so we can live with pretty ineffective one query per submission here...)
1396             $sql = "SELECT 'x'
1397                       FROM {workshop_submissions} s
1398                       JOIN {user} a ON (a.id = s.authorid)
1399                       JOIN {groups_members} agm ON (a.id = agm.userid)
1400                       JOIN {user} u ON (u.id = ?)
1401                       JOIN {groups_members} ugm ON (u.id = ugm.userid)
1402                      WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1403             $params = array($USER->id, $cm->instance, $itemid);
1404             if (!$DB->record_exists_sql($sql, $params)) {
1405                 return null;
1406             }
1407         }
1409         // we are inside some particular submission container
1411         $filepath = is_null($filepath) ? '/' : $filepath;
1412         $filename = is_null($filename) ? '.' : $filename;
1414         if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, $itemid, $filepath, $filename)) {
1415             if ($filepath === '/' and $filename === '.') {
1416                 $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, $itemid);
1417             } else {
1418                 // not found
1419                 return null;
1420             }
1421         }
1423         // Checks to see if the user can manage files or is the owner.
1424         // TODO MDL-33805 - Do not use userid here and move the capability check above.
1425         if (!has_capability('moodle/course:managefiles', $context) && $storedfile->get_userid() != $USER->id) {
1426             return null;
1427         }
1429         // let us display the author's name instead of itemid (submission id)
1431         if (isset($submissionauthors[$itemid])) {
1432             $topvisiblename = $submissionauthors[$itemid];
1434         } else {
1436             $sql = "SELECT s.id, u.lastname, u.firstname
1437                       FROM {workshop_submissions} s
1438                       JOIN {user} u ON (s.authorid = u.id)
1439                      WHERE s.example = 0 AND s.workshopid = ?";
1440             $params = array($cm->instance);
1441             $rs = $DB->get_recordset_sql($sql, $params);
1443             foreach ($rs as $submissionauthor) {
1444                 $title = s(fullname($submissionauthor)); // this is generally not unique...
1445                 $submissionauthors[$submissionauthor->id] = $title;
1446             }
1447             $rs->close();
1449             if (!isset($submissionauthors[$itemid])) {
1450                 // should not happen
1451                 return null;
1452             } else {
1453                 $topvisiblename = $submissionauthors[$itemid];
1454             }
1455         }
1457         $urlbase = $CFG->wwwroot . '/pluginfile.php';
1458         // do not allow manual modification of any files!
1459         return new file_info_stored($browser, $context, $storedfile, $urlbase, $topvisiblename, true, true, false, false);
1460     }
1462     if ($filearea === 'overallfeedback_content' or $filearea === 'overallfeedback_attachment') {
1464         if (!has_capability('mod/workshop:viewallassessments', $context)) {
1465             return null;
1466         }
1468         if (is_null($itemid)) {
1469             // No itemid (assessmentid) passed, display the list of all assessments.
1470             require_once($CFG->dirroot . '/mod/workshop/fileinfolib.php');
1471             return new workshop_file_info_overallfeedback_container($browser, $course, $cm, $context, $areas, $filearea);
1472         }
1474         // Make sure the user can see the particular assessment in separate groups mode.
1475         $gmode = groups_get_activity_groupmode($cm, $course);
1476         if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1477             // Check there is at least one common group with both the $USER
1478             // and the submission author.
1479             $sql = "SELECT 'x'
1480                       FROM {workshop_submissions} s
1481                       JOIN {user} a ON (a.id = s.authorid)
1482                       JOIN {groups_members} agm ON (a.id = agm.userid)
1483                       JOIN {user} u ON (u.id = ?)
1484                       JOIN {groups_members} ugm ON (u.id = ugm.userid)
1485                      WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1486             $params = array($USER->id, $cm->instance, $itemid);
1487             if (!$DB->record_exists_sql($sql, $params)) {
1488                 return null;
1489             }
1490         }
1492         // We are inside a particular assessment container.
1493         $filepath = is_null($filepath) ? '/' : $filepath;
1494         $filename = is_null($filename) ? '.' : $filename;
1496         if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, $itemid, $filepath, $filename)) {
1497             if ($filepath === '/' and $filename === '.') {
1498                 $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, $itemid);
1499             } else {
1500                 // Not found
1501                 return null;
1502             }
1503         }
1505         // Check to see if the user can manage files or is the owner.
1506         if (!has_capability('moodle/course:managefiles', $context) and $storedfile->get_userid() != $USER->id) {
1507             return null;
1508         }
1510         $urlbase = $CFG->wwwroot . '/pluginfile.php';
1512         // Do not allow manual modification of any files.
1513         return new file_info_stored($browser, $context, $storedfile, $urlbase, $itemid, true, true, false, false);
1514     }
1516     if ($filearea == 'instructauthors' or $filearea == 'instructreviewers' or $filearea == 'conclusion') {
1517         // always only itemid 0
1519         $filepath = is_null($filepath) ? '/' : $filepath;
1520         $filename = is_null($filename) ? '.' : $filename;
1522         $urlbase = $CFG->wwwroot.'/pluginfile.php';
1523         if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, 0, $filepath, $filename)) {
1524             if ($filepath === '/' and $filename === '.') {
1525                 $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, 0);
1526             } else {
1527                 // not found
1528                 return null;
1529             }
1530         }
1531         return new file_info_stored($browser, $context, $storedfile, $urlbase, $areas[$filearea], false, true, true, false);
1532     }
1535 ////////////////////////////////////////////////////////////////////////////////
1536 // Navigation API                                                             //
1537 ////////////////////////////////////////////////////////////////////////////////
1539 /**
1540  * Extends the global navigation tree by adding workshop nodes if there is a relevant content
1541  *
1542  * This can be called by an AJAX request so do not rely on $PAGE as it might not be set up properly.
1543  *
1544  * @param navigation_node $navref An object representing the navigation tree node of the workshop module instance
1545  * @param stdClass $course
1546  * @param stdClass $module
1547  * @param cm_info $cm
1548  */
1549 function workshop_extend_navigation(navigation_node $navref, stdclass $course, stdclass $module, cm_info $cm) {
1550     global $CFG;
1552     if (has_capability('mod/workshop:submit', context_module::instance($cm->id))) {
1553         $url = new moodle_url('/mod/workshop/submission.php', array('cmid' => $cm->id));
1554         $mysubmission = $navref->add(get_string('mysubmission', 'workshop'), $url);
1555         $mysubmission->mainnavonly = true;
1556     }
1559 /**
1560  * Extends the settings navigation with the Workshop settings
1562  * This function is called when the context for the page is a workshop module. This is not called by AJAX
1563  * so it is safe to rely on the $PAGE.
1564  *
1565  * @param settings_navigation $settingsnav {@link settings_navigation}
1566  * @param navigation_node $workshopnode {@link navigation_node}
1567  */
1568 function workshop_extend_settings_navigation(settings_navigation $settingsnav, navigation_node $workshopnode=null) {
1569     global $PAGE;
1571     //$workshopobject = $DB->get_record("workshop", array("id" => $PAGE->cm->instance));
1573     if (has_capability('mod/workshop:editdimensions', $PAGE->cm->context)) {
1574         $url = new moodle_url('/mod/workshop/editform.php', array('cmid' => $PAGE->cm->id));
1575         $workshopnode->add(get_string('editassessmentform', 'workshop'), $url, settings_navigation::TYPE_SETTING);
1576     }
1577     if (has_capability('mod/workshop:allocate', $PAGE->cm->context)) {
1578         $url = new moodle_url('/mod/workshop/allocation.php', array('cmid' => $PAGE->cm->id));
1579         $workshopnode->add(get_string('allocate', 'workshop'), $url, settings_navigation::TYPE_SETTING);
1580     }
1583 /**
1584  * Return a list of page types
1585  * @param string $pagetype current page type
1586  * @param stdClass $parentcontext Block's parent context
1587  * @param stdClass $currentcontext Current context of block
1588  */
1589 function workshop_page_type_list($pagetype, $parentcontext, $currentcontext) {
1590     $module_pagetype = array('mod-workshop-*'=>get_string('page-mod-workshop-x', 'workshop'));
1591     return $module_pagetype;
1594 ////////////////////////////////////////////////////////////////////////////////
1595 // Calendar API                                                               //
1596 ////////////////////////////////////////////////////////////////////////////////
1598 /**
1599  * Updates the calendar events associated to the given workshop
1600  *
1601  * @param stdClass $workshop the workshop instance record
1602  * @param int $cmid course module id
1603  */
1604 function workshop_calendar_update(stdClass $workshop, $cmid) {
1605     global $DB;
1607     // get the currently registered events so that we can re-use their ids
1608     $currentevents = $DB->get_records('event', array('modulename' => 'workshop', 'instance' => $workshop->id));
1610     // the common properties for all events
1611     $base = new stdClass();
1612     $base->description  = format_module_intro('workshop', $workshop, $cmid, false);
1613     $base->courseid     = $workshop->course;
1614     $base->groupid      = 0;
1615     $base->userid       = 0;
1616     $base->modulename   = 'workshop';
1617     $base->eventtype    = 'pluginname';
1618     $base->instance     = $workshop->id;
1619     $base->visible      = instance_is_visible('workshop', $workshop);
1620     $base->timeduration = 0;
1622     if ($workshop->submissionstart) {
1623         $event = clone($base);
1624         $event->name = get_string('submissionstartevent', 'mod_workshop', $workshop->name);
1625         $event->timestart = $workshop->submissionstart;
1626         if ($reusedevent = array_shift($currentevents)) {
1627             $event->id = $reusedevent->id;
1628         } else {
1629             // should not be set but just in case
1630             unset($event->id);
1631         }
1632         // update() will reuse a db record if the id field is set
1633         $eventobj = new calendar_event($event);
1634         $eventobj->update($event, false);
1635     }
1637     if ($workshop->submissionend) {
1638         $event = clone($base);
1639         $event->name = get_string('submissionendevent', 'mod_workshop', $workshop->name);
1640         $event->timestart = $workshop->submissionend;
1641         if ($reusedevent = array_shift($currentevents)) {
1642             $event->id = $reusedevent->id;
1643         } else {
1644             // should not be set but just in case
1645             unset($event->id);
1646         }
1647         // update() will reuse a db record if the id field is set
1648         $eventobj = new calendar_event($event);
1649         $eventobj->update($event, false);
1650     }
1652     if ($workshop->assessmentstart) {
1653         $event = clone($base);
1654         $event->name = get_string('assessmentstartevent', 'mod_workshop', $workshop->name);
1655         $event->timestart = $workshop->assessmentstart;
1656         if ($reusedevent = array_shift($currentevents)) {
1657             $event->id = $reusedevent->id;
1658         } else {
1659             // should not be set but just in case
1660             unset($event->id);
1661         }
1662         // update() will reuse a db record if the id field is set
1663         $eventobj = new calendar_event($event);
1664         $eventobj->update($event, false);
1665     }
1667     if ($workshop->assessmentend) {
1668         $event = clone($base);
1669         $event->name = get_string('assessmentendevent', 'mod_workshop', $workshop->name);
1670         $event->timestart = $workshop->assessmentend;
1671         if ($reusedevent = array_shift($currentevents)) {
1672             $event->id = $reusedevent->id;
1673         } else {
1674             // should not be set but just in case
1675             unset($event->id);
1676         }
1677         // update() will reuse a db record if the id field is set
1678         $eventobj = new calendar_event($event);
1679         $eventobj->update($event, false);
1680     }
1682     // delete any leftover events
1683     foreach ($currentevents as $oldevent) {
1684         $oldevent = calendar_event::load($oldevent);
1685         $oldevent->delete();
1686     }