Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
 
3
// This file is part of Moodle - http://moodle.org/
4
//
5
// Moodle is free software: you can redistribute it and/or modify
6
// it under the terms of the GNU General Public License as published by
7
// the Free Software Foundation, either version 3 of the License, or
8
// (at your option) any later version.
9
//
10
// Moodle is distributed in the hope that it will be useful,
11
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
// GNU General Public License for more details.
14
//
15
// You should have received a copy of the GNU General Public License
16
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17
 
18
/**
19
 * Library of workshop module functions needed by Moodle core and other subsystems
20
 *
21
 * All the functions neeeded by Moodle core, gradebook, file subsystem etc
22
 * are placed here.
23
 *
24
 * @package    mod_workshop
25
 * @copyright  2009 David Mudrak <david.mudrak@gmail.com>
26
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27
 */
28
 
29
defined('MOODLE_INTERNAL') || die();
30
 
31
require_once($CFG->dirroot . '/calendar/lib.php');
32
 
33
define('WORKSHOP_EVENT_TYPE_SUBMISSION_OPEN',   'opensubmission');
34
define('WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE',  'closesubmission');
35
define('WORKSHOP_EVENT_TYPE_ASSESSMENT_OPEN',   'openassessment');
36
define('WORKSHOP_EVENT_TYPE_ASSESSMENT_CLOSE',  'closeassessment');
37
define('WORKSHOP_SUBMISSION_TYPE_DISABLED', 0);
38
define('WORKSHOP_SUBMISSION_TYPE_AVAILABLE', 1);
39
define('WORKSHOP_SUBMISSION_TYPE_REQUIRED', 2);
40
 
41
////////////////////////////////////////////////////////////////////////////////
42
// Moodle core API                                                            //
43
////////////////////////////////////////////////////////////////////////////////
44
 
45
/**
46
 * Returns the information if the module supports a feature
47
 *
48
 * @see plugin_supports() in lib/moodlelib.php
49
 * @param string $feature FEATURE_xx constant for requested feature
50
 * @return mixed True if module supports feature, false if not, null if doesn't know or string for the module purpose.
51
 */
52
function workshop_supports($feature) {
53
    switch($feature) {
54
        case FEATURE_GRADE_HAS_GRADE:   return true;
55
        case FEATURE_GROUPS:            return true;
56
        case FEATURE_GROUPINGS:         return true;
57
        case FEATURE_MOD_INTRO:         return true;
58
        case FEATURE_BACKUP_MOODLE2:    return true;
59
        case FEATURE_COMPLETION_TRACKS_VIEWS:
60
            return true;
61
        case FEATURE_SHOW_DESCRIPTION:  return true;
62
        case FEATURE_PLAGIARISM:        return true;
63
        case FEATURE_MOD_PURPOSE:       return MOD_PURPOSE_ASSESSMENT;
64
        default:                        return null;
65
    }
66
}
67
 
68
/**
69
 * Saves a new instance of the workshop into the database
70
 *
71
 * Given an object containing all the necessary data,
72
 * (defined by the form in mod_form.php) this function
73
 * will save a new instance and return the id number
74
 * of the new instance.
75
 *
76
 * @param stdClass $workshop An object from the form in mod_form.php
77
 * @return int The id of the newly inserted workshop record
78
 */
79
function workshop_add_instance(stdclass $workshop) {
80
    global $CFG, $DB;
81
    require_once(__DIR__ . '/locallib.php');
82
 
83
    $workshop->phase                 = workshop::PHASE_SETUP;
84
    $workshop->timecreated           = time();
85
    $workshop->timemodified          = $workshop->timecreated;
86
    $workshop->useexamples           = (int)!empty($workshop->useexamples);
87
    $workshop->usepeerassessment     = 1;
88
    $workshop->useselfassessment     = (int)!empty($workshop->useselfassessment);
89
    $workshop->latesubmissions       = (int)!empty($workshop->latesubmissions);
90
    $workshop->phaseswitchassessment = (int)!empty($workshop->phaseswitchassessment);
91
    $workshop->evaluation            = 'best';
92
 
93
    if (isset($workshop->gradinggradepass)) {
94
        $workshop->gradinggradepass = (float)unformat_float($workshop->gradinggradepass);
95
    }
96
 
97
    if (isset($workshop->submissiongradepass)) {
98
        $workshop->submissiongradepass = (float)unformat_float($workshop->submissiongradepass);
99
    }
100
 
101
    if (isset($workshop->submissionfiletypes)) {
102
        $filetypesutil = new \core_form\filetypes_util();
103
        $submissionfiletypes = $filetypesutil->normalize_file_types($workshop->submissionfiletypes);
104
        $workshop->submissionfiletypes = implode(' ', $submissionfiletypes);
105
    }
106
 
107
    if (isset($workshop->overallfeedbackfiletypes)) {
108
        $filetypesutil = new \core_form\filetypes_util();
109
        $overallfeedbackfiletypes = $filetypesutil->normalize_file_types($workshop->overallfeedbackfiletypes);
110
        $workshop->overallfeedbackfiletypes = implode(' ', $overallfeedbackfiletypes);
111
    }
112
 
113
    // insert the new record so we get the id
114
    $workshop->id = $DB->insert_record('workshop', $workshop);
115
 
116
    // we need to use context now, so we need to make sure all needed info is already in db
117
    $cmid = $workshop->coursemodule;
118
    $DB->set_field('course_modules', 'instance', $workshop->id, array('id' => $cmid));
119
    $context = context_module::instance($cmid);
120
 
121
    // process the custom wysiwyg editors
122
    if ($draftitemid = $workshop->instructauthorseditor['itemid']) {
123
        $workshop->instructauthors = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructauthors',
124
                0, workshop::instruction_editors_options($context), $workshop->instructauthorseditor['text']);
125
        $workshop->instructauthorsformat = $workshop->instructauthorseditor['format'];
126
    }
127
 
128
    if ($draftitemid = $workshop->instructreviewerseditor['itemid']) {
129
        $workshop->instructreviewers = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructreviewers',
130
                0, workshop::instruction_editors_options($context), $workshop->instructreviewerseditor['text']);
131
        $workshop->instructreviewersformat = $workshop->instructreviewerseditor['format'];
132
    }
133
 
134
    if ($draftitemid = $workshop->conclusioneditor['itemid']) {
135
        $workshop->conclusion = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'conclusion',
136
                0, workshop::instruction_editors_options($context), $workshop->conclusioneditor['text']);
137
        $workshop->conclusionformat = $workshop->conclusioneditor['format'];
138
    }
139
 
140
    // re-save the record with the replaced URLs in editor fields
141
    $DB->update_record('workshop', $workshop);
142
 
143
    // create gradebook items
144
    workshop_grade_item_update($workshop);
145
    workshop_grade_item_category_update($workshop);
146
 
147
    // create calendar events
148
    workshop_calendar_update($workshop, $workshop->coursemodule);
149
    if (!empty($workshop->completionexpected)) {
150
        \core_completion\api::update_completion_date_event($cmid, 'workshop', $workshop->id, $workshop->completionexpected);
151
    }
152
 
153
    return $workshop->id;
154
}
155
 
156
/**
157
 * Given an object containing all the necessary data,
158
 * (defined by the form in mod_form.php) this function
159
 * will update an existing instance with new data.
160
 *
161
 * @param stdClass $workshop An object from the form in mod_form.php
162
 * @return bool success
163
 */
164
function workshop_update_instance(stdclass $workshop) {
165
    global $CFG, $DB;
166
    require_once(__DIR__ . '/locallib.php');
167
 
168
    $workshop->timemodified          = time();
169
    $workshop->id                    = $workshop->instance;
170
    $workshop->useexamples           = (int)!empty($workshop->useexamples);
171
    $workshop->usepeerassessment     = 1;
172
    $workshop->useselfassessment     = (int)!empty($workshop->useselfassessment);
173
    $workshop->latesubmissions       = (int)!empty($workshop->latesubmissions);
174
    $workshop->phaseswitchassessment = (int)!empty($workshop->phaseswitchassessment);
175
 
176
    if (isset($workshop->gradinggradepass)) {
177
        $workshop->gradinggradepass = (float)unformat_float($workshop->gradinggradepass);
178
    }
179
 
180
    if (isset($workshop->submissiongradepass)) {
181
        $workshop->submissiongradepass = (float)unformat_float($workshop->submissiongradepass);
182
    }
183
 
184
    if (isset($workshop->submissionfiletypes)) {
185
        $filetypesutil = new \core_form\filetypes_util();
186
        $submissionfiletypes = $filetypesutil->normalize_file_types($workshop->submissionfiletypes);
187
        $workshop->submissionfiletypes = implode(' ', $submissionfiletypes);
188
    }
189
 
190
    if (isset($workshop->overallfeedbackfiletypes)) {
191
        $filetypesutil = new \core_form\filetypes_util();
192
        $overallfeedbackfiletypes = $filetypesutil->normalize_file_types($workshop->overallfeedbackfiletypes);
193
        $workshop->overallfeedbackfiletypes = implode(' ', $overallfeedbackfiletypes);
194
    }
195
 
196
    // todo - if the grading strategy is being changed, we may want to replace all aggregated peer grades with nulls
197
 
198
    $DB->update_record('workshop', $workshop);
199
    $context = context_module::instance($workshop->coursemodule);
200
 
201
    // process the custom wysiwyg editors
202
    if ($draftitemid = $workshop->instructauthorseditor['itemid']) {
203
        $workshop->instructauthors = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructauthors',
204
                0, workshop::instruction_editors_options($context), $workshop->instructauthorseditor['text']);
205
        $workshop->instructauthorsformat = $workshop->instructauthorseditor['format'];
206
    }
207
 
208
    if ($draftitemid = $workshop->instructreviewerseditor['itemid']) {
209
        $workshop->instructreviewers = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructreviewers',
210
                0, workshop::instruction_editors_options($context), $workshop->instructreviewerseditor['text']);
211
        $workshop->instructreviewersformat = $workshop->instructreviewerseditor['format'];
212
    }
213
 
214
    if ($draftitemid = $workshop->conclusioneditor['itemid']) {
215
        $workshop->conclusion = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'conclusion',
216
                0, workshop::instruction_editors_options($context), $workshop->conclusioneditor['text']);
217
        $workshop->conclusionformat = $workshop->conclusioneditor['format'];
218
    }
219
 
220
    // re-save the record with the replaced URLs in editor fields
221
    $DB->update_record('workshop', $workshop);
222
 
223
    // update gradebook items
224
    workshop_grade_item_update($workshop);
225
    workshop_grade_item_category_update($workshop);
226
 
227
    // update calendar events
228
    workshop_calendar_update($workshop, $workshop->coursemodule);
229
    $completionexpected = (!empty($workshop->completionexpected)) ? $workshop->completionexpected : null;
230
    \core_completion\api::update_completion_date_event($workshop->coursemodule, 'workshop', $workshop->id, $completionexpected);
231
 
232
    return true;
233
}
234
 
235
/**
236
 * Given an ID of an instance of this module,
237
 * this function will permanently delete the instance
238
 * and any data that depends on it.
239
 *
240
 * @param int $id Id of the module instance
241
 * @return boolean Success/Failure
242
 */
243
function workshop_delete_instance($id) {
244
    global $CFG, $DB;
245
    require_once($CFG->libdir.'/gradelib.php');
246
 
247
    if (! $workshop = $DB->get_record('workshop', array('id' => $id))) {
248
        return false;
249
    }
250
 
251
    // delete all associated aggregations
252
    $DB->delete_records('workshop_aggregations', array('workshopid' => $workshop->id));
253
 
254
    // get the list of ids of all submissions
255
    $submissions = $DB->get_records('workshop_submissions', array('workshopid' => $workshop->id), '', 'id');
256
 
257
    // get the list of all allocated assessments
258
    $assessments = $DB->get_records_list('workshop_assessments', 'submissionid', array_keys($submissions), '', 'id');
259
 
260
    // delete the associated records from the workshop core tables
261
    $DB->delete_records_list('workshop_grades', 'assessmentid', array_keys($assessments));
262
    $DB->delete_records_list('workshop_assessments', 'id', array_keys($assessments));
263
    $DB->delete_records_list('workshop_submissions', 'id', array_keys($submissions));
264
 
265
    // call the static clean-up methods of all available subplugins
266
    $strategies = core_component::get_plugin_list('workshopform');
267
    foreach ($strategies as $strategy => $path) {
268
        require_once($path.'/lib.php');
269
        $classname = 'workshop_'.$strategy.'_strategy';
270
        call_user_func($classname.'::delete_instance', $workshop->id);
271
    }
272
 
273
    $allocators = core_component::get_plugin_list('workshopallocation');
274
    foreach ($allocators as $allocator => $path) {
275
        require_once($path.'/lib.php');
276
        $classname = 'workshop_'.$allocator.'_allocator';
277
        call_user_func($classname.'::delete_instance', $workshop->id);
278
    }
279
 
280
    $evaluators = core_component::get_plugin_list('workshopeval');
281
    foreach ($evaluators as $evaluator => $path) {
282
        require_once($path.'/lib.php');
283
        $classname = 'workshop_'.$evaluator.'_evaluation';
284
        call_user_func($classname.'::delete_instance', $workshop->id);
285
    }
286
 
287
    // delete the calendar events
288
    $events = $DB->get_records('event', array('modulename' => 'workshop', 'instance' => $workshop->id));
289
    foreach ($events as $event) {
290
        $event = calendar_event::load($event);
291
        $event->delete();
292
    }
293
 
294
    // gradebook cleanup
295
    grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 0, null, array('deleted' => true));
