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