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