296
    grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 1, null, array('deleted' => true));
297
 
298
    // finally remove the workshop record itself
299
    // We must delete the module record after we delete the grade item.
300
    $DB->delete_records('workshop', array('id' => $workshop->id));
301
 
302
    return true;
303
}
304
 
305
/**
306
 * This standard function will check all instances of this module
307
 * and make sure there are up-to-date events created for each of them.
308
 * If courseid = 0, then every workshop event in the site is checked, else
309
 * only workshop events belonging to the course specified are checked.
310
 *
311
 * @param  integer $courseid The Course ID.
312
 * @param int|stdClass $instance workshop module instance or ID.
313
 * @param int|stdClass $cm Course module object or ID.
314
 * @return bool Returns true if the calendar events were successfully updated.
315
 */
316
function workshop_refresh_events($courseid = 0, $instance = null, $cm = null) {
317
    global $DB;
318
 
319
    // If we have instance information then we can just update the one event instead of updating all events.
320
    if (isset($instance)) {
321
        if (!is_object($instance)) {
322
            $instance = $DB->get_record('workshop', array('id' => $instance), '*', MUST_EXIST);
323
        }
324
        if (isset($cm)) {
325
            if (!is_object($cm)) {
326
                $cm = (object)array('id' => $cm);
327
            }
328
        } else {
329
            $cm = get_coursemodule_from_instance('workshop', $instance->id);
330
        }
331
        workshop_calendar_update($instance, $cm->id);
332
        return true;
333
    }
334
 
335
    if ($courseid) {
336
        // Make sure that the course id is numeric.
337
        if (!is_numeric($courseid)) {
338
            return false;
339
        }
340
        if (!$workshops = $DB->get_records('workshop', array('course' => $courseid))) {
341
            return false;
342
        }
343
    } else {
344
        if (!$workshops = $DB->get_records('workshop')) {
345
            return false;
346
        }
347
    }
348
    foreach ($workshops as $workshop) {
349
        if (!$cm = get_coursemodule_from_instance('workshop', $workshop->id, $courseid, false)) {
350
            continue;
351
        }
352
        workshop_calendar_update($workshop, $cm->id);
353
    }
354
    return true;
355
}
356
 
357
/**
358
 * List the actions that correspond to a view of this module.
359
 * This is used by the participation report.
360
 *
361
 * Note: This is not used by new logging system. Event with
362
 *       crud = 'r' and edulevel = LEVEL_PARTICIPATING will
363
 *       be considered as view action.
364
 *
365
 * @return array
366
 */
367
function workshop_get_view_actions() {
368
    return array('view', 'view all', 'view submission', 'view example');
369
}
370
 
371
/**
372
 * List the actions that correspond to a post of this module.
373
 * This is used by the participation report.
374
 *
375
 * Note: This is not used by new logging system. Event with
376
 *       crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING
377
 *       will be considered as post action.
378
 *
379
 * @return array
380
 */
381
function workshop_get_post_actions() {
382
    return array('add', 'add assessment', 'add example', 'add submission',
383
                 'update', 'update assessment', 'update example', 'update submission');
384
}
385
 
386
/**
387
 * Return a small object with summary information about what a
388
 * user has done with a given particular instance of this module
389
 * Used for user activity reports.
390
 * $return->time = the time they did it
391
 * $return->info = a short text description
392
 *
393
 * @param stdClass $course The course record.
394
 * @param stdClass $user The user record.
395
 * @param cm_info|stdClass $mod The course module info object or record.
396
 * @param stdClass $workshop The workshop instance record.
397
 * @return stdclass|null
398
 */
399
function workshop_user_outline($course, $user, $mod, $workshop) {
400
    global $CFG, $DB;
401
    require_once($CFG->libdir.'/gradelib.php');
402
 
403
    $grades = grade_get_grades($course->id, 'mod', 'workshop', $workshop->id, $user->id);
404
 
405
    $submissiongrade = null;
406
    $assessmentgrade = null;
407
 
408
    $info = '';
409
    $time = 0;
410
 
411
    if (!empty($grades->items[0]->grades)) {
412
        $submissiongrade = reset($grades->items[0]->grades);
413
        $time = max($time, $submissiongrade->dategraded);
414
        if (!$submissiongrade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
415
            $info .= get_string('submissiongrade', 'workshop') . ': ' . $submissiongrade->str_long_grade
416
                . html_writer::empty_tag('br');
417
        } else {
418
            $info .= get_string('submissiongrade', 'workshop') . ': ' . get_string('hidden', 'grades')
419
                . html_writer::empty_tag('br');
420
        }
421
    }
422
    if (!empty($grades->items[1]->grades)) {
423
        $assessmentgrade = reset($grades->items[1]->grades);
424
        $time = max($time, $assessmentgrade->dategraded);
425
        if (!$assessmentgrade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
426
            $info .= get_string('gradinggrade', 'workshop') . ': ' . $assessmentgrade->str_long_grade;
427
        } else {
428
            $info .= get_string('gradinggrade', 'workshop') . ': ' . get_string('hidden', 'grades');
429
        }
430
    }
431
 
432
    if (!empty($info) and !empty($time)) {
433
        $return = new stdclass();
434
        $return->time = $time;
435
        $return->info = $info;
436
        return $return;
437
    }
438
 
439
    return null;
440
}
441
 
442
/**
443
 * Print a detailed representation of what a user has done with
444
 * a given particular instance of this module, for user activity reports.
445
 *
446
 * @param stdClass $course The course record.
447
 * @param stdClass $user The user record.
448
 * @param cm_info|stdClass $mod The course module info object or record.
449
 * @param stdClass $workshop The workshop instance record.
450
 * @return string HTML
451
 */
452
function workshop_user_complete($course, $user, $mod, $workshop) {
453
    global $CFG, $DB, $OUTPUT;
454
    require_once(__DIR__.'/locallib.php');
455
    require_once($CFG->libdir.'/gradelib.php');
456
 
457
    $workshop   = new workshop($workshop, $mod, $course);
458
    $grades     = grade_get_grades($course->id, 'mod', 'workshop', $workshop->id, $user->id);
459
 
460
    if (!empty($grades->items[0]->grades)) {
461
        $submissiongrade = reset($grades->items[0]->grades);
462
        if (!$submissiongrade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
463
            $info = get_string('submissiongrade', 'workshop') . ': ' . $submissiongrade->str_long_grade;
464
        } else {
465
            $info = get_string('submissiongrade', 'workshop') . ': ' . get_string('hidden', 'grades');
466
        }
467
        echo html_writer::tag('li', $info, array('class'=>'submissiongrade'));
468
    }
469
    if (!empty($grades->items[1]->grades)) {
470
        $assessmentgrade = reset($grades->items[1]->grades);
471
        if (!$assessmentgrade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
472
            $info = get_string('gradinggrade', 'workshop') . ': ' . $assessmentgrade->str_long_grade;
473
        } else {
474
            $info = get_string('gradinggrade', 'workshop') . ': ' . get_string('hidden', 'grades');
475
        }
476
        echo html_writer::tag('li', $info, array('class'=>'gradinggrade'));
477
    }
478
 
479
    if (has_capability('mod/workshop:viewallsubmissions', $workshop->context)) {
480
        $canviewsubmission = true;
481
        if (groups_get_activity_groupmode($workshop->cm) == SEPARATEGROUPS) {
482
            // user must have accessallgroups or share at least one group with the submission author
483
            if (!has_capability('moodle/site:accessallgroups', $workshop->context)) {
484
                $usersgroups = groups_get_activity_allowed_groups($workshop->cm);
485
                $authorsgroups = groups_get_all_groups($workshop->course->id, $user->id, $workshop->cm->groupingid, 'g.id');
486
                $sharedgroups = array_intersect_key($usersgroups, $authorsgroups);
487
                if (empty($sharedgroups)) {
488
                    $canviewsubmission = false;
489
                }
490
            }
491
        }
492
        if ($canviewsubmission and $submission = $workshop->get_submission_by_author($user->id)) {
493
            $title      = format_string($submission->title);
494
            $url        = $workshop->submission_url($submission->id);
495
            $link       = html_writer::link($url, $title);
496
            $info       = get_string('submission', 'workshop').': '.$link;
497
            echo html_writer::tag('li', $info, array('class'=>'submission'));
498
        }
499
    }
500
 
501
    if (has_capability('mod/workshop:viewallassessments', $workshop->context)) {
502
        if ($assessments = $workshop->get_assessments_by_reviewer($user->id)) {
503
            foreach ($assessments as $assessment) {
504
                $a = new stdclass();
505
                $a->submissionurl = $workshop->submission_url($assessment->submissionid)->out();
506
                $a->assessmenturl = $workshop->assess_url($assessment->id)->out();
507
                $a->submissiontitle = s($assessment->submissiontitle);
508
                echo html_writer::tag('li', get_string('assessmentofsubmission', 'workshop', $a));
509
            }
510
        }
511
    }
512
}
513
 
514
/**
515
 * Given a course and a time, this module should find recent activity
516
 * that has occurred in workshop activities and print it out.
517
 * Return true if there was output, or false is there was none.
518
 *
519
 * @param stdClass $course
520
 * @param bool $viewfullnames
521
 * @param int $timestart
522
 * @return boolean
523
 */
