MDL-45029 core_course: set aggregationcoef while creating a new course module
[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             if (!empty($workshop->add)) {
1145                 $gradecategory = $gradeitem->get_parent_category();
1146                 if (grade_category::aggregation_uses_aggregationcoef($gradecategory->aggregation)) {
1147                     if ($gradecategory->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN) {
1148                         $gradeitem->aggregationcoef = 1;
1149                     } else {
1150                         $gradeitem->aggregationcoef = 0;
1151                     }
1152                     $gradeitem->update();
1153                 }
1154             }
1155         }
1156     }
1159 ////////////////////////////////////////////////////////////////////////////////
1160 // File API                                                                   //
1161 ////////////////////////////////////////////////////////////////////////////////
1163 /**
1164  * Returns the lists of all browsable file areas within the given module context
1165  *
1166  * The file area workshop_intro for the activity introduction field is added automatically
1167  * by {@link file_browser::get_file_info_context_module()}
1168  *
1169  * @package  mod_workshop
1170  * @category files
1171  *
1172  * @param stdClass $course
1173  * @param stdClass $cm
1174  * @param stdClass $context
1175  * @return array of [(string)filearea] => (string)description
1176  */
1177 function workshop_get_file_areas($course, $cm, $context) {
1178     $areas = array();
1179     $areas['instructauthors']          = get_string('areainstructauthors', 'workshop');
1180     $areas['instructreviewers']        = get_string('areainstructreviewers', 'workshop');
1181     $areas['submission_content']       = get_string('areasubmissioncontent', 'workshop');
1182     $areas['submission_attachment']    = get_string('areasubmissionattachment', 'workshop');
1183     $areas['conclusion']               = get_string('areaconclusion', 'workshop');
1184     $areas['overallfeedback_content']  = get_string('areaoverallfeedbackcontent', 'workshop');
1185     $areas['overallfeedback_attachment'] = get_string('areaoverallfeedbackattachment', 'workshop');
1187     return $areas;
1190 /**
1191  * Serves the files from the workshop file areas
1192  *
1193  * Apart from module intro (handled by pluginfile.php automatically), workshop files may be
1194  * media inserted into submission content (like images) and submission attachments. For these two,
1195  * the fileareas submission_content and submission_attachment are used.
1196  * Besides that, areas instructauthors, instructreviewers and conclusion contain the media
1197  * embedded using the mod_form.php.
1198  *
1199  * @package  mod_workshop
1200  * @category files
1201  *
1202  * @param stdClass $course the course object
1203  * @param stdClass $cm the course module object
1204  * @param stdClass $context the workshop's context
1205  * @param string $filearea the name of the file area
1206  * @param array $args extra arguments (itemid, path)
1207  * @param bool $forcedownload whether or not force download
1208  * @param array $options additional options affecting the file serving
1209  * @return bool false if the file not found, just send the file otherwise and do not return anything
1210  */
1211 function workshop_pluginfile($course, $cm, $context, $filearea, array $args, $forcedownload, array $options=array()) {
1212     global $DB, $CFG, $USER;
1214     if ($context->contextlevel != CONTEXT_MODULE) {
1215         return false;
1216     }
1218     require_login($course, true, $cm);
1220     if ($filearea === 'instructauthors') {
1221         array_shift($args); // itemid is ignored here
1222         $relativepath = implode('/', $args);
1223         $fullpath = "/$context->id/mod_workshop/$filearea/0/$relativepath";
1225         $fs = get_file_storage();
1226         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1227             send_file_not_found();
1228         }
1230         // finally send the file
1231         send_stored_file($file, null, 0, $forcedownload, $options);
1233     } else if ($filearea === 'instructreviewers') {
1234         array_shift($args); // itemid is ignored here
1235         $relativepath = implode('/', $args);
1236         $fullpath = "/$context->id/mod_workshop/$filearea/0/$relativepath";
1238         $fs = get_file_storage();
1239         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1240             send_file_not_found();
1241         }
1243         // finally send the file
1244         send_stored_file($file, null, 0, $forcedownload, $options);
1246     } else if ($filearea === 'conclusion') {
1247         array_shift($args); // itemid is ignored here
1248         $relativepath = implode('/', $args);
1249         $fullpath = "/$context->id/mod_workshop/$filearea/0/$relativepath";
1251         $fs = get_file_storage();
1252         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1253             send_file_not_found();
1254         }
1256         // finally send the file
1257         send_stored_file($file, null, 0, $forcedownload, $options);
1259     } else if ($filearea === 'submission_content' or $filearea === 'submission_attachment') {
1260         $itemid = (int)array_shift($args);
1261         if (!$workshop = $DB->get_record('workshop', array('id' => $cm->instance))) {
1262             return false;
1263         }
1264         if (!$submission = $DB->get_record('workshop_submissions', array('id' => $itemid, 'workshopid' => $workshop->id))) {
1265             return false;
1266         }
1268         // make sure the user is allowed to see the file
1269         if (empty($submission->example)) {
1270             if ($USER->id != $submission->authorid) {
1271                 if ($submission->published == 1 and $workshop->phase == 50
1272                         and has_capability('mod/workshop:viewpublishedsubmissions', $context)) {
1273                     // Published submission, we can go (workshop does not take the group mode
1274                     // into account in this case yet).
1275                 } else if (!$DB->record_exists('workshop_assessments', array('submissionid' => $submission->id, 'reviewerid' => $USER->id))) {
1276                     if (!has_capability('mod/workshop:viewallsubmissions', $context)) {
1277                         send_file_not_found();
1278                     } else {
1279                         $gmode = groups_get_activity_groupmode($cm, $course);
1280                         if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1281                             // check there is at least one common group with both the $USER
1282                             // and the submission author
1283                             $sql = "SELECT 'x'
1284                                       FROM {workshop_submissions} s
1285                                       JOIN {user} a ON (a.id = s.authorid)
1286                                       JOIN {groups_members} agm ON (a.id = agm.userid)
1287                                       JOIN {user} u ON (u.id = ?)
1288                                       JOIN {groups_members} ugm ON (u.id = ugm.userid)
1289                                      WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1290                             $params = array($USER->id, $workshop->id, $submission->id);
1291                             if (!$DB->record_exists_sql($sql, $params)) {
1292                                 send_file_not_found();
1293                             }
1294                         }
1295                     }
1296                 }
1297             }
1298         }
1300         $fs = get_file_storage();
1301         $relativepath = implode('/', $args);
1302         $fullpath = "/$context->id/mod_workshop/$filearea/$itemid/$relativepath";
1303         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1304             return false;
1305         }
1306         // finally send the file
1307         // these files are uploaded by students - forcing download for security reasons
1308         send_stored_file($file, 0, 0, true, $options);
1310     } else if ($filearea === 'overallfeedback_content' or $filearea === 'overallfeedback_attachment') {
1311         $itemid = (int)array_shift($args);
1312         if (!$workshop = $DB->get_record('workshop', array('id' => $cm->instance))) {
1313             return false;
1314         }
1315         if (!$assessment = $DB->get_record('workshop_assessments', array('id' => $itemid))) {
1316             return false;
1317         }
1318         if (!$submission = $DB->get_record('workshop_submissions', array('id' => $assessment->submissionid, 'workshopid' => $workshop->id))) {
1319             return false;
1320         }
1322         if ($USER->id == $assessment->reviewerid) {
1323             // Reviewers can always see their own files.
1324         } else if ($USER->id == $submission->authorid and $workshop->phase == 50) {
1325             // Authors can see the feedback once the workshop is closed.
1326         } else if (!empty($submission->example) and $assessment->weight == 1) {
1327             // Reference assessments of example submissions can be displayed.
1328         } else if (!has_capability('mod/workshop:viewallassessments', $context)) {
1329             send_file_not_found();
1330         } else {
1331             $gmode = groups_get_activity_groupmode($cm, $course);
1332             if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1333                 // Check there is at least one common group with both the $USER
1334                 // and the submission author.
1335                 $sql = "SELECT 'x'
1336                           FROM {workshop_submissions} s
1337                           JOIN {user} a ON (a.id = s.authorid)
1338                           JOIN {groups_members} agm ON (a.id = agm.userid)
1339                           JOIN {user} u ON (u.id = ?)
1340                           JOIN {groups_members} ugm ON (u.id = ugm.userid)
1341                          WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1342                 $params = array($USER->id, $workshop->id, $submission->id);
1343                 if (!$DB->record_exists_sql($sql, $params)) {
1344                     send_file_not_found();
1345                 }
1346             }
1347         }
1349         $fs = get_file_storage();
1350         $relativepath = implode('/', $args);
1351         $fullpath = "/$context->id/mod_workshop/$filearea/$itemid/$relativepath";
1352         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1353             return false;
1354         }
1355         // finally send the file
1356         // these files are uploaded by students - forcing download for security reasons
1357         send_stored_file($file, 0, 0, true, $options);
1358     }
1360     return false;
1363 /**
1364  * File browsing support for workshop file areas
1365  *
1366  * @package  mod_workshop
1367  * @category files
1368  *
1369  * @param file_browser $browser
1370  * @param array $areas
1371  * @param stdClass $course
1372  * @param stdClass $cm
1373  * @param stdClass $context
1374  * @param string $filearea
1375  * @param int $itemid
1376  * @param string $filepath
1377  * @param string $filename
1378  * @return file_info instance or null if not found
1379  */
1380 function workshop_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
1381     global $CFG, $DB, $USER;
1383     /** @var array internal cache for author names */
1384     static $submissionauthors = array();
1386     $fs = get_file_storage();
1388     if ($filearea === 'submission_content' or $filearea === 'submission_attachment') {
1390         if (!has_capability('mod/workshop:viewallsubmissions', $context)) {
1391             return null;
1392         }
1394         if (is_null($itemid)) {
1395             // no itemid (submissionid) passed, display the list of all submissions
1396             require_once($CFG->dirroot . '/mod/workshop/fileinfolib.php');
1397             return new workshop_file_info_submissions_container($browser, $course, $cm, $context, $areas, $filearea);
1398         }
1400         // make sure the user can see the particular submission in separate groups mode
1401         $gmode = groups_get_activity_groupmode($cm, $course);
1403         if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1404             // check there is at least one common group with both the $USER
1405             // and the submission author (this is not expected to be a frequent
1406             // usecase so we can live with pretty ineffective one query per submission here...)
1407             $sql = "SELECT 'x'
1408                       FROM {workshop_submissions} s
1409                       JOIN {user} a ON (a.id = s.authorid)
1410                       JOIN {groups_members} agm ON (a.id = agm.userid)
1411                       JOIN {user} u ON (u.id = ?)
1412                       JOIN {groups_members} ugm ON (u.id = ugm.userid)
1413                      WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1414             $params = array($USER->id, $cm->instance, $itemid);
1415             if (!$DB->record_exists_sql($sql, $params)) {
1416                 return null;
1417             }
1418         }
1420         // we are inside some particular submission container
1422         $filepath = is_null($filepath) ? '/' : $filepath;
1423         $filename = is_null($filename) ? '.' : $filename;
1425         if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, $itemid, $filepath, $filename)) {
1426             if ($filepath === '/' and $filename === '.') {
1427                 $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, $itemid);
1428             } else {
1429                 // not found
1430                 return null;
1431             }
1432         }
1434         // Checks to see if the user can manage files or is the owner.
1435         // TODO MDL-33805 - Do not use userid here and move the capability check above.
1436         if (!has_capability('moodle/course:managefiles', $context) && $storedfile->get_userid() != $USER->id) {
1437             return null;
1438         }
1440         // let us display the author's name instead of itemid (submission id)
1442         if (isset($submissionauthors[$itemid])) {
1443             $topvisiblename = $submissionauthors[$itemid];
1445         } else {
1447             $sql = "SELECT s.id, u.lastname, u.firstname
1448                       FROM {workshop_submissions} s
1449                       JOIN {user} u ON (s.authorid = u.id)
1450                      WHERE s.example = 0 AND s.workshopid = ?";
1451             $params = array($cm->instance);
1452             $rs = $DB->get_recordset_sql($sql, $params);
1454             foreach ($rs as $submissionauthor) {
1455                 $title = s(fullname($submissionauthor)); // this is generally not unique...
1456                 $submissionauthors[$submissionauthor->id] = $title;
1457             }
1458             $rs->close();
1460             if (!isset($submissionauthors[$itemid])) {
1461                 // should not happen
1462                 return null;
1463             } else {
1464                 $topvisiblename = $submissionauthors[$itemid];
1465             }
1466         }
1468         $urlbase = $CFG->wwwroot . '/pluginfile.php';
1469         // do not allow manual modification of any files!
1470         return new file_info_stored($browser, $context, $storedfile, $urlbase, $topvisiblename, true, true, false, false);
1471     }
1473     if ($filearea === 'overallfeedback_content' or $filearea === 'overallfeedback_attachment') {
1475         if (!has_capability('mod/workshop:viewallassessments', $context)) {
1476             return null;
1477         }
1479         if (is_null($itemid)) {
1480             // No itemid (assessmentid) passed, display the list of all assessments.
1481             require_once($CFG->dirroot . '/mod/workshop/fileinfolib.php');
1482             return new workshop_file_info_overallfeedback_container($browser, $course, $cm, $context, $areas, $filearea);
1483         }
1485         // Make sure the user can see the particular assessment in separate groups mode.
1486         $gmode = groups_get_activity_groupmode($cm, $course);
1487         if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1488             // Check there is at least one common group with both the $USER
1489             // and the submission author.
1490             $sql = "SELECT 'x'
1491                       FROM {workshop_submissions} s
1492                       JOIN {user} a ON (a.id = s.authorid)
1493                       JOIN {groups_members} agm ON (a.id = agm.userid)
1494                       JOIN {user} u ON (u.id = ?)
1495                       JOIN {groups_members} ugm ON (u.id = ugm.userid)
1496                      WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1497             $params = array($USER->id, $cm->instance, $itemid);
1498             if (!$DB->record_exists_sql($sql, $params)) {
1499                 return null;
1500             }
1501         }
1503         // We are inside a particular assessment container.
1504         $filepath = is_null($filepath) ? '/' : $filepath;
1505         $filename = is_null($filename) ? '.' : $filename;
1507         if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, $itemid, $filepath, $filename)) {
1508             if ($filepath === '/' and $filename === '.') {
1509                 $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, $itemid);
1510             } else {
1511                 // Not found
1512                 return null;
1513             }
1514         }
1516         // Check to see if the user can manage files or is the owner.
1517         if (!has_capability('moodle/course:managefiles', $context) and $storedfile->get_userid() != $USER->id) {
1518             return null;
1519         }
1521         $urlbase = $CFG->wwwroot . '/pluginfile.php';
1523         // Do not allow manual modification of any files.
1524         return new file_info_stored($browser, $context, $storedfile, $urlbase, $itemid, true, true, false, false);
1525     }
1527     if ($filearea == 'instructauthors' or $filearea == 'instructreviewers' or $filearea == 'conclusion') {
1528         // always only itemid 0
1530         $filepath = is_null($filepath) ? '/' : $filepath;
1531         $filename = is_null($filename) ? '.' : $filename;
1533         $urlbase = $CFG->wwwroot.'/pluginfile.php';
1534         if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, 0, $filepath, $filename)) {
1535             if ($filepath === '/' and $filename === '.') {
1536                 $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, 0);
1537             } else {
1538                 // not found
1539                 return null;
1540             }
1541         }
1542         return new file_info_stored($browser, $context, $storedfile, $urlbase, $areas[$filearea], false, true, true, false);
1543     }
1546 ////////////////////////////////////////////////////////////////////////////////
1547 // Navigation API                                                             //
1548 ////////////////////////////////////////////////////////////////////////////////
1550 /**
1551  * Extends the global navigation tree by adding workshop nodes if there is a relevant content
1552  *
1553  * This can be called by an AJAX request so do not rely on $PAGE as it might not be set up properly.
1554  *
1555  * @param navigation_node $navref An object representing the navigation tree node of the workshop module instance
1556  * @param stdClass $course
1557  * @param stdClass $module
1558  * @param cm_info $cm
1559  */
1560 function workshop_extend_navigation(navigation_node $navref, stdclass $course, stdclass $module, cm_info $cm) {
1561     global $CFG;
1563     if (has_capability('mod/workshop:submit', context_module::instance($cm->id))) {
1564         $url = new moodle_url('/mod/workshop/submission.php', array('cmid' => $cm->id));
1565         $mysubmission = $navref->add(get_string('mysubmission', 'workshop'), $url);
1566         $mysubmission->mainnavonly = true;
1567     }
1570 /**
1571  * Extends the settings navigation with the Workshop settings
1573  * This function is called when the context for the page is a workshop module. This is not called by AJAX
1574  * so it is safe to rely on the $PAGE.
1575  *
1576  * @param settings_navigation $settingsnav {@link settings_navigation}
1577  * @param navigation_node $workshopnode {@link navigation_node}
1578  */
1579 function workshop_extend_settings_navigation(settings_navigation $settingsnav, navigation_node $workshopnode=null) {
1580     global $PAGE;
1582     //$workshopobject = $DB->get_record("workshop", array("id" => $PAGE->cm->instance));
1584     if (has_capability('mod/workshop:editdimensions', $PAGE->cm->context)) {
1585         $url = new moodle_url('/mod/workshop/editform.php', array('cmid' => $PAGE->cm->id));
1586         $workshopnode->add(get_string('editassessmentform', 'workshop'), $url, settings_navigation::TYPE_SETTING);
1587     }
1588     if (has_capability('mod/workshop:allocate', $PAGE->cm->context)) {
1589         $url = new moodle_url('/mod/workshop/allocation.php', array('cmid' => $PAGE->cm->id));
1590         $workshopnode->add(get_string('allocate', 'workshop'), $url, settings_navigation::TYPE_SETTING);
1591     }
1594 /**
1595  * Return a list of page types
1596  * @param string $pagetype current page type
1597  * @param stdClass $parentcontext Block's parent context
1598  * @param stdClass $currentcontext Current context of block
1599  */
1600 function workshop_page_type_list($pagetype, $parentcontext, $currentcontext) {
1601     $module_pagetype = array('mod-workshop-*'=>get_string('page-mod-workshop-x', 'workshop'));
1602     return $module_pagetype;
1605 ////////////////////////////////////////////////////////////////////////////////
1606 // Calendar API                                                               //
1607 ////////////////////////////////////////////////////////////////////////////////
1609 /**
1610  * Updates the calendar events associated to the given workshop
1611  *
1612  * @param stdClass $workshop the workshop instance record
1613  * @param int $cmid course module id
1614  */
1615 function workshop_calendar_update(stdClass $workshop, $cmid) {
1616     global $DB;
1618     // get the currently registered events so that we can re-use their ids
1619     $currentevents = $DB->get_records('event', array('modulename' => 'workshop', 'instance' => $workshop->id));
1621     // the common properties for all events
1622     $base = new stdClass();
1623     $base->description  = format_module_intro('workshop', $workshop, $cmid, false);
1624     $base->courseid     = $workshop->course;
1625     $base->groupid      = 0;
1626     $base->userid       = 0;
1627     $base->modulename   = 'workshop';
1628     $base->eventtype    = 'pluginname';
1629     $base->instance     = $workshop->id;
1630     $base->visible      = instance_is_visible('workshop', $workshop);
1631     $base->timeduration = 0;
1633     if ($workshop->submissionstart) {
1634         $event = clone($base);
1635         $event->name = get_string('submissionstartevent', 'mod_workshop', $workshop->name);
1636         $event->timestart = $workshop->submissionstart;
1637         if ($reusedevent = array_shift($currentevents)) {
1638             $event->id = $reusedevent->id;
1639         } else {
1640             // should not be set but just in case
1641             unset($event->id);
1642         }
1643         // update() will reuse a db record if the id field is set
1644         $eventobj = new calendar_event($event);
1645         $eventobj->update($event, false);
1646     }
1648     if ($workshop->submissionend) {
1649         $event = clone($base);
1650         $event->name = get_string('submissionendevent', 'mod_workshop', $workshop->name);
1651         $event->timestart = $workshop->submissionend;
1652         if ($reusedevent = array_shift($currentevents)) {
1653             $event->id = $reusedevent->id;
1654         } else {
1655             // should not be set but just in case
1656             unset($event->id);
1657         }
1658         // update() will reuse a db record if the id field is set
1659         $eventobj = new calendar_event($event);
1660         $eventobj->update($event, false);
1661     }
1663     if ($workshop->assessmentstart) {
1664         $event = clone($base);
1665         $event->name = get_string('assessmentstartevent', 'mod_workshop', $workshop->name);
1666         $event->timestart = $workshop->assessmentstart;
1667         if ($reusedevent = array_shift($currentevents)) {
1668             $event->id = $reusedevent->id;
1669         } else {
1670             // should not be set but just in case
1671             unset($event->id);
1672         }
1673         // update() will reuse a db record if the id field is set
1674         $eventobj = new calendar_event($event);
1675         $eventobj->update($event, false);
1676     }
1678     if ($workshop->assessmentend) {
1679         $event = clone($base);
1680         $event->name = get_string('assessmentendevent', 'mod_workshop', $workshop->name);
1681         $event->timestart = $workshop->assessmentend;
1682         if ($reusedevent = array_shift($currentevents)) {
1683             $event->id = $reusedevent->id;
1684         } else {
1685             // should not be set but just in case
1686             unset($event->id);
1687         }
1688         // update() will reuse a db record if the id field is set
1689         $eventobj = new calendar_event($event);
1690         $eventobj->update($event, false);
1691     }
1693     // delete any leftover events
1694     foreach ($currentevents as $oldevent) {
1695         $oldevent = calendar_event::load($oldevent);
1696         $oldevent->delete();
1697     }