524
function workshop_print_recent_activity($course, $viewfullnames, $timestart) {
525
    global $CFG, $USER, $DB, $OUTPUT;
526
 
527
    $userfieldsapi = \core_user\fields::for_name();
528
    $authoramefields = $userfieldsapi->get_sql('author', false, 'author', '', false)->selects;
529
    $reviewerfields = $userfieldsapi->get_sql('reviewer', false, 'reviewer', '', false)->selects;
530
 
531
    $sql = "SELECT s.id AS submissionid, s.title AS submissiontitle, s.timemodified AS submissionmodified,
532
                   author.id AS authorid, $authoramefields, a.id AS assessmentid, a.timemodified AS assessmentmodified,
533
                   reviewer.id AS reviewerid, $reviewerfields, cm.id AS cmid
534
              FROM {workshop} w
535
        INNER JOIN {course_modules} cm ON cm.instance = w.id
536
        INNER JOIN {modules} md ON md.id = cm.module
537
        INNER JOIN {workshop_submissions} s ON s.workshopid = w.id
538
        INNER JOIN {user} author ON s.authorid = author.id
539
         LEFT JOIN {workshop_assessments} a ON a.submissionid = s.id
540
         LEFT JOIN {user} reviewer ON a.reviewerid = reviewer.id
541
             WHERE cm.course = ?
542
                   AND md.name = 'workshop'
543
                   AND s.example = 0
544
                   AND (s.timemodified > ? OR a.timemodified > ?)
545
          ORDER BY s.timemodified";
546
 
547
    $rs = $DB->get_recordset_sql($sql, array($course->id, $timestart, $timestart));
548
 
549
    $modinfo = get_fast_modinfo($course); // reference needed because we might load the groups
550
 
551
    $submissions = array(); // recent submissions indexed by submission id
552
    $assessments = array(); // recent assessments indexed by assessment id
553
    $users       = array();
554
 
555
    foreach ($rs as $activity) {
556
        if (!array_key_exists($activity->cmid, $modinfo->cms)) {
557
            // this should not happen but just in case
558
            continue;
559
        }
560
 
561
        $cm = $modinfo->cms[$activity->cmid];
562
        if (!$cm->uservisible) {
563
            continue;
564
        }
565
 
566
        // remember all user names we can use later
567
        if (empty($users[$activity->authorid])) {
568
            $u = new stdclass();
569
            $users[$activity->authorid] = username_load_fields_from_object($u, $activity, 'author');
570
        }
571
        if ($activity->reviewerid and empty($users[$activity->reviewerid])) {
572
            $u = new stdclass();
573
            $users[$activity->reviewerid] = username_load_fields_from_object($u, $activity, 'reviewer');
574
        }
575
 
576
        $context = context_module::instance($cm->id);
577
        $groupmode = groups_get_activity_groupmode($cm, $course);
578
 
579
        if ($activity->submissionmodified > $timestart and empty($submissions[$activity->submissionid])) {
580
            $s = new stdclass();
581
            $s->title = $activity->submissiontitle;
582
            $s->authorid = $activity->authorid;
583
            $s->timemodified = $activity->submissionmodified;
584
            $s->cmid = $activity->cmid;
585
            if ($activity->authorid == $USER->id || has_capability('mod/workshop:viewauthornames', $context)) {
586
                $s->authornamevisible = true;
587
            } else {
588
                $s->authornamevisible = false;
589
            }
590
 
591
            // the following do-while wrapper allows to break from deeply nested if-statements
592
            do {
593
                if ($s->authorid === $USER->id) {
594
                    // own submissions always visible
595
                    $submissions[$activity->submissionid] = $s;
596
                    break;
597
                }
598
 
599
                if (has_capability('mod/workshop:viewallsubmissions', $context)) {
600
                    if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
601
                        if (isguestuser()) {
602
                            // shortcut - guest user does not belong into any group
603
                            break;
604
                        }
605
 
606
                        // this might be slow - show only submissions by users who share group with me in this cm
607
                        if (!$modinfo->get_groups($cm->groupingid)) {
608
                            break;
609
                        }
610
                        $authorsgroups = groups_get_all_groups($course->id, $s->authorid, $cm->groupingid);
611
                        if (is_array($authorsgroups)) {
612
                            $authorsgroups = array_keys($authorsgroups);
613
                            $intersect = array_intersect($authorsgroups, $modinfo->get_groups($cm->groupingid));
614
                            if (empty($intersect)) {
615
                                break;
616
                            } else {
617
                                // can see all submissions and shares a group with the author
618
                                $submissions[$activity->submissionid] = $s;
619
                                break;
620
                            }
621
                        }
622
 
623
                    } else {
624
                        // can see all submissions from all groups
625
                        $submissions[$activity->submissionid] = $s;
626
                    }
627
                }
628
            } while (0);
629
        }
630
 
631
        if ($activity->assessmentmodified > $timestart and empty($assessments[$activity->assessmentid])) {
632
            $a = new stdclass();
633
            $a->submissionid = $activity->submissionid;
634
            $a->submissiontitle = $activity->submissiontitle;
635
            $a->reviewerid = $activity->reviewerid;
636
            $a->timemodified = $activity->assessmentmodified;
637
            $a->cmid = $activity->cmid;
638
            if ($activity->reviewerid == $USER->id || has_capability('mod/workshop:viewreviewernames', $context)) {
639
                $a->reviewernamevisible = true;
640
            } else {
641
                $a->reviewernamevisible = false;
642
            }
643
 
644
            // the following do-while wrapper allows to break from deeply nested if-statements
645
            do {
646
                if ($a->reviewerid === $USER->id) {
647
                    // own assessments always visible
648
                    $assessments[$activity->assessmentid] = $a;
649
                    break;
650
                }
651
 
652
                if (has_capability('mod/workshop:viewallassessments', $context)) {
653
                    if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
654
                        if (isguestuser()) {
655
                            // shortcut - guest user does not belong into any group
656
                            break;
657
                        }
658
 
659
                        // this might be slow - show only submissions by users who share group with me in this cm
660
                        if (!$modinfo->get_groups($cm->groupingid)) {
661
                            break;
662
                        }
663
                        $reviewersgroups = groups_get_all_groups($course->id, $a->reviewerid, $cm->groupingid);
664
                        if (is_array($reviewersgroups)) {
665
                            $reviewersgroups = array_keys($reviewersgroups);
666
                            $intersect = array_intersect($reviewersgroups, $modinfo->get_groups($cm->groupingid));
667
                            if (empty($intersect)) {
668
                                break;
669
                            } else {
670
                                // can see all assessments and shares a group with the reviewer
671
                                $assessments[$activity->assessmentid] = $a;
672
                                break;
673
                            }
674
                        }
675
 
676
                    } else {
677
                        // can see all assessments from all groups
678
                        $assessments[$activity->assessmentid] = $a;
679
                    }
680
                }
681
            } while (0);
682
        }
683
    }
684
    $rs->close();
685
 
686
    $shown = false;
687
 
688
    if (!empty($submissions)) {
689
        $shown = true;
690
        echo $OUTPUT->heading(get_string('recentsubmissions', 'workshop') . ':', 6);
691
        foreach ($submissions as $id => $submission) {
692
            $link = new moodle_url('/mod/workshop/submission.php', array('id'=>$id, 'cmid'=>$submission->cmid));
693
            if ($submission->authornamevisible) {
694
                $author = $users[$submission->authorid];
695
            } else {
696
                $author = null;
697
            }
698
            print_recent_activity_note($submission->timemodified, $author, $submission->title, $link->out(), false, $viewfullnames);
699
        }
700
    }
701
 
702
    if (!empty($assessments)) {
703
        $shown = true;
704
        echo $OUTPUT->heading(get_string('recentassessments', 'workshop') . ':', 6);
705
        core_collator::asort_objects_by_property($assessments, 'timemodified');
706
        foreach ($assessments as $id => $assessment) {
707
            $link = new moodle_url('/mod/workshop/assessment.php', array('asid' => $id));
708
            if ($assessment->reviewernamevisible) {
709
                $reviewer = $users[$assessment->reviewerid];
710
            } else {
711
                $reviewer = null;
712
            }
713
            print_recent_activity_note($assessment->timemodified, $reviewer, $assessment->submissiontitle, $link->out(), false, $viewfullnames);
714
        }
715
    }
716
 
717
    if ($shown) {
718
        return true;
719
    }
720
 
721
    return false;
722
}
723
 
724
/**
725
 * Returns all activity in course workshops since a given time
726
 *
727
 * @param array $activities sequentially indexed array of objects
728
 * @param int $index
729
 * @param int $timestart
730
 * @param int $courseid
731
 * @param int $cmid
732
 * @param int $userid defaults to 0
733
 * @param int $groupid defaults to 0
734
 * @return void adds items into $activities and increases $index
735
 */
736
function workshop_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid=0, $groupid=0) {
737
    global $CFG, $COURSE, $USER, $DB;
738
 
739
    if ($COURSE->id == $courseid) {
740
        $course = $COURSE;
741
    } else {
742
        $course = $DB->get_record('course', array('id'=>$courseid));
743
    }
744
 
745
    $modinfo = get_fast_modinfo($course);
746
 
747
    $cm = $modinfo->cms[$cmid];
748
 
749
    $params = array();
750
    if ($userid) {
751
        $userselect = "AND (author.id = :authorid OR reviewer.id = :reviewerid)";
752
        $params['authorid'] = $userid;
753
        $params['reviewerid'] = $userid;
754
    } else {
755
        $userselect = "";
756
    }
757
 
758
    if ($groupid) {
759
        $groupselect = "AND (authorgroupmembership.groupid = :authorgroupid OR reviewergroupmembership.groupid = :reviewergroupid)";
760
        $groupjoin   = "LEFT JOIN {groups_members} authorgroupmembership ON authorgroupmembership.userid = author.id
761
                        LEFT JOIN {groups_members} reviewergroupmembership ON reviewergroupmembership.userid = reviewer.id";
762
        $params['authorgroupid'] = $groupid;
763
        $params['reviewergroupid'] = $groupid;
764
    } else {
765
        $groupselect = "";
766
        $groupjoin   = "";
767
    }
768
 
769
    $params['cminstance'] = $cm->instance;
770
    $params['submissionmodified'] = $timestart;
771
    $params['assessmentmodified'] = $timestart;
772
 
773
    $userfieldsapi = \core_user\fields::for_name();
774
    $authornamefields = $userfieldsapi->get_sql('author', false, 'author', '', false)->selects;
775
    $reviewerfields = $userfieldsapi->get_sql('reviewer', false, 'reviewer', '', false)->selects;
776
 
777
    $sql = "SELECT s.id AS submissionid, s.title AS submissiontitle, s.timemodified AS submissionmodified,
778
                   author.id AS authorid, $authornamefields, author.picture AS authorpicture, author.imagealt AS authorimagealt,
779
                   author.email AS authoremail, a.id AS assessmentid, a.timemodified AS assessmentmodified,
780
                   reviewer.id AS reviewerid, $reviewerfields, reviewer.picture AS reviewerpicture,
781
                   reviewer.imagealt AS reviewerimagealt, reviewer.email AS revieweremail
782
              FROM {workshop_submissions} s
783
        INNER JOIN {workshop} w ON s.workshopid = w.id
784
        INNER JOIN {user} author ON s.authorid = author.id
785
         LEFT JOIN {workshop_assessments} a ON a.submissionid = s.id
786
         LEFT JOIN {user} reviewer ON a.reviewerid = reviewer.id
787
        $groupjoin
788
             WHERE w.id = :cminstance
789
                   AND s.example = 0
790
                   $userselect $groupselect
791
                   AND (s.timemodified > :submissionmodified OR a.timemodified > :assessmentmodified)
792
          ORDER BY s.timemodified ASC, a.timemodified ASC";
793
 
794
    $rs = $DB->get_recordset_sql($sql, $params);
795
 
796
    $groupmode       = groups_get_activity_groupmode($cm, $course);
797
    $context         = context_module::instance($cm->id);
798
    $grader          = has_capability('moodle/grade:viewall', $context);
799
    $accessallgroups = has_capability('moodle/site:accessallgroups', $context);
800
    $viewauthors     = has_capability('mod/workshop:viewauthornames', $context);
801
    $viewreviewers   = has_capability('mod/workshop:viewreviewernames', $context);
802
 
803
    $submissions = array(); // recent submissions indexed by submission id
804
    $assessments = array(); // recent assessments indexed by assessment id
805
    $users       = array();
806
 
807
    foreach ($rs as $activity) {
808
 
809
        // remember all user names we can use later
810
        if (empty($users[$activity->authorid])) {
811
            $u = new stdclass();
812
            $additionalfields = explode(',', implode(',', \core_user\fields::get_picture_fields()));
813
            $u = username_load_fields_from_object($u, $activity, 'author', $additionalfields);
814
            $users[$activity->authorid] = $u;
815
        }
816
        if ($activity->reviewerid and empty($users[$activity->reviewerid])) {
817
            $u = new stdclass();
818
            $additionalfields = explode(',', implode(',', \core_user\fields::get_picture_fields()));
819
            $u = username_load_fields_from_object($u, $activity, 'reviewer', $additionalfields);
820
            $users[$activity->reviewerid] = $u;
821
        }
822
 
823
        if ($activity->submissionmodified > $timestart and empty($submissions[$activity->submissionid])) {
824
            $s = new stdclass();
825
            $s->id = $activity->submissionid;
826
            $s->title = $activity->submissiontitle;
827
            $s->authorid = $activity->authorid;
828
            $s->timemodified = $activity->submissionmodified;
829
            if ($activity->authorid == $USER->id || has_capability('mod/workshop:viewauthornames', $context)) {
830
                $s->authornamevisible = true;
831
            } else {
832
                $s->authornamevisible = false;
833
            }
834
 
835
            // the following do-while wrapper allows to break from deeply nested if-statements
836
            do {
837
                if ($s->authorid === $USER->id) {
838
                    // own submissions always visible
839
                    $submissions[$activity->submissionid] = $s;
840
                    break;
841
                }
842
 
843
                if (has_capability('mod/workshop:viewallsubmissions', $context)) {
844
                    if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
845
                        if (isguestuser()) {
846
                            // shortcut - guest user does not belong into any group
847
                            break;
848
                        }
849
 
850
                        // this might be slow - show only submissions by users who share group with me in this cm
851
                        if (!$modinfo->get_groups($cm->groupingid)) {
852
                            break;
853
                        }
854
                        $authorsgroups = groups_get_all_groups($course->id, $s->authorid, $cm->groupingid);
855
                        if (is_array($authorsgroups)) {
856
                            $authorsgroups = array_keys($authorsgroups);
857
                            $intersect = array_intersect($authorsgroups, $modinfo->get_groups($cm->groupingid));
858
                            if (empty($intersect)) {
859
                                break;
860
                            } else {
861
                                // can see all submissions and shares a group with the author
862
                                $submissions[$activity->submissionid] = $s;
863
                                break;
864
                            }
865
                        }
866
 
867
                    } else {
868
                        // can see all submissions from all groups
869
                        $submissions[$activity->submissionid] = $s;
870
                    }
871
                }
872
            } while (0);
873
        }
874
 
875
        if ($activity->assessmentmodified > $timestart and empty($assessments[$activity->assessmentid])) {
876
            $a = new stdclass();
877
            $a->id = $activity->assessmentid;
878
            $a->submissionid = $activity->submissionid;
879
            $a->submissiontitle = $activity->submissiontitle;
880
            $a->reviewerid = $activity->reviewerid;
881
            $a->timemodified = $activity->assessmentmodified;
882
            if ($activity->reviewerid == $USER->id || has_capability('mod/workshop:viewreviewernames', $context)) {
883
                $a->reviewernamevisible = true;
884
            } else {
885
                $a->reviewernamevisible = false;
886
            }
887
 
888
            // the following do-while wrapper allows to break from deeply nested if-statements
889
            do {
890
                if ($a->reviewerid === $USER->id) {
891
                    // own assessments always visible
892
                    $assessments[$activity->assessmentid] = $a;
893
                    break;
894
                }
895
 
896
                if (has_capability('mod/workshop:viewallassessments', $context)) {
897
                    if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
898
                        if (isguestuser()) {
899
                            // shortcut - guest user does not belong into any group
900
                            break;
901
                        }
902
 
903
                        // this might be slow - show only submissions by users who share group with me in this cm
904
                        if (!$modinfo->get_groups($cm->groupingid)) {
905
                            break;
906
                        }
907
                        $reviewersgroups = groups_get_all_groups($course->id, $a->reviewerid, $cm->groupingid);
908
                        if (is_array($reviewersgroups)) {
909
                            $reviewersgroups = array_keys($reviewersgroups);
910
                            $intersect = array_intersect($reviewersgroups, $modinfo->get_groups($cm->groupingid));
911
                            if (empty($intersect)) {
912
                                break;
913
                            } else {
914
                                // can see all assessments and shares a group with the reviewer
915
                                $assessments[$activity->assessmentid] = $a;
916
                                break;
917
                            }
918
                        }
919
 
920
                    } else {
921
                        // can see all assessments from all groups
922
                        $assessments[$activity->assessmentid] = $a;
923
                    }
924
                }
925
            } while (0);
926
        }
927
    }
928
    $rs->close();
929
 
930
    $workshopname = format_string($cm->name, true);
931
 
932
    if ($grader) {
933
        require_once($CFG->libdir.'/gradelib.php');
934
        $grades = grade_get_grades($courseid, 'mod', 'workshop', $cm->instance, array_keys($users));
935
    }
936
 
937
    foreach ($submissions as $submission) {
938
        $tmpactivity                = new stdclass();
939
        $tmpactivity->type          = 'workshop';
940
        $tmpactivity->cmid          = $cm->id;
941
        $tmpactivity->name          = $workshopname;
942
        $tmpactivity->sectionnum    = $cm->sectionnum;
943
        $tmpactivity->timestamp     = $submission->timemodified;
944
        $tmpactivity->subtype       = 'submission';
945
        $tmpactivity->content       = $submission;
946
        if ($grader) {
947
            $tmpactivity->grade     = $grades->items[0]->grades[$submission->authorid]->str_long_grade;
948
        }
949
        if ($submission->authornamevisible and !empty($users[$submission->authorid])) {
950
            $tmpactivity->user      = $users[$submission->authorid];
951
        }
952
        $activities[$index++]       = $tmpactivity;
953
    }
954
 
955
    foreach ($assessments as $assessment) {
956
        $tmpactivity                = new stdclass();
957
        $tmpactivity->type          = 'workshop';
958
        $tmpactivity->cmid          = $cm->id;
959
        $tmpactivity->name          = $workshopname;
960
        $tmpactivity->sectionnum    = $cm->sectionnum;
961
        $tmpactivity->timestamp     = $assessment->timemodified;
962
        $tmpactivity->subtype       = 'assessment';
963
        $tmpactivity->content       = $assessment;
964
        if ($grader) {
965
            $tmpactivity->grade     = $grades->items[1]->grades[$assessment->reviewerid]->str_long_grade;
966
        }
967
        if ($assessment->reviewernamevisible and !empty($users[$assessment->reviewerid])) {
968
            $tmpactivity->user      = $users[$assessment->reviewerid];
969
        }
970
        $activities[$index++]       = $tmpactivity;
971
    }
972
}
973
 
974
/**
975
 * Print single activity item prepared by {@see workshop_get_recent_mod_activity()}
976
 */
977
function workshop_print_recent_mod_activity($activity, $courseid, $detail, $modnames, $viewfullnames) {
978
    global $CFG, $OUTPUT;
979
 
980
    if (!empty($activity->user)) {
981
        echo html_writer::tag('div', $OUTPUT->user_picture($activity->user, array('courseid'=>$courseid)),
982
                array('style' => 'float: left; padding: 7px;'));
983
    }
984
 
985
    if ($activity->subtype == 'submission') {
986
        echo html_writer::start_tag('div', array('class'=>'submission', 'style'=>'padding: 7px; float:left;'));
987
 
988
        if ($detail) {
989
            echo html_writer::start_tag('h4', array('class'=>'workshop'));
990
            $url = new moodle_url('/mod/workshop/view.php', array('id'=>$activity->cmid));
991
            $name = s($activity->name);
992
            echo $OUTPUT->image_icon('monologo', $name, $activity->type);
993
            echo ' ' . $modnames[$activity->type];
994
            echo html_writer::link($url, $name, array('class'=>'name', 'style'=>'margin-left: 5px'));
995
            echo html_writer::end_tag('h4');
996
        }
997
 
998
        echo html_writer::start_tag('div', array('class'=>'title'));
999
        $url = new moodle_url('/mod/workshop/submission.php', array('cmid'=>$activity->cmid, 'id'=>$activity->content->id));
1000
        $name = s($activity->content->title);
1001
        echo html_writer::tag('strong', html_writer::link($url, $name));
1002
        echo html_writer::end_tag('div');
1003
 
1004
        if (!empty($activity->user)) {
1005
            echo html_writer::start_tag('div', array('class'=>'user'));
1006
            $url = new moodle_url('/user/view.php', array('id'=>$activity->user->id, 'course'=>$courseid));
1007
            $name = fullname($activity->user);
1008
            $link = html_writer::link($url, $name);
1009
            echo get_string('submissionby', 'workshop', $link);
1010
            echo ' - '.userdate($activity->timestamp);
1011
            echo html_writer::end_tag('div');
1012
        } else {
1013
            echo html_writer::start_tag('div', array('class'=>'anonymous'));
1014
            echo get_string('submission', 'workshop');
1015
            echo ' - '.userdate($activity->timestamp);
1016
            echo html_writer::end_tag('div');
1017
        }
1018
 
1019
        echo html_writer::end_tag('div');
1020
    }
1021
 
1022
    if ($activity->subtype == 'assessment') {
1023
        echo html_writer::start_tag('div', array('class'=>'assessment', 'style'=>'padding: 7px; float:left;'));
1024
 
1025
        if ($detail) {
1026
            echo html_writer::start_tag('h4', array('class'=>'workshop'));
1027
            $url = new moodle_url('/mod/workshop/view.php', array('id'=>$activity->cmid));
1028
            $name = s($activity->name);
1029
            echo $OUTPUT->image_icon('monologo', $name, $activity->type);
1030
            echo ' ' . $modnames[$activity->type];
1031
            echo html_writer::link($url, $name, array('class'=>'name', 'style'=>'margin-left: 5px'));
1032
            echo html_writer::end_tag('h4');
1033
        }
1034
 
1035
        echo html_writer::start_tag('div', array('class'=>'title'));
1036
        $url = new moodle_url('/mod/workshop/assessment.php', array('asid'=>$activity->content->id));
1037
        $name = s($activity->content->submissiontitle);
1038
        echo html_writer::tag('em', html_writer::link($url, $name));
1039
        echo html_writer::end_tag('div');
1040
 
1041
        if (!empty($activity->user)) {
1042
            echo html_writer::start_tag('div', array('class'=>'user'));
1043
            $url = new moodle_url('/user/view.php', array('id'=>$activity->user->id, 'course'=>$courseid));
1044
            $name = fullname($activity->user);
1045
            $link = html_writer::link($url, $name);
1046
            echo get_string('assessmentbyfullname', 'workshop', $link);
1047
            echo ' - '.userdate($activity->timestamp);
1048
            echo html_writer::end_tag('div');
1049
        } else {
1050
            echo html_writer::start_tag('div', array('class'=>'anonymous'));
1051
            echo get_string('assessment', 'workshop');
1052
            echo ' - '.userdate($activity->timestamp);
1053
            echo html_writer::end_tag('div');
1054
        }
1055
 
1056
        echo html_writer::end_tag('div');
1057
    }
1058
 
1059
    echo html_writer::empty_tag('br', array('style'=>'clear:both'));
1060
}
1061
 
1062
/**
1063
 * @deprecated since Moodle 3.8
1064
 */
1065
function workshop_scale_used() {
1066
    throw new coding_exception('workshop_scale_used() can not be used anymore. Plugins can implement ' .
1067
        '<modname>_scale_used_anywhere, all implementations of <modname>_scale_used are now ignored');
1068
}
1069
 
1070
/**
1071
 * Is a given scale used by any instance of workshop?
1072
 *
1073
 * The function asks all installed grading strategy subplugins. The workshop
1074
 * core itself does not use scales. Both grade for submission and grade for
1075
 * assessments do not use scales.
1076
 *
1077
 * @param int $scaleid id of the scale to check
1078
 * @return bool
1079
 */
1080
function workshop_scale_used_anywhere($scaleid) {
1081
    global $CFG; // other files included from here
1082
 
1083
    $strategies = core_component::get_plugin_list('workshopform');
1084
    foreach ($strategies as $strategy => $strategypath) {
1085
        $strategylib = $strategypath . '/lib.php';
1086
        if (is_readable($strategylib)) {
1087
            require_once($strategylib);
1088
        } else {
1089
            throw new coding_exception('the grading forms subplugin must contain library ' . $strategylib);
1090
        }
1091
        $classname = 'workshop_' . $strategy . '_strategy';
1092
        if (method_exists($classname, 'scale_used')) {
1093
            if (call_user_func(array($classname, 'scale_used'), $scaleid)) {
1094
                // no need to include any other files - scale is used
1095
                return true;
1096
            }
1097
        }
1098
    }
1099
 
1100
    return false;
1101
}
1102
 
1103
////////////////////////////////////////////////////////////////////////////////
1104
// Gradebook API                                                              //
1105
////////////////////////////////////////////////////////////////////////////////
1106
 
1107
/**
1108
 * Creates or updates grade items for the give workshop instance
1109
 *
1110
 * Needed by grade_update_mod_grades() in lib/gradelib.php. Also used by
1111
 * {@link workshop_update_grades()}.
1112
 *
1113
 * @param stdClass $workshop instance object with extra cmidnumber property
1114
 * @param stdClass $submissiongrades data for the first grade item
1115
 * @param stdClass $assessmentgrades data for the second grade item
1116
 * @return void
1117
 */
1118
function workshop_grade_item_update(stdclass $workshop, $submissiongrades=null, $assessmentgrades=null) {
1119
    global $CFG;
1120
    require_once($CFG->libdir.'/gradelib.php');
1121
 
1122
    $a = new stdclass();
1123
    $a->workshopname = clean_param($workshop->name, PARAM_NOTAGS);
1124
 
1125
    $item = array();
1126
    $item['itemname'] = get_string('gradeitemsubmission', 'workshop', $a);
1127
    $item['gradetype'] = GRADE_TYPE_VALUE;
1128
    $item['grademax']  = $workshop->grade;
1129
    $item['grademin']  = 0;
1130
    grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 0, $submissiongrades , $item);
1131
 
1132
    $item = array();
1133
    $item['itemname'] = get_string('gradeitemassessment', 'workshop', $a);
1134
    $item['gradetype'] = GRADE_TYPE_VALUE;
1135
    $item['grademax']  = $workshop->gradinggrade;
1136
    $item['grademin']  = 0;
1137
    grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 1, $assessmentgrades, $item);
1138
}
1139
 
1140
/**
1141
 * Update workshop grades in the gradebook
1142
 *
1143
 * Needed by grade_update_mod_grades() in lib/gradelib.php
1144
 *
1145
 * @category grade
1146
 * @param stdClass $workshop instance object with extra cmidnumber and modname property
1147
 * @param int $userid        update grade of specific user only, 0 means all participants
1148
 * @return void
1149
 */
1150
function workshop_update_grades(stdclass $workshop, $userid=0) {
1151
    global $CFG, $DB;
1152
    require_once($CFG->libdir.'/gradelib.php');
1153
 
1154
    $whereuser = $userid ? ' AND authorid = :userid' : '';
1155
    $params = array('workshopid' => $workshop->id, 'userid' => $userid);
1156
    $sql = 'SELECT authorid, grade, gradeover, gradeoverby, feedbackauthor, feedbackauthorformat, timemodified, timegraded
1157
              FROM {workshop_submissions}
1158
             WHERE workshopid = :workshopid AND example=0' . $whereuser;
1159
    $records = $DB->get_records_sql($sql, $params);
1160
    $submissiongrades = array();
1161
    foreach ($records as $record) {
1162
        $grade = new stdclass();
1163
        $grade->userid = $record->authorid;
1164
        if (!is_null($record->gradeover)) {
1165
            $grade->rawgrade = grade_floatval($workshop->grade * $record->gradeover / 100);
1166
            $grade->usermodified = $record->gradeoverby;
1167
        } else {
1168
            $grade->rawgrade = grade_floatval($workshop->grade * $record->grade / 100);
1169
        }
1170
        $grade->feedback = $record->feedbackauthor;
1171
        $grade->feedbackformat = $record->feedbackauthorformat;
1172
        $grade->datesubmitted = $record->timemodified;
1173
        $grade->dategraded = $record->timegraded;
1174
        $submissiongrades[$record->authorid] = $grade;
1175
    }
1176
 
1177
    $whereuser = $userid ? ' AND userid = :userid' : '';
1178
    $params = array('workshopid' => $workshop->id, 'userid' => $userid);
1179
    $sql = 'SELECT userid, gradinggrade, timegraded
1180
              FROM {workshop_aggregations}
1181
             WHERE workshopid = :workshopid' . $whereuser;
1182
    $records = $DB->get_records_sql($sql, $params);
1183
    $assessmentgrades = array();
1184
    foreach ($records as $record) {
1185
        $grade = new stdclass();
1186
        $grade->userid = $record->userid;
1187
        $grade->rawgrade = grade_floatval($workshop->gradinggrade * $record->gradinggrade / 100);
1188
        $grade->dategraded = $record->timegraded;
1189
        $assessmentgrades[$record->userid] = $grade;
1190
    }
1191
 
1192
    workshop_grade_item_update($workshop, $submissiongrades, $assessmentgrades);
1193
}
1194
 
1195
/**
1196
 * Update the grade items categories if they are changed via mod_form.php
1197
 *
1198
 * We must do it manually here in the workshop module because modedit supports only
1199
 * single grade item while we use two.
1200
 *
1201
 * @param stdClass $workshop An object from the form in mod_form.php
1202
 */
1203
function workshop_grade_item_category_update($workshop) {
1204
 
1205
    $gradeitems = grade_item::fetch_all(array(
1206
        'itemtype'      => 'mod',
1207
        'itemmodule'    => 'workshop',
1208
        'iteminstance'  => $workshop->id,
1209
        'courseid'      => $workshop->course));
1210
 
1211
    if (!empty($gradeitems)) {
1212
        foreach ($gradeitems as $gradeitem) {
1213
            if ($gradeitem->itemnumber == 0) {
1214
                if (isset($workshop->submissiongradepass) &&
1215
                        $gradeitem->gradepass != $workshop->submissiongradepass) {
1216
                    $gradeitem->gradepass = $workshop->submissiongradepass;
1217
                    $gradeitem->update();
1218
                }
1219
                if ($gradeitem->categoryid != $workshop->gradecategory) {
1220
                    $gradeitem->set_parent($workshop->gradecategory);
1221
                }
1222
            } else if ($gradeitem->itemnumber == 1) {
1223
                if (isset($workshop->gradinggradepass) &&
1224
                        $gradeitem->gradepass != $workshop->gradinggradepass) {
1225
                    $gradeitem->gradepass = $workshop->gradinggradepass;
1226
                    $gradeitem->update();
1227
                }
1228
                if ($gradeitem->categoryid != $workshop->gradinggradecategory) {
1229
                    $gradeitem->set_parent($workshop->gradinggradecategory);
1230
                }
1231
            }
1232
        }
1233
    }
1234
}
1235
 
1236
////////////////////////////////////////////////////////////////////////////////
1237
// File API                                                                   //
1238
////////////////////////////////////////////////////////////////////////////////
1239
 
1240
/**
1241
 * Returns the lists of all browsable file areas within the given module context
1242
 *
1243
 * The file area workshop_intro for the activity introduction field is added automatically
1244
 * by {@link file_browser::get_file_info_context_module()}
1245
 *
1246
 * @package  mod_workshop
1247
 * @category files
1248
 *
1249
 * @param stdClass $course
1250
 * @param stdClass $cm
1251
 * @param stdClass $context
1252
 * @return array of [(string)filearea] => (string)description
1253
 */
1254
function workshop_get_file_areas($course, $cm, $context) {
1255
    $areas = array();
1256
    $areas['instructauthors']          = get_string('areainstructauthors', 'workshop');
1257
    $areas['instructreviewers']        = get_string('areainstructreviewers', 'workshop');
1258
    $areas['submission_content']       = get_string('areasubmissioncontent', 'workshop');
1259
    $areas['submission_attachment']    = get_string('areasubmissionattachment', 'workshop');
1260
    $areas['conclusion']               = get_string('areaconclusion', 'workshop');
1261
    $areas['overallfeedback_content']  = get_string('areaoverallfeedbackcontent', 'workshop');
1262
    $areas['overallfeedback_attachment'] = get_string('areaoverallfeedbackattachment', 'workshop');
1263
 
1264
    return $areas;
1265
}
1266
 
1267
/**
1268
 * Serves the files from the workshop file areas
1269
 *
1270
 * Apart from module intro (handled by pluginfile.php automatically), workshop files may be
1271
 * media inserted into submission content (like images) and submission attachments. For these two,
1272
 * the fileareas submission_content and submission_attachment are used.
1273
 * Besides that, areas instructauthors, instructreviewers and conclusion contain the media
1274
 * embedded using the mod_form.php.
1275
 *
1276
 * @package  mod_workshop
1277
 * @category files
1278
 *
1279
 * @param stdClass $course the course object
1280
 * @param stdClass $cm the course module object
1281
 * @param stdClass $context the workshop's context
1282
 * @param string $filearea the name of the file area
1283
 * @param array $args extra arguments (itemid, path)
1284
 * @param bool $forcedownload whether or not force download
1285
 * @param array $options additional options affecting the file serving
1286
 * @return bool false if the file not found, just send the file otherwise and do not return anything
1287
 */
1288
function workshop_pluginfile($course, $cm, $context, $filearea, array $args, $forcedownload, array $options=array()) {
1289
    global $DB, $CFG, $USER;
1290
 
1291
    if ($context->contextlevel != CONTEXT_MODULE) {
1292
        return false;
1293
    }
1294
 
1295
    require_login($course, true, $cm);
1296
 
1297
    if ($filearea === 'instructauthors' or $filearea === 'instructreviewers' or $filearea === 'conclusion') {
1298
        // The $args are supposed to contain just the path, not the item id.
1299
        $relativepath = implode('/', $args);
1300
        $fullpath = "/$context->id/mod_workshop/$filearea/0/$relativepath";
1301
 
1302
        $fs = get_file_storage();
1303
        if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1304
            send_file_not_found();
1305
        }
1306
        send_stored_file($file, null, 0, $forcedownload, $options);
1307
 
1308
    } else if ($filearea === 'submission_content' or $filearea === 'submission_attachment') {
1309
        $itemid = (int)array_shift($args);
1310
        if (!$workshop = $DB->get_record('workshop', array('id' => $cm->instance))) {
1311
            return false;
1312
        }
1313
        if (!$submission = $DB->get_record('workshop_submissions', array('id' => $itemid, 'workshopid' => $workshop->id))) {
1314
            return false;
1315
        }
1316
 
1317
        // make sure the user is allowed to see the file
1318
        if (empty($submission->example)) {
1319
            if ($USER->id != $submission->authorid) {
1320
                if ($submission->published == 1 and $workshop->phase == 50
1321
                        and has_capability('mod/workshop:viewpublishedsubmissions', $context)) {
1322
                    // Published submission, we can go (workshop does not take the group mode
1323
                    // into account in this case yet).
1324
                } else if (!$DB->record_exists('workshop_assessments', array('submissionid' => $submission->id, 'reviewerid' => $USER->id))) {
1325
                    if (!has_capability('mod/workshop:viewallsubmissions', $context)) {
1326
                        send_file_not_found();
1327
                    } else {
1328
                        $gmode = groups_get_activity_groupmode($cm, $course);
1329
                        if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1330
                            // check there is at least one common group with both the $USER
1331
                            // and the submission author
1332
                            $sql = "SELECT 'x'
1333
                                      FROM {workshop_submissions} s
1334
                                      JOIN {user} a ON (a.id = s.authorid)
1335
                                      JOIN {groups_members} agm ON (a.id = agm.userid)
1336
                                      JOIN {user} u ON (u.id = ?)
1337
                                      JOIN {groups_members} ugm ON (u.id = ugm.userid)
1338
                                     WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1339
                            $params = array($USER->id, $workshop->id, $submission->id);
1340
                            if (!$DB->record_exists_sql($sql, $params)) {
1341
                                send_file_not_found();
1342
                            }
1343
                        }
1344
                    }
1345
                }
1346
            }
1347
        }
1348
 
1349
        $fs = get_file_storage();
1350
        $relativepath = implode('/', $args);
1351
        $fullpath = "/$context->id/mod_workshop/$filearea/$itemid/$relativepath";
1352
        if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1353
            return false;
1354
        }
1355
        // finally send the file
1356
        // these files are uploaded by students - forcing download for security reasons
1357
        send_stored_file($file, 0, 0, true, $options);
1358
 
1359
    } else if ($filearea === 'overallfeedback_content' or $filearea === 'overallfeedback_attachment') {
1360
        $itemid = (int)array_shift($args);
1361
        if (!$workshop = $DB->get_record('workshop', array('id' => $cm->instance))) {
1362
            return false;
1363
        }
1364
        if (!$assessment = $DB->get_record('workshop_assessments', array('id' => $itemid))) {
1365
            return false;
1366
        }
1367
        if (!$submission = $DB->get_record('workshop_submissions', array('id' => $assessment->submissionid, 'workshopid' => $workshop->id))) {
1368
            return false;
1369
        }
1370
 
1371
        if ($USER->id == $assessment->reviewerid) {
1372
            // Reviewers can always see their own files.
1373
        } else if ($USER->id == $submission->authorid and $workshop->phase == 50) {
1374
            // Authors can see the feedback once the workshop is closed.
1375
        } else if (!empty($submission->example) and $assessment->weight == 1) {
1376
            // Reference assessments of example submissions can be displayed.
1377
        } else if (!has_capability('mod/workshop:viewallassessments', $context)) {
1378
            send_file_not_found();
1379
        } else {
1380
            $gmode = groups_get_activity_groupmode($cm, $course);
1381
            if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1382
                // Check there is at least one common group with both the $USER
1383
                // and the submission author.
1384
                $sql = "SELECT 'x'
1385
                          FROM {workshop_submissions} s
1386
                          JOIN {user} a ON (a.id = s.authorid)
1387
                          JOIN {groups_members} agm ON (a.id = agm.userid)
1388
                          JOIN {user} u ON (u.id = ?)
1389
                          JOIN {groups_members} ugm ON (u.id = ugm.userid)
1390
                         WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1391
                $params = array($USER->id, $workshop->id, $submission->id);
1392
                if (!$DB->record_exists_sql($sql, $params)) {
1393
                    send_file_not_found();
1394
                }
1395
            }
1396
        }
1397
 
1398
        $fs = get_file_storage();
1399
        $relativepath = implode('/', $args);
1400
        $fullpath = "/$context->id/mod_workshop/$filearea/$itemid/$relativepath";
1401
        if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1402
            return false;
1403
        }
1404
        // finally send the file
1405
        // these files are uploaded by students - forcing download for security reasons
1406
        send_stored_file($file, 0, 0, true, $options);
1407
    }
1408
 
1409
    return false;
1410
}
1411
 
1412
/**
1413
 * File browsing support for workshop file areas
1414
 *
1415
 * @package  mod_workshop
1416
 * @category files
1417
 *
1418
 * @param file_browser $browser
1419
 * @param array $areas
1420
 * @param stdClass $course
1421
 * @param stdClass $cm
1422
 * @param stdClass $context
1423
 * @param string $filearea
1424
 * @param int $itemid
1425
 * @param string $filepath
1426
 * @param string $filename
1427
 * @return file_info instance or null if not found
1428
 */
1429
function workshop_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
1430
    global $CFG, $DB, $USER;
1431
 
1432
    /** @var array internal cache for author names */
1433
    static $submissionauthors = array();
1434
 
1435
    $fs = get_file_storage();
1436
 
1437
    if ($filearea === 'submission_content' or $filearea === 'submission_attachment') {
1438
 
1439
        if (!has_capability('mod/workshop:viewallsubmissions', $context)) {
1440
            return null;
1441
        }
1442
 
1443
        if (is_null($itemid)) {
1444
            // no itemid (submissionid) passed, display the list of all submissions
1445
            require_once($CFG->dirroot . '/mod/workshop/fileinfolib.php');
1446
            return new workshop_file_info_submissions_container($browser, $course, $cm, $context, $areas, $filearea);
1447
        }
1448
 
1449
        // make sure the user can see the particular submission in separate groups mode
1450
        $gmode = groups_get_activity_groupmode($cm, $course);
1451
 
1452
        if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1453
            // check there is at least one common group with both the $USER
1454
            // and the submission author (this is not expected to be a frequent
1455
            // usecase so we can live with pretty ineffective one query per submission here...)
1456
            $sql = "SELECT 'x'
1457
                      FROM {workshop_submissions} s
1458
                      JOIN {user} a ON (a.id = s.authorid)
1459
                      JOIN {groups_members} agm ON (a.id = agm.userid)
1460
                      JOIN {user} u ON (u.id = ?)
1461
                      JOIN {groups_members} ugm ON (u.id = ugm.userid)
1462
                     WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1463
            $params = array($USER->id, $cm->instance, $itemid);
1464
            if (!$DB->record_exists_sql($sql, $params)) {
1465
                return null;
1466
            }
1467
        }
1468
 
1469
        // we are inside some particular submission container
1470
 
1471
        $filepath = is_null($filepath) ? '/' : $filepath;
1472
        $filename = is_null($filename) ? '.' : $filename;
1473
 
1474
        if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, $itemid, $filepath, $filename)) {
1475
            if ($filepath === '/' and $filename === '.') {
1476
                $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, $itemid);
1477
            } else {
1478
                // not found
1479
                return null;
1480
            }
1481
        }
1482
 
1483
        // Checks to see if the user can manage files or is the owner.
1484
        // TODO MDL-33805 - Do not use userid here and move the capability check above.
1485
        if (!has_capability('moodle/course:managefiles', $context) && $storedfile->get_userid() != $USER->id) {
1486
            return null;
1487
        }
1488
 
1489
        // let us display the author's name instead of itemid (submission id)
1490
 
1491
        if (isset($submissionauthors[$itemid])) {
1492
            $topvisiblename = $submissionauthors[$itemid];
1493
 
1494
        } else {
1495
 
1496
            $userfieldsapi = \core_user\fields::for_name();
1497
            $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
1498
            $sql = "SELECT s.id, $userfields
1499
                      FROM {workshop_submissions} s
1500
                      JOIN {user} u ON (s.authorid = u.id)
1501
                     WHERE s.example = 0 AND s.workshopid = ?";
1502
            $params = array($cm->instance);
1503
            $rs = $DB->get_recordset_sql($sql, $params);
1504
 
1505
            foreach ($rs as $submissionauthor) {
1506
                $title = s(fullname($submissionauthor)); // this is generally not unique...
1507
                $submissionauthors[$submissionauthor->id] = $title;
1508
            }
1509
            $rs->close();
1510
 
1511
            if (!isset($submissionauthors[$itemid])) {
1512
                // should not happen
1513
                return null;
1514
            } else {
1515
                $topvisiblename = $submissionauthors[$itemid];
1516
            }
1517
        }
1518
 
1519
        $urlbase = $CFG->wwwroot . '/pluginfile.php';
1520
        // do not allow manual modification of any files!
1521
        return new file_info_stored($browser, $context, $storedfile, $urlbase, $topvisiblename, true, true, false, false);
1522
    }
1523
 
1524
    if ($filearea === 'overallfeedback_content' or $filearea === 'overallfeedback_attachment') {
1525
 
1526
        if (!has_capability('mod/workshop:viewallassessments', $context)) {
1527
            return null;
1528
        }
1529
 
1530
        if (is_null($itemid)) {
1531
            // No itemid (assessmentid) passed, display the list of all assessments.
1532
            require_once($CFG->dirroot . '/mod/workshop/fileinfolib.php');
1533
            return new workshop_file_info_overallfeedback_container($browser, $course, $cm, $context, $areas, $filearea);
1534
        }
1535
 
1536
        // Make sure the user can see the particular assessment in separate groups mode.
1537
        $gmode = groups_get_activity_groupmode($cm, $course);
1538
        if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1539
            // Check there is at least one common group with both the $USER
1540
            // and the submission author.
1541
            $sql = "SELECT 'x'
1542
                      FROM {workshop_submissions} s
1543
                      JOIN {user} a ON (a.id = s.authorid)
1544
                      JOIN {groups_members} agm ON (a.id = agm.userid)
1545
                      JOIN {user} u ON (u.id = ?)
1546
                      JOIN {groups_members} ugm ON (u.id = ugm.userid)
1547
                     WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1548
            $params = array($USER->id, $cm->instance, $itemid);
1549
            if (!$DB->record_exists_sql($sql, $params)) {
1550
                return null;
1551
            }
1552
        }
1553
 
1554
        // We are inside a particular assessment container.
1555
        $filepath = is_null($filepath) ? '/' : $filepath;
1556
        $filename = is_null($filename) ? '.' : $filename;
1557
 
1558
        if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, $itemid, $filepath, $filename)) {
1559
            if ($filepath === '/' and $filename === '.') {
1560
                $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, $itemid);
1561
            } else {
1562
                // Not found
1563
                return null;
1564
            }
1565
        }
1566
 
1567
        // Check to see if the user can manage files or is the owner.
1568
        if (!has_capability('moodle/course:managefiles', $context) and $storedfile->get_userid() != $USER->id) {
1569
            return null;
1570
        }
1571
 
1572
        $urlbase = $CFG->wwwroot . '/pluginfile.php';
1573
 
1574
        // Do not allow manual modification of any files.
1575
        return new file_info_stored($browser, $context, $storedfile, $urlbase, $itemid, true, true, false, false);
1576
    }
1577
 
1578
    if ($filearea == 'instructauthors' or $filearea == 'instructreviewers' or $filearea == 'conclusion') {
1579
        // always only itemid 0
1580
 
1581
        $filepath = is_null($filepath) ? '/' : $filepath;
1582
        $filename = is_null($filename) ? '.' : $filename;
1583
 
1584
        $urlbase = $CFG->wwwroot.'/pluginfile.php';
1585
        if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, 0, $filepath, $filename)) {
1586
            if ($filepath === '/' and $filename === '.') {
1587
                $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, 0);
1588
            } else {
1589
                // not found
1590
                return null;
1591
            }
1592
        }
1593
        return new file_info_stored($browser, $context, $storedfile, $urlbase, $areas[$filearea], false, true, true, false);
1594
    }
1595
}
1596
 
1597
////////////////////////////////////////////////////////////////////////////////
1598
// Navigation API                                                             //
1599
////////////////////////////////////////////////////////////////////////////////
1600
 
1601
/**
1602
 * Extends the global navigation tree by adding workshop nodes if there is a relevant content
1603
 *
1604
 * This can be called by an AJAX request so do not rely on $PAGE as it might not be set up properly.
1605
 *
1606
 * @param navigation_node $navref An object representing the navigation tree node of the workshop module instance
1607
 * @param stdClass $course
1608
 * @param stdClass $module
1609
 * @param cm_info $cm
1610
 */
1611
function workshop_extend_navigation(navigation_node $navref, stdclass $course, stdclass $module, cm_info $cm) {
1612
    global $CFG;
1613
 
1614
    if (has_capability('mod/workshop:submit', context_module::instance($cm->id))) {
1615
        $url = new moodle_url('/mod/workshop/submission.php', array('cmid' => $cm->id));
1616
        $mysubmission = $navref->add(get_string('mysubmission', 'workshop'), $url);
1617
        $mysubmission->mainnavonly = true;
1618
    }
1619
}
1620
 
1621
/**
1622
 * Extends the settings navigation with the Workshop settings
1623
 
1624
 * This function is called when the context for the page is a workshop module. This is not called by AJAX
1625
 * so it is safe to rely on the $PAGE.
1626
 *
1627
 * @param settings_navigation $settingsnav {@link settings_navigation}
1628
 * @param navigation_node $workshopnode {@link navigation_node}
1629
 */
1630
function workshop_extend_settings_navigation(settings_navigation $settingsnav, navigation_node $workshopnode=null) {
1631
    if (has_capability('mod/workshop:editdimensions', $settingsnav->get_page()->cm->context)) {
1632
        $url = new moodle_url('/mod/workshop/editform.php', array('cmid' => $settingsnav->get_page()->cm->id));
1633
        $workshopnode->add(get_string('assessmentform', 'workshop'), $url,
1634
        settings_navigation::TYPE_SETTING, null, 'workshopassessement');
1635
    }
1636
    if (has_capability('mod/workshop:allocate', $settingsnav->get_page()->cm->context)) {
1637
        $url = new moodle_url('/mod/workshop/allocation.php', array('cmid' => $settingsnav->get_page()->cm->id));
1638
        $workshopnode->add(get_string('submissionsallocation', 'workshop'), $url, settings_navigation::TYPE_SETTING);
1639
    }
1640
}
1641
 
1642
/**
1643
 * Return a list of page types
1644
 * @param string $pagetype current page type
1645
 * @param stdClass $parentcontext Block's parent context
1646
 * @param stdClass $currentcontext Current context of block
1647
 */
1648
function workshop_page_type_list($pagetype, $parentcontext, $currentcontext) {
1649
    $module_pagetype = array('mod-workshop-*'=>get_string('page-mod-workshop-x', 'workshop'));
1650
    return $module_pagetype;
1651
}
1652
 
1653
////////////////////////////////////////////////////////////////////////////////
1654
// Calendar API                                                               //
1655
////////////////////////////////////////////////////////////////////////////////
1656
 
1657
/**
1658
 * Updates the calendar events associated to the given workshop
1659
 *
1660
 * @param stdClass $workshop the workshop instance record
1661
 * @param int $cmid course module id
1662
 */
1663
function workshop_calendar_update(stdClass $workshop, $cmid) {
1664
    global $DB;
1665
 
1666
    // get the currently registered events so that we can re-use their ids
1667
    $currentevents = $DB->get_records('event', array('modulename' => 'workshop', 'instance' => $workshop->id));
1668
 
1669
    // the common properties for all events
1670
    $base = new stdClass();
1671
    $base->description  = format_module_intro('workshop', $workshop, $cmid, false);
1672
    $base->format       = FORMAT_HTML;
1673
    $base->courseid     = $workshop->course;
1674
    $base->groupid      = 0;
1675
    $base->userid       = 0;
1676
    $base->modulename   = 'workshop';
1677
    $base->instance     = $workshop->id;
1678
    $base->visible      = instance_is_visible('workshop', $workshop);
1679
    $base->timeduration = 0;
1680
 
1681
    if ($workshop->submissionstart) {
1682
        $event = clone($base);
1683
        $event->name = get_string('submissionstartevent', 'mod_workshop', $workshop->name);
1684
        $event->eventtype = WORKSHOP_EVENT_TYPE_SUBMISSION_OPEN;
1685
        $event->type = empty($workshop->submissionend) ? CALENDAR_EVENT_TYPE_ACTION : CALENDAR_EVENT_TYPE_STANDARD;
1686
        $event->timestart = $workshop->submissionstart;
1687
        $event->timesort  = $workshop->submissionstart;
1688
        if ($reusedevent = array_shift($currentevents)) {
1689
            $event->id = $reusedevent->id;
1690
        } else {
1691
            // should not be set but just in case
1692
            unset($event->id);
1693
        }
1694
        // update() will reuse a db record if the id field is set
1695
        $eventobj = new calendar_event($event);
1696
        $eventobj->update($event, false);
1697
    }
1698
 
1699
    if ($workshop->submissionend) {
1700
        $event = clone($base);
1701
        $event->name = get_string('submissionendevent', 'mod_workshop', $workshop->name);
1702
        $event->eventtype = WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE;
1703
        $event->type      = CALENDAR_EVENT_TYPE_ACTION;
1704
        $event->timestart = $workshop->submissionend;
1705
        $event->timesort  = $workshop->submissionend;
1706
        if ($reusedevent = array_shift($currentevents)) {
1707
            $event->id = $reusedevent->id;
1708
        } else {
1709
            // should not be set but just in case
1710
            unset($event->id);
1711
        }
1712
        // update() will reuse a db record if the id field is set
1713
        $eventobj = new calendar_event($event);
1714
        $eventobj->update($event, false);
1715
    }
1716
 
1717
    if ($workshop->assessmentstart) {
1718
        $event = clone($base);
1719
        $event->name = get_string('assessmentstartevent', 'mod_workshop', $workshop->name);
1720
        $event->eventtype = WORKSHOP_EVENT_TYPE_ASSESSMENT_OPEN;
1721
        $event->type      = empty($workshop->assessmentend) ? CALENDAR_EVENT_TYPE_ACTION : CALENDAR_EVENT_TYPE_STANDARD;
1722
        $event->timestart = $workshop->assessmentstart;
1723
        $event->timesort  = $workshop->assessmentstart;
1724
        if ($reusedevent = array_shift($currentevents)) {
1725
            $event->id = $reusedevent->id;
1726
        } else {
1727
            // should not be set but just in case
1728
            unset($event->id);
1729
        }
1730
        // update() will reuse a db record if the id field is set
1731
        $eventobj = new calendar_event($event);
1732
        $eventobj->update($event, false);
1733
    }
1734
 
1735
    if ($workshop->assessmentend) {
1736
        $event = clone($base);
1737
        $event->name = get_string('assessmentendevent', 'mod_workshop', $workshop->name);
1738
        $event->eventtype = WORKSHOP_EVENT_TYPE_ASSESSMENT_CLOSE;
1739
        $event->type      = CALENDAR_EVENT_TYPE_ACTION;
1740
        $event->timestart = $workshop->assessmentend;
1741
        $event->timesort  = $workshop->assessmentend;
1742
        if ($reusedevent = array_shift($currentevents)) {
1743
            $event->id = $reusedevent->id;
1744
        } else {
1745
            // should not be set but just in case
1746
            unset($event->id);
1747
        }
1748
        // update() will reuse a db record if the id field is set
1749
        $eventobj = new calendar_event($event);
1750
        $eventobj->update($event, false);
1751
    }
1752
 
1753
    // delete any leftover events
1754
    foreach ($currentevents as $oldevent) {
1755
        $oldevent = calendar_event::load($oldevent);
1756
        $oldevent->delete();
1757
    }
1758
}
1759
 
1760
/**
1761
 * This function receives a calendar event and returns the action associated with it, or null if there is none.
1762
 *
1763
 * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event
1764
 * is not displayed on the block.
1765
 *
1766
 * @param calendar_event $event
1767
 * @param \core_calendar\action_factory $factory
1768
 * @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default).
1769
 * @return \core_calendar\local\event\entities\action_interface|null
1770
 */
1771
function mod_workshop_core_calendar_provide_event_action(calendar_event $event,
1772
        \core_calendar\action_factory $factory, int $userid = 0) {
1773
    global $USER;
1774
 
1775
    if (!$userid) {
1776
        $userid = $USER->id;
1777
    }
1778
 
1779
    $cm = get_fast_modinfo($event->courseid, $userid)->instances['workshop'][$event->instance];
1780
 
1781
    if (!$cm->uservisible) {
1782
        // The module is not visible to the user for any reason.
1783
        return null;
1784
    }
1785
 
1786
    $completion = new \completion_info($cm->get_course());
1787
 
1788
    $completiondata = $completion->get_data($cm, false, $userid);
1789
 
1790
    if ($completiondata->completionstate != COMPLETION_INCOMPLETE) {
1791
        return null;
1792
    }
1793
 
1794
    return $factory->create_instance(
1795
        get_string('viewworkshopsummary', 'workshop'),
1796
        new \moodle_url('/mod/workshop/view.php', array('id' => $cm->id)),
1797
        1,
1798
        true
1799
    );
1800
}
1801
 
1802
/**
1803
 * This function calculates the minimum and maximum cutoff values for the timestart of
1804
 * the given event.
1805
 *
1806
 * It will return an array with two values, the first being the minimum cutoff value and
1807
 * the second being the maximum cutoff value. Either or both values can be null, which
1808
 * indicates there is no minimum or maximum, respectively.
1809
 *
1810
 * If a cutoff is required then the function must return an array containing the cutoff
1811
 * timestamp and error string to display to the user if the cutoff value is violated.
1812
 *
1813
 * A minimum and maximum cutoff return value will look like:
1814
 * [
1815
 *     [1505704373, 'The date must be after this date'],
1816
 *     [1506741172, 'The date must be before this date']
1817
 * ]
1818
 *
1819
 * @param calendar_event $event The calendar event to get the time range for
1820
 * @param stdClass $workshop The module instance to get the range from
1821
 * @return array Returns an array with min and max date.
1822
 */
1823
function mod_workshop_core_calendar_get_valid_event_timestart_range(\calendar_event $event, \stdClass $workshop): array {
1824
    $mindate = null;
1825
    $maxdate = null;
1826
 
1827
    $phasesubmissionend = max($workshop->submissionstart, $workshop->submissionend);
1828
    $phaseassessmentstart = min($workshop->assessmentstart, $workshop->assessmentend);
1829
    if ($phaseassessmentstart == 0) {
1830
        $phaseassessmentstart = max($workshop->assessmentstart, $workshop->assessmentend);
1831
    }
1832
 
1833
    switch ($event->eventtype) {
1834
        case WORKSHOP_EVENT_TYPE_SUBMISSION_OPEN:
1835
            if (!empty($workshop->submissionend)) {
1836
                $maxdate = [
1837
                    $workshop->submissionend - 1,   // The submissionstart and submissionend cannot be exactly the same.
1838
                    get_string('submissionendbeforestart', 'mod_workshop')
1839
                ];
1840
            } else if ($phaseassessmentstart) {
1841
                $maxdate = [
1842
                    $phaseassessmentstart,
1843
                    get_string('phasesoverlap', 'mod_workshop')
1844
                ];
1845
            }
1846
            break;
1847
        case WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE:
1848
            if (!empty($workshop->submissionstart)) {
1849
                $mindate = [
1850
                    $workshop->submissionstart + 1, // The submissionstart and submissionend cannot be exactly the same.
1851
                    get_string('submissionendbeforestart', 'mod_workshop')
1852
                ];
1853
            }
1854
            if ($phaseassessmentstart) {
1855
                $maxdate = [
1856
                    $phaseassessmentstart,
1857
                    get_string('phasesoverlap', 'mod_workshop')
1858
                ];
1859
            }
1860
            break;
1861
        case WORKSHOP_EVENT_TYPE_ASSESSMENT_OPEN:
1862
            if ($phasesubmissionend) {
1863
                $mindate = [
1864
                    $phasesubmissionend,
1865
                    get_string('phasesoverlap', 'mod_workshop')
1866
                ];
1867
            }
1868
            if (!empty($workshop->assessmentend)) {
1869
                $maxdate = [
1870
                    $workshop->assessmentend - 1,   // The assessmentstart and assessmentend cannot be exactly the same.
1871
                    get_string('assessmentendbeforestart', 'mod_workshop')
1872
                ];
1873
            }
1874
            break;
1875
        case WORKSHOP_EVENT_TYPE_ASSESSMENT_CLOSE:
1876
            if (!empty($workshop->assessmentstart)) {
1877
                $mindate = [
1878
                    $workshop->assessmentstart + 1, // The assessmentstart and assessmentend cannot be exactly the same.
1879
                    get_string('assessmentendbeforestart', 'mod_workshop')
1880
                ];
1881
            } else if ($phasesubmissionend) {
1882
                $mindate = [
1883
                    $phasesubmissionend,
1884
                    get_string('phasesoverlap', 'mod_workshop')
1885
                ];
1886
            }
1887
            break;
1888
    }
1889
 
1890
    return [$mindate, $maxdate];
1891
}
1892
 
1893
/**
1894
 * This function will update the workshop module according to the
1895
 * event that has been modified.
1896
 *
1897
 * @param \calendar_event $event
1898
 * @param stdClass $workshop The module instance to get the range from
1899
 */
1900
function mod_workshop_core_calendar_event_timestart_updated(\calendar_event $event, \stdClass $workshop): void {
1901
    global $DB;
1902
 
1903
    $courseid = $event->courseid;
1904
    $modulename = $event->modulename;
1905
    $instanceid = $event->instance;
1906
 
1907
    // Something weird going on. The event is for a different module so
1908
    // we should ignore it.
1909
    if ($modulename != 'workshop') {
1910
        return;
1911
    }
1912
 
1913
    if ($workshop->id != $instanceid) {
1914
        return;
1915
    }
1916
 
1917
    if (!in_array(
1918
            $event->eventtype,
1919
            [
1920
                WORKSHOP_EVENT_TYPE_SUBMISSION_OPEN,
1921
                WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE,
1922
                WORKSHOP_EVENT_TYPE_ASSESSMENT_OPEN,
1923
                WORKSHOP_EVENT_TYPE_ASSESSMENT_CLOSE
1924
            ]
1925
    )) {
1926
        return;
1927
    }
1928
 
1929
    $coursemodule = get_fast_modinfo($courseid)->instances[$modulename][$instanceid];
1930
    $context = context_module::instance($coursemodule->id);
1931
 
1932
    // The user does not have the capability to modify this activity.
1933
    if (!has_capability('moodle/course:manageactivities', $context)) {
1934
        return;
1935
    }
1936
 
1937
    $modified = false;
1938
 
1939
    switch ($event->eventtype) {
1940
        case WORKSHOP_EVENT_TYPE_SUBMISSION_OPEN:
1941
            if ($event->timestart != $workshop->submissionstart) {
1942
                $workshop->submissionstart = $event->timestart;
1943
                $modified = true;
1944
            }
1945
            break;
1946
        case WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE:
1947
            if ($event->timestart != $workshop->submissionend) {
1948
                $workshop->submissionend = $event->timestart;
1949
                $modified = true;
1950
            }
1951
            break;
1952
        case WORKSHOP_EVENT_TYPE_ASSESSMENT_OPEN:
1953
            if ($event->timestart != $workshop->assessmentstart) {
1954
                $workshop->assessmentstart = $event->timestart;
1955
                $modified = true;
1956
            }
1957
            break;
1958
        case WORKSHOP_EVENT_TYPE_ASSESSMENT_CLOSE:
1959
            if ($event->timestart != $workshop->assessmentend) {
1960
                $workshop->assessmentend = $event->timestart;
1961
                $modified = true;
1962
            }
1963
            break;
1964
    }
1965
 
1966
    if ($modified) {
1967
        $workshop->timemodified = time();
1968
        // Persist the assign instance changes.
1969
        $DB->update_record('workshop', $workshop);
1970
        $event = \core\event\course_module_updated::create_from_cm($coursemodule, $context);
1971
        $event->trigger();
1972
    }
1973
}
1974
 
1975
////////////////////////////////////////////////////////////////////////////////
1976
// Course reset API                                                           //
1977
////////////////////////////////////////////////////////////////////////////////
1978
 
1979
/**
1980
 * Extends the course reset form with workshop specific settings.
1981
 *
1982
 * @param MoodleQuickForm $mform
1983
 */
1984
function workshop_reset_course_form_definition($mform) {
1985
 
1986
    $mform->addElement('header', 'workshopheader', get_string('modulenameplural', 'mod_workshop'));
1987
 
1988
    $mform->addElement('advcheckbox', 'reset_workshop_submissions', get_string('resetsubmissions', 'mod_workshop'));
1989
    $mform->addHelpButton('reset_workshop_submissions', 'resetsubmissions', 'mod_workshop');
1990
 
1991
    $mform->addElement('advcheckbox', 'reset_workshop_assessments', get_string('resetassessments', 'mod_workshop'));
1992
    $mform->addHelpButton('reset_workshop_assessments', 'resetassessments', 'mod_workshop');
1993
    $mform->disabledIf('reset_workshop_assessments', 'reset_workshop_submissions', 'checked');
1994
 
1995
    $mform->addElement('advcheckbox', 'reset_workshop_phase', get_string('resetphase', 'mod_workshop'));
1996
    $mform->addHelpButton('reset_workshop_phase', 'resetphase', 'mod_workshop');
1997
}
1998
 
1999
/**
2000
 * Provides default values for the workshop settings in the course reset form.
2001
 *
2002
 * @param stdClass $course The course to be reset.
2003
 */
2004
function workshop_reset_course_form_defaults(stdClass $course) {
2005
 
2006
    $defaults = array(
2007
        'reset_workshop_submissions'    => 1,
2008
        'reset_workshop_assessments'    => 1,
2009
        'reset_workshop_phase'          => 1,
2010
    );
2011
 
2012
    return $defaults;
2013
}
2014
 
2015
/**
2016
 * Performs the reset of all workshop instances in the course.
2017
 *
2018
 * @param stdClass $data The actual course reset settings.
2019
 * @return array List of results, each being array[(string)component, (string)item, (string)error]
2020
 */
2021
function workshop_reset_userdata(stdClass $data) {
2022
    global $CFG, $DB;
2023
 
2024
    // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset.
2025
    // See MDL-9367.
2026
    shift_course_mod_dates('workshop', array('submissionstart', 'submissionend', 'assessmentstart', 'assessmentend'),
2027
        $data->timeshift, $data->courseid);
2028
    $status = array();
2029
    $status[] = array('component' => get_string('modulenameplural', 'workshop'), 'item' => get_string('datechanged'),
2030
        'error' => false);
2031
 
2032
    if (empty($data->reset_workshop_submissions)
2033
            and empty($data->reset_workshop_assessments)
2034
            and empty($data->reset_workshop_phase) ) {
2035
        // Nothing to do here.
2036
        return $status;
2037
    }
2038
 
2039
    $workshoprecords = $DB->get_records('workshop', array('course' => $data->courseid));
2040
 
2041
    if (empty($workshoprecords)) {
2042
        // What a boring course - no workshops here!
2043
        return $status;
2044
    }
2045
 
2046
    require_once($CFG->dirroot . '/mod/workshop/locallib.php');
2047
 
2048
    $course = $DB->get_record('course', array('id' => $data->courseid), '*', MUST_EXIST);
2049
 
2050
    foreach ($workshoprecords as $workshoprecord) {
2051
        $cm = get_coursemodule_from_instance('workshop', $workshoprecord->id, $course->id, false, MUST_EXIST);
2052
        $workshop = new workshop($workshoprecord, $cm, $course);
2053
        $status = array_merge($status, $workshop->reset_userdata($data));
2054
    }
2055
 
2056
    return $status;
2057
}
2058
 
2059
/**
2060
 * Get icon mapping for font-awesome.
2061
 */
2062
function mod_workshop_get_fontawesome_icon_map() {
2063
    return [
2064
        'mod_workshop:userplan/task-info' => 'fa-info text-info',
2065
        'mod_workshop:userplan/task-todo' => 'fa-square-o',
2066
        'mod_workshop:userplan/task-done' => 'fa-check text-success',
2067
        'mod_workshop:userplan/task-fail' => 'fa-remove text-danger',
2068
    ];
2069
}
2070
 
2071
/**
2072
 * Check if the module has any update that affects the current user since a given time.
2073
 *
2074
 * @param  cm_info $cm course module data
2075
 * @param  int $from the time to check updates from
2076
 * @param  array $filter  if we need to check only specific updates
2077
 * @return stdClass an object with the different type of areas indicating if they were updated or not
2078
 * @since Moodle 3.4
2079
 */
2080
function workshop_check_updates_since(cm_info $cm, $from, $filter = array()) {
2081
    global $DB, $USER;
2082
 
2083
    $updates = course_check_module_updates_since($cm, $from, array('instructauthors', 'instructreviewers', 'conclusion'), $filter);
2084
 
2085
    // Check if there are new submissions, assessments or assessments grades in the workshop.
2086
    $updates->submissions = (object) array('updated' => false);
2087
    $updates->assessments = (object) array('updated' => false);
2088
    $updates->assessmentgrades = (object) array('updated' => false);
2089
 
2090
    $select = 'workshopid = ? AND authorid = ? AND (timecreated > ? OR timegraded > ? OR timemodified > ?)';
2091
    $params = array($cm->instance, $USER->id, $from, $from, $from);
2092
    $submissions = $DB->get_records_select('workshop_submissions', $select, $params, '', 'id');
2093
    if (!empty($submissions)) {
2094
        $updates->submissions->updated = true;
2095
        $updates->submissions->itemids = array_keys($submissions);
2096
    }
2097
 
2098
    // Get assessments updates (both submissions reviewed by me or reviews by others).
2099
    $select = "SELECT a.id
2100
                 FROM {workshop_assessments} a
2101
                 JOIN {workshop_submissions} s ON a.submissionid = s.id
2102
                 WHERE s.workshopid = ? AND (a.timecreated > ? OR a.timemodified > ?) AND (s.authorid = ? OR a.reviewerid = ?)";
2103
    $params = array($cm->instance, $from, $from, $USER->id, $USER->id);
2104
    $assessments = $DB->get_records_sql($select, $params);
2105
    if (!empty($assessments)) {
2106
        $updates->assessments->updated = true;
2107
        $updates->assessments->itemids = array_keys($assessments);
2108
    }
2109
    // Finally assessment aggregated grades.
2110
    $select = 'workshopid = ? AND userid = ? AND timegraded > ?';
2111
    $params = array($cm->instance, $USER->id, $from);
2112
    $assessmentgrades = $DB->get_records_select('workshop_aggregations', $select, $params, '', 'id');
2113
    if (!empty($assessmentgrades)) {
2114
        $updates->assessmentgrades->updated = true;
2115
        $updates->assessmentgrades->itemids = array_keys($assessmentgrades);
2116
    }
2117
 
2118
    // Now, teachers should see other students updates.
2119
    $canviewallsubmissions = has_capability('mod/workshop:viewallsubmissions', $cm->context);
2120
    $canviewallassessments = has_capability('mod/workshop:viewallassessments', $cm->context);
2121
    if ($canviewallsubmissions || $canviewallassessments) {
2122
 
2123
        $insql = '';
2124
        $inparams = array();
2125
        // To filter by users in my groups when separated groups are forced.
2126
        if (groups_get_activity_groupmode($cm) == SEPARATEGROUPS) {
2127
            $groupusers = array_keys(groups_get_activity_shared_group_members($cm));
2128
            if (empty($groupusers)) {
2129
                return $updates;
2130
            }
2131
            list($insql, $inparams) = $DB->get_in_or_equal($groupusers);
2132
        }
2133
 
2134
        if ($canviewallsubmissions) {
2135
            $updates->usersubmissions = (object) array('updated' => false);
2136
            $select = 'workshopid = ? AND (timecreated > ? OR timegraded > ? OR timemodified > ?)';
2137
            $params = array($cm->instance, $from, $from, $from);
2138
            if (!empty($insql)) {
2139
                $select .= " AND authorid $insql";
2140
                $params = array_merge($params, $inparams);
2141
            }
2142
            $usersubmissions = $DB->get_records_select('workshop_submissions', $select, $params, '', 'id');
2143
            if (!empty($usersubmissions)) {
2144
                $updates->usersubmissions->updated = true;
2145
                $updates->usersubmissions->itemids = array_keys($usersubmissions);
2146
            }
2147
        }
2148
 
2149
        if ($canviewallassessments) {
2150
            $updates->userassessments = (object) array('updated' => false);
2151
            $select = "SELECT a.id
2152
                         FROM {workshop_assessments} a
2153
                         JOIN {workshop_submissions} s ON a.submissionid = s.id
2154
                        WHERE s.workshopid = ? AND (a.timecreated > ? OR a.timemodified > ?)";
2155
            $params = array($cm->instance, $from, $from);
2156
            if (!empty($insql)) {
2157
                $select .= " AND s.reviewerid $insql";
2158
                $params = array_merge($params, $inparams);
2159
            }
2160
            $userassessments = $DB->get_records_sql($select, $params);
2161
            if (!empty($userassessments)) {
2162
                $updates->userassessments->updated = true;
2163
                $updates->userassessments->itemids = array_keys($userassessments);
2164
            }
2165
 
2166
            $updates->userassessmentgrades = (object) array('updated' => false);
2167
            $select = 'workshopid = ? AND timegraded > ?';
2168
            $params = array($cm->instance, $USER->id);
2169
            if (!empty($insql)) {
2170
                $select .= " AND userid $insql";
2171
                $params = array_merge($params, $inparams);
2172
            }
2173
            $userassessmentgrades = $DB->get_records_select('workshop_aggregations', $select, $params, '', 'id');
2174
            if (!empty($userassessmentgrades)) {
2175
                $updates->userassessmentgrades->updated = true;
2176
                $updates->userassessmentgrades->itemids = array_keys($userassessmentgrades);
2177
            }
2178
        }
2179
    }
2180
    return $updates;
2181
}
2182
 
2183
/**
2184
 * Given an array with a file path, it returns the itemid and the filepath for the defined filearea.
2185
 *
2186
 * @param  string $filearea The filearea.
2187
 * @param  array  $args The path (the part after the filearea and before the filename).
2188
 * @return array|null The itemid and the filepath inside the $args path, for the defined filearea.
2189
 */
2190
function mod_workshop_get_path_from_pluginfile(string $filearea, array $args): ?array {
2191
    if ($filearea !== 'instructauthors' && $filearea !== 'instructreviewers' && $filearea !== 'conclusion') {
2192
        return null;
2193
    }
2194
 
2195
    // Workshop only has empty itemid for some of the fileareas.
2196
    array_shift($args);
2197
 
2198
    // Get the filepath.
2199
    if (empty($args)) {
2200
        $filepath = '/';
2201
    } else {
2202
        $filepath = '/' . implode('/', $args) . '/';
2203
    }
2204
 
2205
    return [
2206
        'itemid' => 0,
2207
        'filepath' => $filepath,
2208
    ];
2209
}
2210
 
2211
/**
2212
 * Add a get_coursemodule_info function in case any feedback type wants to add 'extra' information
2213
 * for the course (see resource).
2214
 *
2215
 * Given a course_module object, this function returns any "extra" information that may be needed
2216
 * when printing this activity in a course listing.  See get_array_of_activities() in course/lib.php.
2217
 *
2218
 * @param stdClass $coursemodule The coursemodule object (record).
2219
 * @return cached_cm_info|false An object on information that the courses will know about (most noticeably, an icon).
2220
 */
2221
function workshop_get_coursemodule_info($coursemodule) {
2222
    global $DB;
2223
 
2224
    $dbparams = ['id' => $coursemodule->instance];
2225
    $fields = 'id, name, intro, introformat, submissionstart, submissionend, assessmentstart, assessmentend';
2226
    if (!$workshop = $DB->get_record('workshop', $dbparams, $fields)) {
2227
        return false;
2228
    }
2229
 
2230
    $result = new cached_cm_info();
2231
    $result->name = $workshop->name;
2232
 
2233
    if ($coursemodule->showdescription) {
2234
        // Convert intro to html. Do not filter cached version, filters run at display time.
2235
        $result->content = format_module_intro('workshop', $workshop, $coursemodule->id, false);
2236
    }
2237
 
2238
    // Populate some other values that can be used in calendar or on dashboard.
2239
    if ($workshop->submissionstart) {
2240
        $result->customdata['submissionstart'] = $workshop->submissionstart;
2241
    }
2242
    if ($workshop->submissionend) {
2243
        $result->customdata['submissionend'] = $workshop->submissionend;
2244
    }
2245
    if ($workshop->assessmentstart) {
2246
        $result->customdata['assessmentstart'] = $workshop->assessmentstart;
2247
    }
2248
    if ($workshop->assessmentend) {
2249
        $result->customdata['assessmentend'] = $workshop->assessmentend;
2250
    }
2251
 
2252
    return $result;
2253
}
2254
 
2255
/**
2256
 * Get the current user preferences that are available
2257
 *
2258
 * @return array[]
2259
 */
2260
function mod_workshop_user_preferences(): array {
2261
    $preferencedefinition = [
2262
        'type' => PARAM_BOOL,
2263
        'null' => NULL_NOT_ALLOWED,
2264
        'default' => false,
2265
        'permissioncallback' => [core_user::class, 'is_current_user'],
2266
    ];
2267
 
2268
    return [
2269
        'workshop-viewlet-allexamples-collapsed' => $preferencedefinition,
2270
        'workshop-viewlet-allsubmissions-collapsed' => $preferencedefinition,
2271
        'workshop-viewlet-assessmentform-collapsed' => $preferencedefinition,
2272
        'workshop-viewlet-assignedassessments-collapsed' => $preferencedefinition,
2273
        'workshop-viewlet-cleargrades-collapsed' => $preferencedefinition,
2274
        'workshop-viewlet-conclusion-collapsed' => $preferencedefinition,
2275
        'workshop-viewlet-examples-collapsed' => $preferencedefinition,
2276
        'workshop-viewlet-examplesfail-collapsed' => $preferencedefinition,
2277
        'workshop-viewlet-gradereport-collapsed' => $preferencedefinition,
2278
        'workshop-viewlet-instructauthors-collapsed' => $preferencedefinition,
2279
        'workshop-viewlet-instructreviewers-collapsed' => $preferencedefinition,
2280
        'workshop-viewlet-intro-collapsed' => $preferencedefinition,
2281
        'workshop-viewlet-ownsubmission-collapsed' => $preferencedefinition,
2282
        'workshop-viewlet-publicsubmissions-collapsed' => $preferencedefinition,
2283
        'workshop-viewlet-yourgrades-collapsed' => $preferencedefinition,
2284
    ];
2285
}
2286
 
2287
/**
2288
 * Callback to fetch the activity event type lang string.
2289
 *
2290
 * @param string $eventtype The event type.
2291
 * @return lang_string The event type lang string.
2292
 */
2293
function mod_workshop_core_calendar_get_event_action_string($eventtype): string {
2294
    $modulename = get_string('modulename', 'workshop');
2295
 
2296
    switch ($eventtype) {
2297
        case WORKSHOP_EVENT_TYPE_SUBMISSION_OPEN:
2298
            $identifier = 'submissionstartevent';
2299
            break;
2300
        case WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE:
2301
            $identifier = 'submissionendevent';
2302
            break;
2303
        case WORKSHOP_EVENT_TYPE_ASSESSMENT_OPEN:
2304
            $identifier = 'assessmentstartevent';
2305
            break;
2306
        case WORKSHOP_EVENT_TYPE_ASSESSMENT_CLOSE;
2307
            $identifier = 'assessmentendevent';
2308
            break;
2309
        default:
2310
            return get_string('requiresaction', 'calendar', $modulename);
2311
    }
2312
 
2313
    return get_string($identifier, 'workshop', $modulename);
2314
}