Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
 
18
/**
19
 * External course API
20
 *
21
 * @package    core_course
22
 * @category   external
23
 * @copyright  2009 Petr Skodak
24
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25
 */
26
 
27
defined('MOODLE_INTERNAL') || die;
28
 
29
use core_course\external\course_summary_exporter;
30
use core_external\external_api;
31
use core_external\external_description;
32
use core_external\external_files;
33
use core_external\external_format_value;
34
use core_external\external_function_parameters;
35
use core_external\external_multiple_structure;
36
use core_external\external_single_structure;
37
use core_external\external_value;
38
use core_external\external_warnings;
39
use core_external\util;
40
require_once(__DIR__ . "/lib.php");
41
 
42
/**
43
 * Course external functions
44
 *
45
 * @package    core_course
46
 * @category   external
47
 * @copyright  2011 Jerome Mouneyrac
48
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
49
 * @since Moodle 2.2
50
 */
51
class core_course_external extends external_api {
52
 
53
    /**
54
     * Returns description of method parameters
55
     *
56
     * @return external_function_parameters
57
     * @since Moodle 2.9 Options available
58
     * @since Moodle 2.2
59
     */
60
    public static function get_course_contents_parameters() {
61
        return new external_function_parameters(
62
                array('courseid' => new external_value(PARAM_INT, 'course id'),
63
                      'options' => new external_multiple_structure (
64
                              new external_single_structure(
65
                                array(
66
                                    'name' => new external_value(PARAM_ALPHANUM,
67
                                                'The expected keys (value format) are:
68
                                                excludemodules (bool) Do not return modules, return only the sections structure
69
                                                excludecontents (bool) Do not return module contents (i.e: files inside a resource)
70
                                                includestealthmodules (bool) Return stealth modules for students in a special
71
                                                    section (with id -1)
72
                                                sectionid (int) Return only this section
73
                                                sectionnumber (int) Return only this section with number (order)
74
                                                cmid (int) Return only this module information (among the whole sections structure)
75
                                                modname (string) Return only modules with this name "label, forum, etc..."
76
                                                modid (int) Return only the module with this id (to be used with modname'),
77
                                    'value' => new external_value(PARAM_RAW, 'the value of the option,
78
                                                                    this param is personaly validated in the external function.')
79
                              )
80
                      ), 'Options, used since Moodle 2.9', VALUE_DEFAULT, array())
81
                )
82
        );
83
    }
84
 
85
    /**
86
     * Get course contents
87
     *
88
     * @param int $courseid course id
89
     * @param array $options Options for filtering the results, used since Moodle 2.9
90
     * @return array
91
     * @since Moodle 2.9 Options available
92
     * @since Moodle 2.2
93
     */
94
    public static function get_course_contents($courseid, $options = array()) {
95
        global $CFG, $DB, $USER, $PAGE;
96
        require_once($CFG->dirroot . "/course/lib.php");
97
        require_once($CFG->libdir . '/completionlib.php');
98
 
99
        //validate parameter
100
        $params = self::validate_parameters(self::get_course_contents_parameters(),
101
                        array('courseid' => $courseid, 'options' => $options));
102
 
103
        $filters = array();
104
        if (!empty($params['options'])) {
105
 
106
            foreach ($params['options'] as $option) {
107
                $name = trim($option['name']);
108
                // Avoid duplicated options.
109
                if (!isset($filters[$name])) {
110
                    switch ($name) {
111
                        case 'excludemodules':
112
                        case 'excludecontents':
113
                        case 'includestealthmodules':
114
                            $value = clean_param($option['value'], PARAM_BOOL);
115
                            $filters[$name] = $value;
116
                            break;
117
                        case 'sectionid':
118
                        case 'sectionnumber':
119
                        case 'cmid':
120
                        case 'modid':
121
                            $value = clean_param($option['value'], PARAM_INT);
122
                            if (is_numeric($value)) {
123
                                $filters[$name] = $value;
124
                            } else {
125
                                throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
126
                            }
127
                            break;
128
                        case 'modname':
129
                            $value = clean_param($option['value'], PARAM_PLUGIN);
130
                            if ($value) {
131
                                $filters[$name] = $value;
132
                            } else {
133
                                throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
134
                            }
135
                            break;
136
                        default:
137
                            throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
138
                    }
139
                }
140
            }
141
        }
142
 
143
        //retrieve the course
144
        $course = $DB->get_record('course', array('id' => $params['courseid']), '*', MUST_EXIST);
145
 
146
        // now security checks
147
        $context = context_course::instance($course->id, IGNORE_MISSING);
148
        try {
149
            self::validate_context($context);
150
        } catch (Exception $e) {
151
            $exceptionparam = new stdClass();
152
            $exceptionparam->message = $e->getMessage();
153
            $exceptionparam->courseid = $course->id;
154
            throw new moodle_exception('errorcoursecontextnotvalid', 'webservice', '', $exceptionparam);
155
        }
156
 
157
        $canupdatecourse = has_capability('moodle/course:update', $context);
158
 
159
        //create return value
160
        $coursecontents = array();
161
 
162
        if ($canupdatecourse or $course->visible
163
                or has_capability('moodle/course:viewhiddencourses', $context)) {
164
 
165
            //retrieve sections
166
            $modinfo = get_fast_modinfo($course);
167
            $sections = $modinfo->get_section_info_all();
168
            $courseformat = course_get_format($course);
169
            $coursenumsections = $courseformat->get_last_section_number();
170
            $stealthmodules = array();   // Array to keep all the modules available but not visible in a course section/topic.
171
 
172
            $completioninfo = new completion_info($course);
173
 
174
            //for each sections (first displayed to last displayed)
175
            $modinfosections = $modinfo->get_sections();
176
            foreach ($sections as $key => $section) {
177
 
178
                // This becomes true when we are filtering and we found the value to filter with.
179
                $sectionfound = false;
180
 
181
                // Filter by section id.
182
                if (!empty($filters['sectionid'])) {
183
                    if ($section->id != $filters['sectionid']) {
184
                        continue;
185
                    } else {
186
                        $sectionfound = true;
187
                    }
188
                }
189
 
190
                // Filter by section number. Note that 0 is a valid section number.
191
                if (isset($filters['sectionnumber'])) {
192
                    if ($key != $filters['sectionnumber']) {
193
                        continue;
194
                    } else {
195
                        $sectionfound = true;
196
                    }
197
                }
198
 
199
                // reset $sectioncontents
200
                $sectionvalues = array();
201
                $sectionvalues['id'] = $section->id;
202
                $sectionvalues['name'] = get_section_name($course, $section);
203
                $sectionvalues['visible'] = $section->visible;
204
 
205
                $options = (object) array('noclean' => true);
206
                list($sectionvalues['summary'], $sectionvalues['summaryformat']) =
207
                        \core_external\util::format_text($section->summary, $section->summaryformat,
208
                                $context, 'course', 'section', $section->id, $options);
209
                $sectionvalues['section'] = $section->section;
210
                $sectionvalues['hiddenbynumsections'] = $section->section > $coursenumsections ? 1 : 0;
211
                $sectionvalues['uservisible'] = $section->uservisible;
212
                if (!empty($section->availableinfo)) {
213
                    $sectionvalues['availabilityinfo'] = \core_availability\info::format_info($section->availableinfo, $course);
214
                }
215
 
216
                $sectioncontents = array();
217
 
218
                // For each module of the section.
219
                if (empty($filters['excludemodules']) and !empty($modinfosections[$section->section])) {
220
                    foreach ($modinfosections[$section->section] as $cmid) {
221
                        $cm = $modinfo->cms[$cmid];
222
                        $cminfo = cm_info::create($cm);
223
                        $activitydates = \core\activity_dates::get_dates_for_module($cminfo, $USER->id);
224
 
225
                        // Stop here if the module is not visible to the user on the course main page:
226
                        // The user can't access the module and the user can't view the module on the course page.
227
                        if (!$cm->uservisible && !$cm->is_visible_on_course_page()) {
228
                            continue;
229
                        }
230
 
231
                        // This becomes true when we are filtering and we found the value to filter with.
232
                        $modfound = false;
233
 
234
                        // Filter by cmid.
235
                        if (!empty($filters['cmid'])) {
236
                            if ($cmid != $filters['cmid']) {
237
                                continue;
238
                            } else {
239
                                $modfound = true;
240
                            }
241
                        }
242
 
243
                        // Filter by module name and id.
244
                        if (!empty($filters['modname'])) {
245
                            if ($cm->modname != $filters['modname']) {
246
                                continue;
247
                            } else if (!empty($filters['modid'])) {
248
                                if ($cm->instance != $filters['modid']) {
249
                                    continue;
250
                                } else {
251
                                    // Note that if we are only filtering by modname we don't break the loop.
252
                                    $modfound = true;
253
                                }
254
                            }
255
                        }
256
 
257
                        $module = array();
258
 
259
                        $modcontext = context_module::instance($cm->id);
260
 
261
                        $isbranded = component_callback('mod_' . $cm->modname, 'is_branded', [], false);
262
 
263
                        // Common info (for people being able to see the module or availability dates).
264
                        $module['id'] = $cm->id;
265
                        $module['name'] = \core_external\util::format_string($cm->name, $modcontext);
266
                        $module['instance'] = $cm->instance;
267
                        $module['contextid'] = $modcontext->id;
268
                        $module['modname'] = (string) $cm->modname;
269
                        $module['modplural'] = (string) $cm->modplural;
270
                        $module['modicon'] = $cm->get_icon_url()->out(false);
271
                        $module['purpose'] = plugin_supports('mod', $cm->modname, FEATURE_MOD_PURPOSE, MOD_PURPOSE_OTHER);
272
                        $module['branded'] = $isbranded;
273
                        $module['indent'] = $cm->indent;
274
                        $module['onclick'] = $cm->onclick;
275
                        $module['afterlink'] = $cm->afterlink;
276
                        $activitybadgedata = $cm->get_activitybadge();
277
                        if (!empty($activitybadgedata)) {
278
                            $module['activitybadge'] = $activitybadgedata;
279
                        }
280
                        $module['customdata'] = json_encode($cm->customdata);
281
                        $module['completion'] = $cm->completion;
282
                        $module['downloadcontent'] = $cm->downloadcontent;
283
                        $module['noviewlink'] = plugin_supports('mod', $cm->modname, FEATURE_NO_VIEW_LINK, false);
284
                        $module['dates'] = $activitydates;
285
                        $module['groupmode'] = $cm->groupmode;
286
 
287
                        // Check module completion.
288
                        $completion = $completioninfo->is_enabled($cm);
289
                        if ($completion != COMPLETION_DISABLED) {
290
                            $exporter = new \core_completion\external\completion_info_exporter($course, $cm, $USER->id);
291
                            $renderer = $PAGE->get_renderer('core');
292
                            $modulecompletiondata = (array)$exporter->export($renderer);
293
                            $module['completiondata'] = $modulecompletiondata;
294
                        }
295
 
296
                        if (!empty($cm->showdescription) or $module['noviewlink']) {
297
                            // We want to use the external format. However from reading get_formatted_content(), $cm->content format is always FORMAT_HTML.
298
                            $options = array('noclean' => true);
299
                            list($module['description'], $descriptionformat) = \core_external\util::format_text($cm->content,
300
                                FORMAT_HTML, $modcontext, $cm->modname, 'intro', $cm->id, $options);
301
                        }
302
 
303
                        //url of the module
304
                        $url = $cm->url;
305
                        if ($url) { //labels don't have url
306
                            $module['url'] = $url->out(false);
307
                        }
308
 
309
                        $canviewhidden = has_capability('moodle/course:viewhiddenactivities',
310
                                            context_module::instance($cm->id));
311
                        //user that can view hidden module should know about the visibility
312
                        $module['visible'] = $cm->visible;
313
                        $module['visibleoncoursepage'] = $cm->visibleoncoursepage;
314
                        $module['uservisible'] = $cm->uservisible;
315
                        if (!empty($cm->availableinfo)) {
316
                            $module['availabilityinfo'] = \core_availability\info::format_info($cm->availableinfo, $course);
317
                        }
318
 
319
                        // Availability date (also send to user who can see hidden module).
320
                        if ($CFG->enableavailability && ($canviewhidden || $canupdatecourse)) {
321
                            $module['availability'] = $cm->availability;
322
                        }
323
 
324
                        // Return contents only if the user can access to the module.
325
                        if ($cm->uservisible) {
326
                            $baseurl = 'webservice/pluginfile.php';
327
 
328
                            // Call $modulename_export_contents (each module callback take care about checking the capabilities).
329
                            require_once($CFG->dirroot . '/mod/' . $cm->modname . '/lib.php');
330
                            $getcontentfunction = $cm->modname.'_export_contents';
331
                            if (function_exists($getcontentfunction)) {
332
                                $contents = $getcontentfunction($cm, $baseurl);
333
                                $module['contentsinfo'] = array(
334
                                    'filescount' => count($contents),
335
                                    'filessize' => 0,
336
                                    'lastmodified' => 0,
337
                                    'mimetypes' => array(),
338
                                );
339
                                foreach ($contents as $content) {
340
                                    // Check repository file (only main file).
341
                                    if (!isset($module['contentsinfo']['repositorytype'])) {
342
                                        $module['contentsinfo']['repositorytype'] =
343
                                            isset($content['repositorytype']) ? $content['repositorytype'] : '';
344
                                    }
345
                                    if (isset($content['filesize'])) {
346
                                        $module['contentsinfo']['filessize'] += $content['filesize'];
347
                                    }
348
                                    if (isset($content['timemodified']) &&
349
                                            ($content['timemodified'] > $module['contentsinfo']['lastmodified'])) {
350
 
351
                                        $module['contentsinfo']['lastmodified'] = $content['timemodified'];
352
                                    }
353
                                    if (isset($content['mimetype'])) {
354
                                        $module['contentsinfo']['mimetypes'][$content['mimetype']] = $content['mimetype'];
355
                                    }
356
                                }
357
 
358
                                if (empty($filters['excludecontents']) and !empty($contents)) {
359
                                    $module['contents'] = $contents;
360
                                } else {
361
                                    $module['contents'] = array();
362
                                }
363
                            }
364
                        }
365
 
366
                        // Assign result to $sectioncontents, there is an exception,
367
                        // stealth activities in non-visible sections for students go to a special section.
368
                        if (!empty($filters['includestealthmodules']) && !$section->uservisible && $cm->is_stealth()) {
369
                            $stealthmodules[] = $module;
370
                        } else {
371
                            $sectioncontents[] = $module;
372
                        }
373
 
374
                        // If we just did a filtering, break the loop.
375
                        if ($modfound) {
376
                            break;
377
                        }
378
 
379
                    }
380
                }
381
                $sectionvalues['modules'] = $sectioncontents;
382
 
383
                // assign result to $coursecontents
384
                $coursecontents[$key] = $sectionvalues;
385
 
386
                // Break the loop if we are filtering.
387
                if ($sectionfound) {
388
                    break;
389
                }
390
            }
391
 
392
            // Now that we have iterated over all the sections and activities, check the visibility.
393
            // We didn't this before to be able to retrieve stealth activities.
394
            foreach ($coursecontents as $sectionnumber => $sectioncontents) {
395
                $section = $sections[$sectionnumber];
396
 
397
                if (!$courseformat->is_section_visible($section)) {
398
                    unset($coursecontents[$sectionnumber]);
399
                    continue;
400
                }
401
 
402
                // Remove section and modules information if the section is not visible for the user.
403
                if (!$section->uservisible) {
404
                    $coursecontents[$sectionnumber]['modules'] = array();
405
                    // Remove summary information if the section is completely hidden only,
406
                    // even if the section is not user visible, the summary is always displayed among the availability information.
407
                    if (!$section->visible) {
408
                        $coursecontents[$sectionnumber]['summary'] = '';
409
                    }
410
                }
411
            }
412
 
413
            // Include stealth modules in special section (without any info).
414
            if (!empty($stealthmodules)) {
415
                $coursecontents[] = array(
416
                    'id' => -1,
417
                    'name' => '',
418
                    'summary' => '',
419
                    'summaryformat' => FORMAT_MOODLE,
420
                    'modules' => $stealthmodules
421
                );
422
            }
423
 
424
        }
425
        return $coursecontents;
426
    }
427
 
428
    /**
429
     * Returns description of method result value
430
     *
431
     * @return \core_external\external_description
432
     * @since Moodle 2.2
433
     */
434
    public static function get_course_contents_returns() {
435
        $completiondefinition = \core_completion\external\completion_info_exporter::get_read_structure(VALUE_DEFAULT, []);
436
 
437
        return new external_multiple_structure(
438
            new external_single_structure(
439
                array(
440
                    'id' => new external_value(PARAM_INT, 'Section ID'),
441
                    'name' => new external_value(PARAM_RAW, 'Section name'),
442
                    'visible' => new external_value(PARAM_INT, 'is the section visible', VALUE_OPTIONAL),
443
                    'summary' => new external_value(PARAM_RAW, 'Section description'),
444
                    'summaryformat' => new external_format_value('summary'),
445
                    'section' => new external_value(PARAM_INT, 'Section number inside the course', VALUE_OPTIONAL),
446
                    'hiddenbynumsections' => new external_value(PARAM_INT, 'Whether is a section hidden in the course format',
447
                                                                VALUE_OPTIONAL),
448
                    'uservisible' => new external_value(PARAM_BOOL, 'Is the section visible for the user?', VALUE_OPTIONAL),
449
                    'availabilityinfo' => new external_value(PARAM_RAW, 'Availability information.', VALUE_OPTIONAL),
450
                    'modules' => new external_multiple_structure(
451
                            new external_single_structure(
452
                                array(
453
                                    'id' => new external_value(PARAM_INT, 'activity id'),
454
                                    'url' => new external_value(PARAM_URL, 'activity url', VALUE_OPTIONAL),
455
                                    'name' => new external_value(PARAM_RAW, 'activity module name'),
456
                                    'instance' => new external_value(PARAM_INT, 'instance id', VALUE_OPTIONAL),
457
                                    'contextid' => new external_value(PARAM_INT, 'Activity context id.', VALUE_OPTIONAL),
458
                                    'description' => new external_value(PARAM_RAW, 'activity description', VALUE_OPTIONAL),
459
                                    'visible' => new external_value(PARAM_INT, 'is the module visible', VALUE_OPTIONAL),
460
                                    'uservisible' => new external_value(PARAM_BOOL, 'Is the module visible for the user?',
461
                                        VALUE_OPTIONAL),
462
                                    'availabilityinfo' => new external_value(PARAM_RAW, 'Availability information.',
463
                                        VALUE_OPTIONAL),
464
                                    'visibleoncoursepage' => new external_value(PARAM_INT, 'is the module visible on course page',
465
                                        VALUE_OPTIONAL),
466
                                    'modicon' => new external_value(PARAM_URL, 'activity icon url'),
467
                                    'modname' => new external_value(PARAM_PLUGIN, 'activity module type'),
468
                                    'purpose' => new external_value(PARAM_ALPHA, 'the module purpose'),
469
                                    'branded' => new external_value(PARAM_BOOL, 'Whether the module is branded or not',
470
                                        VALUE_OPTIONAL),
471
                                    'modplural' => new external_value(PARAM_TEXT, 'activity module plural name'),
472
                                    'availability' => new external_value(PARAM_RAW, 'module availability settings', VALUE_OPTIONAL),
473
                                    'indent' => new external_value(PARAM_INT, 'number of identation in the site'),
474
                                    'onclick' => new external_value(PARAM_RAW, 'Onclick action.', VALUE_OPTIONAL),
475
                                    'afterlink' => new external_value(PARAM_RAW, 'After link info to be displayed.',
476
                                        VALUE_OPTIONAL),
477
                                    'activitybadge' => self::get_activitybadge_structure(),
478
                                    'customdata' => new external_value(PARAM_RAW, 'Custom data (JSON encoded).', VALUE_OPTIONAL),
479
                                    'noviewlink' => new external_value(PARAM_BOOL, 'Whether the module has no view page',
480
                                        VALUE_OPTIONAL),
481
                                    'completion' => new external_value(PARAM_INT, 'Type of completion tracking:
482
 
483
                                    'completiondata' => $completiondefinition,
484
                                    'downloadcontent' => new external_value(PARAM_INT, 'The download content value', VALUE_OPTIONAL),
485
                                    'dates' => new external_multiple_structure(
486
                                        new external_single_structure(
487
                                            array(
488
                                                'label' => new external_value(PARAM_TEXT, 'date label'),
489
                                                'timestamp' => new external_value(PARAM_INT, 'date timestamp'),
490
                                                'relativeto' => new external_value(PARAM_INT, 'relative date timestamp',
491
                                                    VALUE_OPTIONAL),
492
                                                'dataid' => new external_value(PARAM_NOTAGS, 'cm data id', VALUE_OPTIONAL),
493
                                            )
494
                                        ),
495
                                        'Course dates',
496
                                        VALUE_DEFAULT,
497
                                        []
498
                                    ),
499
                                    'groupmode' => new external_value(PARAM_INT, 'Group mode value', VALUE_OPTIONAL),
500
                                    'contents' => new external_multiple_structure(
501
                                          new external_single_structure(
502
                                              array(
503
                                                  // content info
504
                                                  'type'=> new external_value(PARAM_TEXT, 'a file or a folder or external link'),
505
                                                  'filename'=> new external_value(PARAM_FILE, 'filename'),
506
                                                  'filepath'=> new external_value(PARAM_PATH, 'filepath'),
507
                                                  'filesize'=> new external_value(PARAM_INT, 'filesize'),
508
                                                  'fileurl' => new external_value(PARAM_URL, 'downloadable file url', VALUE_OPTIONAL),
509
                                                  'content' => new external_value(PARAM_RAW, 'Raw content, will be used when type is content', VALUE_OPTIONAL),
510
                                                  'timecreated' => new external_value(PARAM_INT, 'Time created'),
511
                                                  'timemodified' => new external_value(PARAM_INT, 'Time modified'),
512
                                                  'sortorder' => new external_value(PARAM_INT, 'Content sort order'),
513
                                                  'mimetype' => new external_value(PARAM_RAW, 'File mime type.', VALUE_OPTIONAL),
514
                                                  'isexternalfile' => new external_value(PARAM_BOOL, 'Whether is an external file.',
515
                                                    VALUE_OPTIONAL),
516
                                                  'repositorytype' => new external_value(PARAM_PLUGIN, 'The repository type for external files.',
517
                                                    VALUE_OPTIONAL),
518
 
519
                                                  // copyright related info
520
                                                  'userid' => new external_value(PARAM_INT, 'User who added this content to moodle'),
521
                                                  'author' => new external_value(PARAM_TEXT, 'Content owner'),
522
                                                  'license' => new external_value(PARAM_TEXT, 'Content license'),
523
                                                  'tags' => new external_multiple_structure(
524
                                                       \core_tag\external\tag_item_exporter::get_read_structure(), 'Tags',
525
                                                            VALUE_OPTIONAL
526
                                                   ),
527
                                              )
528
                                          ), 'Course contents', VALUE_DEFAULT, array()
529
                                      ),
530
                                    'contentsinfo' => new external_single_structure(
531
                                        array(
532
                                            'filescount' => new external_value(PARAM_INT, 'Total number of files.'),
533
                                            'filessize' => new external_value(PARAM_INT, 'Total files size.'),
534
                                            'lastmodified' => new external_value(PARAM_INT, 'Last time files were modified.'),
535
                                            'mimetypes' => new external_multiple_structure(
536
                                                new external_value(PARAM_RAW, 'File mime type.'),
537
                                                'Files mime types.'
538
                                            ),
539
                                            'repositorytype' => new external_value(PARAM_PLUGIN, 'The repository type for
540
                                                the main file.', VALUE_OPTIONAL),
541
                                        ), 'Contents summary information.', VALUE_OPTIONAL
542
                                    ),
543
                                )
544
                            ), 'list of module'
545
                    )
546
                )
547
            )
548
        );
549
    }
550
 
551
    /**
552
     * Returns description of activitybadge data.
553
     *
554
     * @return external_description
555
     */
556
    protected static function get_activitybadge_structure(): external_description {
557
        return new external_single_structure(
558
            [
559
                'badgecontent' => new external_value(
560
                    PARAM_TEXT,
561
                    'The content to be displayed in the activity badge',
562
                    VALUE_OPTIONAL
563
                ),
564
                'badgestyle' => new external_value(
565
                    PARAM_TEXT,
566
                    'The style for the activity badge',
567
                    VALUE_OPTIONAL
568
                ),
569
                'badgeurl' => new external_value(
570
                    PARAM_URL,
571
                    'An optional URL to redirect the user when the activity badge is clicked',
572
                    VALUE_OPTIONAL
573
                ),
574
                'badgeelementid' => new external_value(
575
                    PARAM_ALPHANUMEXT,
576
                    'An optional id in case the module wants to add some code for the activity badge',
577
                    VALUE_OPTIONAL
578
                ),
579
                'badgeextraattributes' => new external_multiple_structure(
580
                    new external_single_structure(
581
                        [
582
                            'name' => new external_value(
583
                                PARAM_TEXT,
584
                                'The attribute name',
585
                                VALUE_OPTIONAL
586
                            ),
587
                            'value' => new external_value(
588
                                PARAM_TEXT,
589
                                'The attribute value',
590
                                VALUE_OPTIONAL
591
                            ),
592
                        ],
593
                        'Each of the attribute names and values',
594
                        VALUE_OPTIONAL
595
                    ),
596
                    'An optional array of extra HTML attributes to add to the badge element',
597
                    VALUE_OPTIONAL
598
                ),
599
            ],
600
            'Activity badge to display near the name',
601
            VALUE_OPTIONAL
602
        );
603
    }
604
 
605
    /**
606
     * Returns description of method parameters
607
     *
608
     * @return external_function_parameters
609
     * @since Moodle 2.3
610
     */
611
    public static function get_courses_parameters() {
612
        return new external_function_parameters(
613
                array('options' => new external_single_structure(
614
                            array('ids' => new external_multiple_structure(
615
                                        new external_value(PARAM_INT, 'Course id')
616
                                        , 'List of course id. If empty return all courses
617
                                            except front page course.',
618
                                        VALUE_OPTIONAL)
619
                            ), 'options - operator OR is used', VALUE_DEFAULT, array())
620
                )
621
        );
622
    }
623
 
624
    /**
625
     * Get courses
626
     *
627
     * @param array $options It contains an array (list of ids)
628
     * @return array
629
     * @since Moodle 2.2
630
     */
631
    public static function get_courses($options = array()) {
632
        global $CFG, $DB;
633
        require_once($CFG->dirroot . "/course/lib.php");
634
 
635
        //validate parameter
636
        $params = self::validate_parameters(self::get_courses_parameters(),
637
                        array('options' => $options));
638
 
639
        //retrieve courses
640
        if (!array_key_exists('ids', $params['options'])
641
                or empty($params['options']['ids'])) {
642
            $courses = $DB->get_records('course');
643
        } else {
644
            $courses = $DB->get_records_list('course', 'id', $params['options']['ids']);
645
        }
646
 
647
        //create return value
648
        $coursesinfo = array();
649
        foreach ($courses as $course) {
650
 
651
            // now security checks
652
            $context = context_course::instance($course->id, IGNORE_MISSING);
653
            $courseformatoptions = course_get_format($course)->get_format_options();
654
            try {
655
                self::validate_context($context);
656
            } catch (Exception $e) {
657
                $exceptionparam = new stdClass();
658
                $exceptionparam->message = $e->getMessage();
659
                $exceptionparam->courseid = $course->id;
660
                throw new moodle_exception('errorcoursecontextnotvalid', 'webservice', '', $exceptionparam);
661
            }
662
            if ($course->id != SITEID) {
663
                require_capability('moodle/course:view', $context);
664
            }
665
 
666
            $courseinfo = array();
667
            $courseinfo['id'] = $course->id;
668
            $courseinfo['fullname'] = \core_external\util::format_string($course->fullname, $context);
669
            $courseinfo['shortname'] = \core_external\util::format_string($course->shortname, $context);
670
            $courseinfo['displayname'] = \core_external\util::format_string(get_course_display_name_for_list($course), $context);
671
            $courseinfo['categoryid'] = $course->category;
672
            list($courseinfo['summary'], $courseinfo['summaryformat']) =
673
                \core_external\util::format_text($course->summary, $course->summaryformat, $context, 'course', 'summary', 0);
674
            $courseinfo['format'] = $course->format;
675
            $courseinfo['startdate'] = $course->startdate;
676
            $courseinfo['enddate'] = $course->enddate;
677
            $courseinfo['showactivitydates'] = $course->showactivitydates;
678
            $courseinfo['showcompletionconditions'] = $course->showcompletionconditions;
679
            if (array_key_exists('numsections', $courseformatoptions)) {
680
                // For backward-compartibility
681
                $courseinfo['numsections'] = $courseformatoptions['numsections'];
682
            }
683
            $courseinfo['pdfexportfont'] = $course->pdfexportfont;
684
 
685
            $handler = core_course\customfield\course_handler::create();
686
            if ($customfields = $handler->export_instance_data($course->id)) {
687
                $courseinfo['customfields'] = [];
688
                foreach ($customfields as $data) {
689
                    $courseinfo['customfields'][] = [
690
                        'type' => $data->get_type(),
691
                        'value' => $data->get_value(),
692
                        'valueraw' => $data->get_data_controller()->get_value(),
693
                        'name' => $data->get_name(),
694
                        'shortname' => $data->get_shortname()
695
                    ];
696
                }
697
            }
698
 
699
            //some field should be returned only if the user has update permission
700
            $courseadmin = has_capability('moodle/course:update', $context);
701
            if ($courseadmin) {
702
                $courseinfo['categorysortorder'] = $course->sortorder;
703
                $courseinfo['idnumber'] = $course->idnumber;
704
                $courseinfo['showgrades'] = $course->showgrades;
705
                $courseinfo['showreports'] = $course->showreports;
706
                $courseinfo['newsitems'] = $course->newsitems;
707
                $courseinfo['visible'] = $course->visible;
708
                $courseinfo['maxbytes'] = $course->maxbytes;
709
                if (array_key_exists('hiddensections', $courseformatoptions)) {
710
                    // For backward-compartibility
711
                    $courseinfo['hiddensections'] = $courseformatoptions['hiddensections'];
712
                }
713
                // Return numsections for backward-compatibility with clients who expect it.
714
                $courseinfo['numsections'] = course_get_format($course)->get_last_section_number();
715
                $courseinfo['groupmode'] = $course->groupmode;
716
                $courseinfo['groupmodeforce'] = $course->groupmodeforce;
717
                $courseinfo['defaultgroupingid'] = $course->defaultgroupingid;
718
                $courseinfo['lang'] = clean_param($course->lang, PARAM_LANG);
719
                $courseinfo['timecreated'] = $course->timecreated;
720
                $courseinfo['timemodified'] = $course->timemodified;
721
                $courseinfo['forcetheme'] = clean_param($course->theme, PARAM_THEME);
722
                $courseinfo['enablecompletion'] = $course->enablecompletion;
723
                $courseinfo['completionnotify'] = $course->completionnotify;
724
                $courseinfo['courseformatoptions'] = array();
725
                foreach ($courseformatoptions as $key => $value) {
726
                    $courseinfo['courseformatoptions'][] = array(
727
                        'name' => $key,
728
                        'value' => $value
729
                    );
730
                }
731
            }
732
 
733
            if ($courseadmin or $course->visible
734
                    or has_capability('moodle/course:viewhiddencourses', $context)) {
735
                $coursesinfo[] = $courseinfo;
736
            }
737
        }
738
 
739
        return $coursesinfo;
740
    }
741
 
742
    /**
743
     * Returns description of method result value
744
     *
745
     * @return \core_external\external_description
746
     * @since Moodle 2.2
747
     */
748
    public static function get_courses_returns() {
749
        return new external_multiple_structure(
750
                new external_single_structure(
751
                        array(
752
                            'id' => new external_value(PARAM_INT, 'course id'),
753
                            'shortname' => new external_value(PARAM_RAW, 'course short name'),
754
                            'categoryid' => new external_value(PARAM_INT, 'category id'),
755
                            'categorysortorder' => new external_value(PARAM_INT,
756
                                    'sort order into the category', VALUE_OPTIONAL),
757
                            'fullname' => new external_value(PARAM_RAW, 'full name'),
758
                            'displayname' => new external_value(PARAM_RAW, 'course display name'),
759
                            'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
760
                            'summary' => new external_value(PARAM_RAW, 'summary'),
761
                            'summaryformat' => new external_format_value('summary'),
762
                            'format' => new external_value(PARAM_PLUGIN,
763
                                    'course format: weeks, topics, social, site,..'),
764
                            'showgrades' => new external_value(PARAM_INT,
765
                                    '1 if grades are shown, otherwise 0', VALUE_OPTIONAL),
766
                            'newsitems' => new external_value(PARAM_INT,
767
                                    'number of recent items appearing on the course page', VALUE_OPTIONAL),
768
                            'startdate' => new external_value(PARAM_INT,
769
                                    'timestamp when the course start'),
770
                            'enddate' => new external_value(PARAM_INT,
771
                                    'timestamp when the course end'),
772
                            'numsections' => new external_value(PARAM_INT,
773
                                    '(deprecated, use courseformatoptions) number of weeks/topics',
774
                                    VALUE_OPTIONAL),
775
                            'maxbytes' => new external_value(PARAM_INT,
776
                                    'largest size of file that can be uploaded into the course',
777
                                    VALUE_OPTIONAL),
778
                            'showreports' => new external_value(PARAM_INT,
779
                                    'are activity report shown (yes = 1, no =0)', VALUE_OPTIONAL),
780
                            'visible' => new external_value(PARAM_INT,
781
                                    '1: available to student, 0:not available', VALUE_OPTIONAL),
782
                            'hiddensections' => new external_value(PARAM_INT,
783
                                    '(deprecated, use courseformatoptions) How the hidden sections in the course are displayed to students',
784
                                    VALUE_OPTIONAL),
785
                            'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible',
786
                                    VALUE_OPTIONAL),
787
                            'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no',
788
                                    VALUE_OPTIONAL),
789
                            'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id',
790
                                    VALUE_OPTIONAL),
791
                            'timecreated' => new external_value(PARAM_INT,
792
                                    'timestamp when the course have been created', VALUE_OPTIONAL),
793
                            'timemodified' => new external_value(PARAM_INT,
794
                                    'timestamp when the course have been modified', VALUE_OPTIONAL),
795
                            'enablecompletion' => new external_value(PARAM_INT,
796
                                    'Enabled, control via completion and activity settings. Disbaled,
797
                                        not shown in activity settings.',
798
                                    VALUE_OPTIONAL),
799
                            'completionnotify' => new external_value(PARAM_INT,
800
                                    '1: yes 0: no', VALUE_OPTIONAL),
801
                            'lang' => new external_value(PARAM_SAFEDIR,
802
                                    'forced course language', VALUE_OPTIONAL),
803
                            'forcetheme' => new external_value(PARAM_PLUGIN,
804
                                    'name of the force theme', VALUE_OPTIONAL),
805
                            'courseformatoptions' => new external_multiple_structure(
806
                                new external_single_structure(
807
                                    array('name' => new external_value(PARAM_ALPHANUMEXT, 'course format option name'),
808
                                        'value' => new external_value(PARAM_RAW, 'course format option value')
809
                                )), 'additional options for particular course format', VALUE_OPTIONAL
810
                             ),
811
                            'showactivitydates' => new external_value(PARAM_BOOL, 'Whether the activity dates are shown or not'),
812
                            'showcompletionconditions' => new external_value(PARAM_BOOL,
813
                                'Whether the activity completion conditions are shown or not'),
814
                            'customfields' => new external_multiple_structure(
815
                                new external_single_structure(
816
                                    ['name' => new external_value(PARAM_RAW, 'The name of the custom field'),
817
                                     'shortname' => new external_value(PARAM_ALPHANUMEXT, 'The shortname of the custom field'),
818
                                     'type'  => new external_value(PARAM_COMPONENT,
819
                                         'The type of the custom field - text, checkbox...'),
820
                                     'valueraw' => new external_value(PARAM_RAW, 'The raw value of the custom field'),
821
                                     'value' => new external_value(PARAM_RAW, 'The value of the custom field')]
822
                                ), 'Custom fields and associated values', VALUE_OPTIONAL),
823
                        ), 'course'
824
                )
825
        );
826
    }
827
 
828
    /**
829
     * Return array of all editable course custom fields indexed by their shortname
830
     *
831
     * @param \context $context
832
     * @param int $courseid
833
     * @return \core_customfield\field_controller[]
834
     */
835
    public static function get_editable_customfields(\context $context, int $courseid = 0): array {
836
        $result = [];
837
 
838
        $handler = \core_course\customfield\course_handler::create();
839
        $handler->set_parent_context($context);
840
 
841
        foreach ($handler->get_editable_fields($courseid) as $field) {
842
            $result[$field->get('shortname')] = $field;
843
        }
844
 
845
        return $result;
846
    }
847
 
848
    /**
849
     * Returns description of method parameters
850
     *
851
     * @return external_function_parameters
852
     * @since Moodle 2.2
853
     */
854
    public static function create_courses_parameters() {
855
        $courseconfig = get_config('moodlecourse'); //needed for many default values
856
        return new external_function_parameters(
857
            array(
858
                'courses' => new external_multiple_structure(
859
                    new external_single_structure(
860
                        array(
861
                            'fullname' => new external_value(PARAM_TEXT, 'full name'),
862
                            'shortname' => new external_value(PARAM_TEXT, 'course short name'),
863
                            'categoryid' => new external_value(PARAM_INT, 'category id'),
864
                            'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
865
                            'summary' => new external_value(PARAM_RAW, 'summary', VALUE_OPTIONAL),
866
                            'summaryformat' => new external_format_value('summary', VALUE_DEFAULT),
867
                            'format' => new external_value(PARAM_PLUGIN,
868
                                    'course format: weeks, topics, social, site,..',
869
                                    VALUE_DEFAULT, $courseconfig->format),
870
                            'showgrades' => new external_value(PARAM_INT,
871
                                    '1 if grades are shown, otherwise 0', VALUE_DEFAULT,
872
                                    $courseconfig->showgrades),
873
                            'newsitems' => new external_value(PARAM_INT,
874
                                    'number of recent items appearing on the course page',
875
                                    VALUE_DEFAULT, $courseconfig->newsitems),
876
                            'startdate' => new external_value(PARAM_INT,
877
                                    'timestamp when the course start', VALUE_OPTIONAL),
878
                            'enddate' => new external_value(PARAM_INT,
879
                                    'timestamp when the course end', VALUE_OPTIONAL),
880
                            'numsections' => new external_value(PARAM_INT,
881
                                    '(deprecated, use courseformatoptions) number of weeks/topics',
882
                                    VALUE_OPTIONAL),
883
                            'maxbytes' => new external_value(PARAM_INT,
884
                                    'largest size of file that can be uploaded into the course',
885
                                    VALUE_DEFAULT, $courseconfig->maxbytes),
886
                            'showreports' => new external_value(PARAM_INT,
887
                                    'are activity report shown (yes = 1, no =0)', VALUE_DEFAULT,
888
                                    $courseconfig->showreports),
889
                            'visible' => new external_value(PARAM_INT,
890
                                    '1: available to student, 0:not available', VALUE_OPTIONAL),
891
                            'hiddensections' => new external_value(PARAM_INT,
892
                                    '(deprecated, use courseformatoptions) How the hidden sections in the course are displayed to students',
893
                                    VALUE_OPTIONAL),
894
                            'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible',
895
                                    VALUE_DEFAULT, $courseconfig->groupmode),
896
                            'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no',
897
                                    VALUE_DEFAULT, $courseconfig->groupmodeforce),
898
                            'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id',
899
                                    VALUE_DEFAULT, 0),
900
                            'enablecompletion' => new external_value(PARAM_INT,
901
                                    'Enabled, control via completion and activity settings. Disabled,
902
                                        not shown in activity settings.',
903
                                    VALUE_OPTIONAL),
904
                            'completionnotify' => new external_value(PARAM_INT,
905
                                    '1: yes 0: no', VALUE_OPTIONAL),
906
                            'lang' => new external_value(PARAM_SAFEDIR,
907
                                    'forced course language', VALUE_OPTIONAL),
908
                            'forcetheme' => new external_value(PARAM_PLUGIN,
909
                                    'name of the force theme', VALUE_OPTIONAL),
910
                            'courseformatoptions' => new external_multiple_structure(
911
                                new external_single_structure(
912
                                    array('name' => new external_value(PARAM_ALPHANUMEXT, 'course format option name'),
913
                                        'value' => new external_value(PARAM_RAW, 'course format option value')
914
                                )),
915
                                    'additional options for particular course format', VALUE_OPTIONAL),
916
                            'customfields' => new external_multiple_structure(
917
                                new external_single_structure(
918
                                    array(
919
                                        'shortname'  => new external_value(PARAM_ALPHANUMEXT, 'The shortname of the custom field'),
920
                                        'value' => new external_value(PARAM_RAW, 'The value of the custom field'),
921
                                )), 'custom fields for the course', VALUE_OPTIONAL
922
                            )
923
                    )), 'courses to create'
924
                )
925
            )
926
        );
927
    }
928
 
929
    /**
930
     * Create  courses
931
     *
932
     * @param array $courses
933
     * @return array courses (id and shortname only)
934
     * @since Moodle 2.2
935
     */
936
    public static function create_courses($courses) {
937
        global $CFG, $DB;
938
        require_once($CFG->dirroot . "/course/lib.php");
939
        require_once($CFG->libdir . '/completionlib.php');
940
 
941
        $params = self::validate_parameters(self::create_courses_parameters(),
942
                        array('courses' => $courses));
943
 
944
        $availablethemes = core_component::get_plugin_list('theme');
945
        $availablelangs = get_string_manager()->get_list_of_translations();
946
 
947
        $transaction = $DB->start_delegated_transaction();
948
 
949
        foreach ($params['courses'] as $course) {
950
 
951
            // Ensure the current user is allowed to run this function
952
            $context = context_coursecat::instance($course['categoryid'], IGNORE_MISSING);
953
            try {
954
                self::validate_context($context);
955
            } catch (Exception $e) {
956
                $exceptionparam = new stdClass();
957
                $exceptionparam->message = $e->getMessage();
958
                $exceptionparam->catid = $course['categoryid'];
959
                throw new moodle_exception('errorcatcontextnotvalid', 'webservice', '', $exceptionparam);
960
            }
961
            require_capability('moodle/course:create', $context);
962
 
963
            // Fullname and short name are required to be non-empty.
964
            if (trim($course['fullname']) === '') {
965
                throw new moodle_exception('errorinvalidparam', 'webservice', '', 'fullname');
966
            } else if (trim($course['shortname']) === '') {
967
                throw new moodle_exception('errorinvalidparam', 'webservice', '', 'shortname');
968
            }
969
 
970
            // Make sure lang is valid
971
            if (array_key_exists('lang', $course)) {
972
                if (empty($availablelangs[$course['lang']])) {
973
                    throw new moodle_exception('errorinvalidparam', 'webservice', '', 'lang');
974
                }
975
                if (!has_capability('moodle/course:setforcedlanguage', $context)) {
976
                    unset($course['lang']);
977
                }
978
            }
979
 
980
            // Make sure theme is valid
981
            if (array_key_exists('forcetheme', $course)) {
982
                if (!empty($CFG->allowcoursethemes)) {
983
                    if (empty($availablethemes[$course['forcetheme']])) {
984
                        throw new moodle_exception('errorinvalidparam', 'webservice', '', 'forcetheme');
985
                    } else {
986
                        $course['theme'] = $course['forcetheme'];
987
                    }
988
                }
989
            }
990
 
991
            //force visibility if ws user doesn't have the permission to set it
992
            $category = $DB->get_record('course_categories', array('id' => $course['categoryid']));
993
            if (!has_capability('moodle/course:visibility', $context)) {
994
                $course['visible'] = $category->visible;
995
            }
996
 
997
            //set default value for completion
998
            $courseconfig = get_config('moodlecourse');
999
            if (completion_info::is_enabled_for_site()) {
1000
                if (!array_key_exists('enablecompletion', $course)) {
1001
                    $course['enablecompletion'] = $courseconfig->enablecompletion;
1002
                }
1003
            } else {
1004
                $course['enablecompletion'] = 0;
1005
            }
1006
 
1007
            $course['category'] = $course['categoryid'];
1008
 
1009
            // Summary format.
1010
            $course['summaryformat'] = util::validate_format($course['summaryformat']);
1011
 
1012
            if (!empty($course['courseformatoptions'])) {
1013
                foreach ($course['courseformatoptions'] as $option) {
1014
                    $course[$option['name']] = $option['value'];
1015
                }
1016
            }
1017
 
1018
            // Custom fields.
1019
            if (!empty($course['customfields'])) {
1020
                $customfields = self::get_editable_customfields($context);
1021
                foreach ($course['customfields'] as $field) {
1022
                    if (array_key_exists($field['shortname'], $customfields)) {
1023
                        // Ensure we're populating the element form fields correctly.
1024
                        $controller = \core_customfield\data_controller::create(0, null, $customfields[$field['shortname']]);
1025
                        $course[$controller->get_form_element_name()] = $field['value'];
1026
                    }
1027
                }
1028
            }
1029
 
1030
            //Note: create_course() core function check shortname, idnumber, category
1031
            $course['id'] = create_course((object) $course)->id;
1032
 
1033
            $resultcourses[] = array('id' => $course['id'], 'shortname' => $course['shortname']);
1034
        }
1035
 
1036
        $transaction->allow_commit();
1037
 
1038
        return $resultcourses;
1039
    }
1040
 
1041
    /**
1042
     * Returns description of method result value
1043
     *
1044
     * @return \core_external\external_description
1045
     * @since Moodle 2.2
1046
     */
1047
    public static function create_courses_returns() {
1048
        return new external_multiple_structure(
1049
            new external_single_structure(
1050
                array(
1051
                    'id'       => new external_value(PARAM_INT, 'course id'),
1052
                    'shortname' => new external_value(PARAM_RAW, 'short name'),
1053
                )
1054
            )
1055
        );
1056
    }
1057
 
1058
    /**
1059
     * Update courses
1060
     *
1061
     * @return external_function_parameters
1062
     * @since Moodle 2.5
1063
     */
1064
    public static function update_courses_parameters() {
1065
        return new external_function_parameters(
1066
            array(
1067
                'courses' => new external_multiple_structure(
1068
                    new external_single_structure(
1069
                        array(
1070
                            'id' => new external_value(PARAM_INT, 'ID of the course'),
1071
                            'fullname' => new external_value(PARAM_TEXT, 'full name', VALUE_OPTIONAL),
1072
                            'shortname' => new external_value(PARAM_TEXT, 'course short name', VALUE_OPTIONAL),
1073
                            'categoryid' => new external_value(PARAM_INT, 'category id', VALUE_OPTIONAL),
1074
                            'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
1075
                            'summary' => new external_value(PARAM_RAW, 'summary', VALUE_OPTIONAL),
1076
                            'summaryformat' => new external_format_value('summary', VALUE_OPTIONAL),
1077
                            'format' => new external_value(PARAM_PLUGIN,
1078
                                    'course format: weeks, topics, social, site,..', VALUE_OPTIONAL),
1079
                            'showgrades' => new external_value(PARAM_INT,
1080
                                    '1 if grades are shown, otherwise 0', VALUE_OPTIONAL),
1081
                            'newsitems' => new external_value(PARAM_INT,
1082
                                    'number of recent items appearing on the course page', VALUE_OPTIONAL),
1083
                            'startdate' => new external_value(PARAM_INT,
1084
                                    'timestamp when the course start', VALUE_OPTIONAL),
1085
                            'enddate' => new external_value(PARAM_INT,
1086
                                    'timestamp when the course end', VALUE_OPTIONAL),
1087
                            'numsections' => new external_value(PARAM_INT,
1088
                                    '(deprecated, use courseformatoptions) number of weeks/topics', VALUE_OPTIONAL),
1089
                            'maxbytes' => new external_value(PARAM_INT,
1090
                                    'largest size of file that can be uploaded into the course', VALUE_OPTIONAL),
1091
                            'showreports' => new external_value(PARAM_INT,
1092
                                    'are activity report shown (yes = 1, no =0)', VALUE_OPTIONAL),
1093
                            'visible' => new external_value(PARAM_INT,
1094
                                    '1: available to student, 0:not available', VALUE_OPTIONAL),
1095
                            'hiddensections' => new external_value(PARAM_INT,
1096
                                    '(deprecated, use courseformatoptions) How the hidden sections in the course are
1097
                                        displayed to students', VALUE_OPTIONAL),
1098
                            'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible', VALUE_OPTIONAL),
1099
                            'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no', VALUE_OPTIONAL),
1100
                            'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id', VALUE_OPTIONAL),
1101
                            'enablecompletion' => new external_value(PARAM_INT,
1102
                                    'Enabled, control via completion and activity settings. Disabled,
1103
                                        not shown in activity settings.', VALUE_OPTIONAL),
1104
                            'completionnotify' => new external_value(PARAM_INT, '1: yes 0: no', VALUE_OPTIONAL),
1105
                            'lang' => new external_value(PARAM_SAFEDIR, 'forced course language', VALUE_OPTIONAL),
1106
                            'forcetheme' => new external_value(PARAM_PLUGIN, 'name of the force theme', VALUE_OPTIONAL),
1107
                            'courseformatoptions' => new external_multiple_structure(
1108
                                new external_single_structure(
1109
                                    array('name' => new external_value(PARAM_ALPHANUMEXT, 'course format option name'),
1110
                                        'value' => new external_value(PARAM_RAW, 'course format option value')
1111
                                )), 'additional options for particular course format', VALUE_OPTIONAL),
1112
                            'customfields' => new external_multiple_structure(
1113
                                new external_single_structure(
1114
                                    [
1115
                                        'shortname'  => new external_value(PARAM_ALPHANUMEXT, 'The shortname of the custom field'),
1116
                                        'value' => new external_value(PARAM_RAW, 'The value of the custom field')
1117
                                    ]
1118
                                ), 'Custom fields', VALUE_OPTIONAL),
1119
                        )
1120
                    ), 'courses to update'
1121
                )
1122
            )
1123
        );
1124
    }
1125
 
1126
    /**
1127
     * Update courses
1128
     *
1129
     * @param array $courses
1130
     * @since Moodle 2.5
1131
     */
1132
    public static function update_courses($courses) {
1133
        global $CFG, $DB;
1134
        require_once($CFG->dirroot . "/course/lib.php");
1135
        $warnings = array();
1136
 
1137
        $params = self::validate_parameters(self::update_courses_parameters(),
1138
                        array('courses' => $courses));
1139
 
1140
        $availablethemes = core_component::get_plugin_list('theme');
1141
        $availablelangs = get_string_manager()->get_list_of_translations();
1142
 
1143
        foreach ($params['courses'] as $course) {
1144
            // Catch any exception while updating course and return as warning to user.
1145
            try {
1146
                // Ensure the current user is allowed to run this function.
1147
                $context = context_course::instance($course['id'], MUST_EXIST);
1148
                self::validate_context($context);
1149
 
1150
                $oldcourse = course_get_format($course['id'])->get_course();
1151
 
1152
                require_capability('moodle/course:update', $context);
1153
 
1154
                // Check if user can change category.
1155
                if (array_key_exists('categoryid', $course) && ($oldcourse->category != $course['categoryid'])) {
1156
                    require_capability('moodle/course:changecategory', $context);
1157
                    $course['category'] = $course['categoryid'];
1158
                }
1159
 
1160
                // Check if the user can change fullname, and the new value is non-empty.
1161
                if (array_key_exists('fullname', $course) && ($oldcourse->fullname != $course['fullname'])) {
1162
                    require_capability('moodle/course:changefullname', $context);
1163
                    if (trim($course['fullname']) === '') {
1164
                        throw new moodle_exception('errorinvalidparam', 'webservice', '', 'fullname');
1165
                    }
1166
                }
1167
 
1168
                // Check if the user can change shortname, and the new value is non-empty.
1169
                if (array_key_exists('shortname', $course) && ($oldcourse->shortname != $course['shortname'])) {
1170
                    require_capability('moodle/course:changeshortname', $context);
1171
                    if (trim($course['shortname']) === '') {
1172
                        throw new moodle_exception('errorinvalidparam', 'webservice', '', 'shortname');
1173
                    }
1174
                }
1175
 
1176
                // Check if the user can change the idnumber.
1177
                if (array_key_exists('idnumber', $course) && ($oldcourse->idnumber != $course['idnumber'])) {
1178
                    require_capability('moodle/course:changeidnumber', $context);
1179
                }
1180
 
1181
                // Check if user can change summary.
1182
                if (array_key_exists('summary', $course) && ($oldcourse->summary != $course['summary'])) {
1183
                    require_capability('moodle/course:changesummary', $context);
1184
                }
1185
 
1186
                // Summary format.
1187
                if (array_key_exists('summaryformat', $course) && ($oldcourse->summaryformat != $course['summaryformat'])) {
1188
                    require_capability('moodle/course:changesummary', $context);
1189
                    $course['summaryformat'] = util::validate_format($course['summaryformat']);
1190
                }
1191
 
1192
                // Check if user can change visibility.
1193
                if (array_key_exists('visible', $course) && ($oldcourse->visible != $course['visible'])) {
1194
                    require_capability('moodle/course:visibility', $context);
1195
                }
1196
 
1197
                // Make sure lang is valid.
1198
                if (array_key_exists('lang', $course) && ($oldcourse->lang != $course['lang'])) {
1199
                    require_capability('moodle/course:setforcedlanguage', $context);
1200
                    if (empty($availablelangs[$course['lang']])) {
1201
                        throw new moodle_exception('errorinvalidparam', 'webservice', '', 'lang');
1202
                    }
1203
                }
1204
 
1205
                // Make sure theme is valid.
1206
                if (array_key_exists('forcetheme', $course)) {
1207
                    if (!empty($CFG->allowcoursethemes)) {
1208
                        if (empty($availablethemes[$course['forcetheme']])) {
1209
                            throw new moodle_exception('errorinvalidparam', 'webservice', '', 'forcetheme');
1210
                        } else {
1211
                            $course['theme'] = $course['forcetheme'];
1212
                        }
1213
                    }
1214
                }
1215
 
1216
                // Make sure completion is enabled before setting it.
1217
                if (array_key_exists('enabledcompletion', $course) && !completion_info::is_enabled_for_site()) {
1218
                    $course['enabledcompletion'] = 0;
1219
                }
1220
 
1221
                // Make sure maxbytes are less then CFG->maxbytes.
1222
                if (array_key_exists('maxbytes', $course)) {
1223
                    // We allow updates back to 0 max bytes, a special value denoting the course uses the site limit.
1224
                    // Otherwise, either use the size specified, or cap at the max size for the course.
1225
                    if ($course['maxbytes'] != 0) {
1226
                        $course['maxbytes'] = get_max_upload_file_size($CFG->maxbytes, $course['maxbytes']);
1227
                    }
1228
                }
1229
 
1230
                if (!empty($course['courseformatoptions'])) {
1231
                    foreach ($course['courseformatoptions'] as $option) {
1232
                        if (isset($option['name']) && isset($option['value'])) {
1233
                            $course[$option['name']] = $option['value'];
1234
                        }
1235
                    }
1236
                }
1237
 
1238
                // Custom fields.
1239
                if (isset($course['customfields'])) {
1240
                    $customfields = self::get_editable_customfields($context, $course['id']);
1241
                    foreach ($course['customfields'] as $field) {
1242
                        if (array_key_exists($field['shortname'], $customfields)) {
1243
                            // Ensure we're populating the element form fields correctly.
1244
                            $controller = \core_customfield\data_controller::create(0, null, $customfields[$field['shortname']]);
1245
                            $course[$controller->get_form_element_name()] = $field['value'];
1246
                        }
1247
                    }
1248
                }
1249
 
1250
                // Update course if user has all required capabilities.
1251
                update_course((object) $course);
1252
            } catch (Exception $e) {
1253
                $warning = array();
1254
                $warning['item'] = 'course';
1255
                $warning['itemid'] = $course['id'];
1256
                if ($e instanceof moodle_exception) {
1257
                    $warning['warningcode'] = $e->errorcode;
1258
                } else {
1259
                    $warning['warningcode'] = $e->getCode();
1260
                }
1261
                $warning['message'] = $e->getMessage();
1262
                $warnings[] = $warning;
1263
            }
1264
        }
1265
 
1266
        $result = array();
1267
        $result['warnings'] = $warnings;
1268
        return $result;
1269
    }
1270
 
1271
    /**
1272
     * Returns description of method result value
1273
     *
1274
     * @return \core_external\external_description
1275
     * @since Moodle 2.5
1276
     */
1277
    public static function update_courses_returns() {
1278
        return new external_single_structure(
1279
            array(
1280
                'warnings' => new external_warnings()
1281
            )
1282
        );
1283
    }
1284
 
1285
    /**
1286
     * Returns description of method parameters
1287
     *
1288
     * @return external_function_parameters
1289
     * @since Moodle 2.2
1290
     */
1291
    public static function delete_courses_parameters() {
1292
        return new external_function_parameters(
1293
            array(
1294
                'courseids' => new external_multiple_structure(new external_value(PARAM_INT, 'course ID')),
1295
            )
1296
        );
1297
    }
1298
 
1299
    /**
1300
     * Delete courses
1301
     *
1302
     * @param array $courseids A list of course ids
1303
     * @since Moodle 2.2
1304
     */
1305
    public static function delete_courses($courseids) {
1306
        global $CFG, $DB;
1307
        require_once($CFG->dirroot."/course/lib.php");
1308
 
1309
        // Parameter validation.
1310
        $params = self::validate_parameters(self::delete_courses_parameters(), array('courseids'=>$courseids));
1311
 
1312
        $warnings = array();
1313
 
1314
        foreach ($params['courseids'] as $courseid) {
1315
            $course = $DB->get_record('course', array('id' => $courseid));
1316
 
1317
            if ($course === false) {
1318
                $warnings[] = array(
1319
                                'item' => 'course',
1320
                                'itemid' => $courseid,
1321
                                'warningcode' => 'unknowncourseidnumber',
1322
                                'message' => 'Unknown course ID ' . $courseid
1323
                            );
1324
                continue;
1325
            }
1326
 
1327
            // Check if the context is valid.
1328
            $coursecontext = context_course::instance($course->id);
1329
            self::validate_context($coursecontext);
1330
 
1331
            // Check if the current user has permission.
1332
            if (!can_delete_course($courseid)) {
1333
                $warnings[] = array(
1334
                                'item' => 'course',
1335
                                'itemid' => $courseid,
1336
                                'warningcode' => 'cannotdeletecourse',
1337
                                'message' => 'You do not have the permission to delete this course' . $courseid
1338
                            );
1339
                continue;
1340
            }
1341
 
1342
            if (delete_course($course, false) === false) {
1343
                $warnings[] = array(
1344
                                'item' => 'course',
1345
                                'itemid' => $courseid,
1346
                                'warningcode' => 'cannotdeletecategorycourse',
1347
                                'message' => 'Course ' . $courseid . ' failed to be deleted'
1348
                            );
1349
                continue;
1350
            }
1351
        }
1352
 
1353
        fix_course_sortorder();
1354
 
1355
        return array('warnings' => $warnings);
1356
    }
1357
 
1358
    /**
1359
     * Returns description of method result value
1360
     *
1361
     * @return \core_external\external_description
1362
     * @since Moodle 2.2
1363
     */
1364
    public static function delete_courses_returns() {
1365
        return new external_single_structure(
1366
            array(
1367
                'warnings' => new external_warnings()
1368
            )
1369
        );
1370
    }
1371
 
1372
    /**
1373
     * Returns description of method parameters
1374
     *
1375
     * @return external_function_parameters
1376
     * @since Moodle 2.3
1377
     */
1378
    public static function duplicate_course_parameters() {
1379
        return new external_function_parameters(
1380
            array(
1381
                'courseid' => new external_value(PARAM_INT, 'course to duplicate id'),
1382
                'fullname' => new external_value(PARAM_TEXT, 'duplicated course full name'),
1383
                'shortname' => new external_value(PARAM_TEXT, 'duplicated course short name'),
1384
                'categoryid' => new external_value(PARAM_INT, 'duplicated course category parent'),
1385
                'visible' => new external_value(PARAM_INT, 'duplicated course visible, default to yes', VALUE_DEFAULT, 1),
1386
                'options' => new external_multiple_structure(
1387
                    new external_single_structure(
1388
                        array(
1389
                                'name' => new external_value(PARAM_ALPHAEXT, 'The backup option name:
1390
                                            "activities" (int) Include course activites (default to 1 that is equal to yes),
1391
                                            "blocks" (int) Include course blocks (default to 1 that is equal to yes),
1392
                                            "filters" (int) Include course filters  (default to 1 that is equal to yes),
1393
                                            "users" (int) Include users (default to 0 that is equal to no),
1394
                                            "enrolments" (int) Include enrolment methods (default to 1 - restore only with users),
1395
                                            "role_assignments" (int) Include role assignments  (default to 0 that is equal to no),
1396
                                            "comments" (int) Include user comments  (default to 0 that is equal to no),
1397
                                            "userscompletion" (int) Include user course completion information  (default to 0 that is equal to no),
1398
                                            "logs" (int) Include course logs  (default to 0 that is equal to no),
1399
                                            "grade_histories" (int) Include histories  (default to 0 that is equal to no)'
1400
                                            ),
1401
                                'value' => new external_value(PARAM_RAW, 'the value for the option 1 (yes) or 0 (no)'
1402
                            )
1403
                        )
1404
                    ), 'Course duplication options', VALUE_DEFAULT, array()
1405
                ),
1406
            )
1407
        );
1408
    }
1409
 
1410
    /**
1411
     * Duplicate a course
1412
     *
1413
     * @param int $courseid
1414
     * @param string $fullname Duplicated course fullname
1415
     * @param string $shortname Duplicated course shortname
1416
     * @param int $categoryid Duplicated course parent category id
1417
     * @param int $visible Duplicated course availability
1418
     * @param array $options List of backup options
1419
     * @return array New course info
1420
     * @since Moodle 2.3
1421
     */
1422
    public static function duplicate_course($courseid, $fullname, $shortname, $categoryid, $visible = 1, $options = array()) {
1423
        global $CFG, $USER, $DB;
1424
        require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
1425
        require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
1426
 
1427
        // Parameter validation.
1428
        $params = self::validate_parameters(
1429
                self::duplicate_course_parameters(),
1430
                array(
1431
                      'courseid' => $courseid,
1432
                      'fullname' => $fullname,
1433
                      'shortname' => $shortname,
1434
                      'categoryid' => $categoryid,
1435
                      'visible' => $visible,
1436
                      'options' => $options
1437
                )
1438
        );
1439
 
1440
        // Context validation.
1441
 
1442
        if (! ($course = $DB->get_record('course', array('id'=>$params['courseid'])))) {
1443
            throw new moodle_exception('invalidcourseid', 'error');
1444
        }
1445
 
1446
        // Category where duplicated course is going to be created.
1447
        $categorycontext = context_coursecat::instance($params['categoryid']);
1448
        self::validate_context($categorycontext);
1449
 
1450
        // Course to be duplicated.
1451
        $coursecontext = context_course::instance($course->id);
1452
        self::validate_context($coursecontext);
1453
 
1454
        $backupdefaults = array(
1455
            'activities' => 1,
1456
            'blocks' => 1,
1457
            'filters' => 1,
1458
            'users' => 0,
1459
            'enrolments' => backup::ENROL_WITHUSERS,
1460
            'role_assignments' => 0,
1461
            'comments' => 0,
1462
            'userscompletion' => 0,
1463
            'logs' => 0,
1464
            'grade_histories' => 0
1465
        );
1466
 
1467
        $backupsettings = array();
1468
        // Check for backup and restore options.
1469
        if (!empty($params['options'])) {
1470
            foreach ($params['options'] as $option) {
1471
 
1472
                // Strict check for a correct value (allways 1 or 0, true or false).
1473
                $value = clean_param($option['value'], PARAM_INT);
1474
 
1475
                if ($value !== 0 and $value !== 1) {
1476
                    throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
1477
                }
1478
 
1479
                if (!isset($backupdefaults[$option['name']])) {
1480
                    throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
1481
                }
1482
 
1483
                $backupsettings[$option['name']] = $value;
1484
            }
1485
        }
1486
 
1487
        // Capability checking.
1488
 
1489
        // The backup controller check for this currently, this may be redundant.
1490
        require_capability('moodle/course:create', $categorycontext);
1491
        require_capability('moodle/restore:restorecourse', $categorycontext);
1492
        require_capability('moodle/backup:backupcourse', $coursecontext);
1493
 
1494
        if (!empty($backupsettings['users'])) {
1495
            require_capability('moodle/backup:userinfo', $coursecontext);
1496
            require_capability('moodle/restore:userinfo', $categorycontext);
1497
        }
1498
 
1499
        // Check if the shortname is used.
1500
        if ($foundcourses = $DB->get_records('course', array('shortname'=>$shortname))) {
1501
            foreach ($foundcourses as $foundcourse) {
1502
                $foundcoursenames[] = $foundcourse->fullname;
1503
            }
1504
 
1505
            $foundcoursenamestring = implode(',', $foundcoursenames);
1506
            throw new moodle_exception('shortnametaken', '', '', $foundcoursenamestring);
1507
        }
1508
 
1509
        // Backup the course.
1510
 
1511
        $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
1512
        backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $USER->id);
1513
 
1514
        foreach ($backupsettings as $name => $value) {
1515
            if ($setting = $bc->get_plan()->get_setting($name)) {
1516
                $bc->get_plan()->get_setting($name)->set_value($value);
1517
            }
1518
        }
1519
 
1520
        $backupid       = $bc->get_backupid();
1521
        $backupbasepath = $bc->get_plan()->get_basepath();
1522
 
1523
        $bc->execute_plan();
1524
        $results = $bc->get_results();
1525
        $file = $results['backup_destination'];
1526
 
1527
        $bc->destroy();
1528
 
1529
        // Restore the backup immediately.
1530
 
1531
        // Check if we need to unzip the file because the backup temp dir does not contains backup files.
1532
        if (!file_exists($backupbasepath . "/moodle_backup.xml")) {
1533
            $file->extract_to_pathname(get_file_packer('application/vnd.moodle.backup'), $backupbasepath);
1534
        }
1535
 
1536
        // Create new course.
1537
        $newcourseid = restore_dbops::create_new_course($params['fullname'], $params['shortname'], $params['categoryid']);
1538
 
1539
        $rc = new restore_controller($backupid, $newcourseid,
1540
                backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $USER->id, backup::TARGET_NEW_COURSE);
1541
 
1542
        foreach ($backupsettings as $name => $value) {
1543
            $setting = $rc->get_plan()->get_setting($name);
1544
            if ($setting->get_status() == backup_setting::NOT_LOCKED) {
1545
                $setting->set_value($value);
1546
            }
1547
        }
1548
 
1549
        if (!$rc->execute_precheck()) {
1550
            $precheckresults = $rc->get_precheck_results();
1551
            if (is_array($precheckresults) && !empty($precheckresults['errors'])) {
1552
                if (empty($CFG->keeptempdirectoriesonbackup)) {
1553
                    fulldelete($backupbasepath);
1554
                }
1555
 
1556
                $errorinfo = '';
1557
 
1558
                foreach ($precheckresults['errors'] as $error) {
1559
                    $errorinfo .= $error;
1560
                }
1561
 
1562
                if (array_key_exists('warnings', $precheckresults)) {
1563
                    foreach ($precheckresults['warnings'] as $warning) {
1564
                        $errorinfo .= $warning;
1565
                    }
1566
                }
1567
 
1568
                throw new moodle_exception('backupprecheckerrors', 'webservice', '', $errorinfo);
1569
            }
1570
        }
1571
 
1572
        $rc->execute_plan();
1573
        $rc->destroy();
1574
 
1575
        $course = $DB->get_record('course', array('id' => $newcourseid), '*', MUST_EXIST);
1576
        $course->fullname = $params['fullname'];
1577
        $course->shortname = $params['shortname'];
1578
        $course->visible = $params['visible'];
1579
 
1580
        // Set shortname and fullname back.
1581
        $DB->update_record('course', $course);
1582
 
1583
        if (empty($CFG->keeptempdirectoriesonbackup)) {
1584
            fulldelete($backupbasepath);
1585
        }
1586
 
1587
        // Delete the course backup file created by this WebService. Originally located in the course backups area.
1588
        $file->delete();
1589
 
1590
        return array('id' => $course->id, 'shortname' => $course->shortname);
1591
    }
1592
 
1593
    /**
1594
     * Returns description of method result value
1595
     *
1596
     * @return \core_external\external_description
1597
     * @since Moodle 2.3
1598
     */
1599
    public static function duplicate_course_returns() {
1600
        return new external_single_structure(
1601
            array(
1602
                'id'       => new external_value(PARAM_INT, 'course id'),
1603
                'shortname' => new external_value(PARAM_RAW, 'short name'),
1604
            )
1605
        );
1606
    }
1607
 
1608
    /**
1609
     * Returns description of method parameters for import_course
1610
     *
1611
     * @return external_function_parameters
1612
     * @since Moodle 2.4
1613
     */
1614
    public static function import_course_parameters() {
1615
        return new external_function_parameters(
1616
            array(
1617
                'importfrom' => new external_value(PARAM_INT, 'the id of the course we are importing from'),
1618
                'importto' => new external_value(PARAM_INT, 'the id of the course we are importing to'),
1619
                'deletecontent' => new external_value(PARAM_INT, 'whether to delete the course content where we are importing to (default to 0 = No)', VALUE_DEFAULT, 0),
1620
                'options' => new external_multiple_structure(
1621
                    new external_single_structure(
1622
                        array(
1623
                                'name' => new external_value(PARAM_ALPHA, 'The backup option name:
1624
                                            "activities" (int) Include course activites (default to 1 that is equal to yes),
1625
                                            "blocks" (int) Include course blocks (default to 1 that is equal to yes),
1626
                                            "filters" (int) Include course filters  (default to 1 that is equal to yes)'
1627
                                            ),
1628
                                'value' => new external_value(PARAM_RAW, 'the value for the option 1 (yes) or 0 (no)'
1629
                            )
1630
                        )
1631
                    ), 'Course import options', VALUE_DEFAULT, array()
1632
                ),
1633
            )
1634
        );
1635
    }
1636
 
1637
    /**
1638
     * Imports a course
1639
     *
1640
     * @param int $importfrom The id of the course we are importing from
1641
     * @param int $importto The id of the course we are importing to
1642
     * @param bool $deletecontent Whether to delete the course we are importing to content
1643
     * @param array $options List of backup options
1644
     * @return null
1645
     * @since Moodle 2.4
1646
     */
1647
    public static function import_course($importfrom, $importto, $deletecontent = 0, $options = array()) {
1648
        global $CFG, $USER, $DB;
1649
        require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
1650
        require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
1651
 
1652
        // Parameter validation.
1653
        $params = self::validate_parameters(
1654
            self::import_course_parameters(),
1655
            array(
1656
                'importfrom' => $importfrom,
1657
                'importto' => $importto,
1658
                'deletecontent' => $deletecontent,
1659
                'options' => $options
1660
            )
1661
        );
1662
 
1663
        if ($params['deletecontent'] !== 0 and $params['deletecontent'] !== 1) {
1664
            throw new moodle_exception('invalidextparam', 'webservice', '', $params['deletecontent']);
1665
        }
1666
 
1667
        // Context validation.
1668
 
1669
        if (! ($importfrom = $DB->get_record('course', array('id'=>$params['importfrom'])))) {
1670
            throw new moodle_exception('invalidcourseid', 'error');
1671
        }
1672
 
1673
        if (! ($importto = $DB->get_record('course', array('id'=>$params['importto'])))) {
1674
            throw new moodle_exception('invalidcourseid', 'error');
1675
        }
1676
 
1677
        $importfromcontext = context_course::instance($importfrom->id);
1678
        self::validate_context($importfromcontext);
1679
 
1680
        $importtocontext = context_course::instance($importto->id);
1681
        self::validate_context($importtocontext);
1682
 
1683
        $backupdefaults = array(
1684
            'activities' => 1,
1685
            'blocks' => 1,
1686
            'filters' => 1
1687
        );
1688
 
1689
        $backupsettings = array();
1690
 
1691
        // Check for backup and restore options.
1692
        if (!empty($params['options'])) {
1693
            foreach ($params['options'] as $option) {
1694
 
1695
                // Strict check for a correct value (allways 1 or 0, true or false).
1696
                $value = clean_param($option['value'], PARAM_INT);
1697
 
1698
                if ($value !== 0 and $value !== 1) {
1699
                    throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
1700
                }
1701
 
1702
                if (!isset($backupdefaults[$option['name']])) {
1703
                    throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
1704
                }
1705
 
1706
                $backupsettings[$option['name']] = $value;
1707
            }
1708
        }
1709
 
1710
        // Capability checking.
1711
 
1712
        require_capability('moodle/backup:backuptargetimport', $importfromcontext);
1713
        require_capability('moodle/restore:restoretargetimport', $importtocontext);
1714
 
1715
        $bc = new backup_controller(backup::TYPE_1COURSE, $importfrom->id, backup::FORMAT_MOODLE,
1716
                backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id);
1717
 
1718
        foreach ($backupsettings as $name => $value) {
1719
            $bc->get_plan()->get_setting($name)->set_value($value);
1720
        }
1721
 
1722
        $backupid       = $bc->get_backupid();
1723
        $backupbasepath = $bc->get_plan()->get_basepath();
1724
 
1725
        $bc->execute_plan();
1726
        $bc->destroy();
1727
 
1728
        // Restore the backup immediately.
1729
 
1730
        // Check if we must delete the contents of the destination course.
1731
        if ($params['deletecontent']) {
1732
            $restoretarget = backup::TARGET_EXISTING_DELETING;
1733
        } else {
1734
            $restoretarget = backup::TARGET_EXISTING_ADDING;
1735
        }
1736
 
1737
        $rc = new restore_controller($backupid, $importto->id,
1738
                backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id, $restoretarget);
1739
 
1740
        foreach ($backupsettings as $name => $value) {
1741
            $rc->get_plan()->get_setting($name)->set_value($value);
1742
        }
1743
 
1744
        if (!$rc->execute_precheck()) {
1745
            $precheckresults = $rc->get_precheck_results();
1746
            if (is_array($precheckresults) && !empty($precheckresults['errors'])) {
1747
                if (empty($CFG->keeptempdirectoriesonbackup)) {
1748
                    fulldelete($backupbasepath);
1749
                }
1750
 
1751
                $errorinfo = '';
1752
 
1753
                foreach ($precheckresults['errors'] as $error) {
1754
                    $errorinfo .= $error;
1755
                }
1756
 
1757
                if (array_key_exists('warnings', $precheckresults)) {
1758
                    foreach ($precheckresults['warnings'] as $warning) {
1759
                        $errorinfo .= $warning;
1760
                    }
1761
                }
1762
 
1763
                throw new moodle_exception('backupprecheckerrors', 'webservice', '', $errorinfo);
1764
            }
1765
        } else {
1766
            if ($restoretarget == backup::TARGET_EXISTING_DELETING) {
1767
                restore_dbops::delete_course_content($importto->id);
1768
            }
1769
        }
1770
 
1771
        $rc->execute_plan();
1772
        $rc->destroy();
1773
 
1774
        if (empty($CFG->keeptempdirectoriesonbackup)) {
1775
            fulldelete($backupbasepath);
1776
        }
1777
 
1778
        return null;
1779
    }
1780
 
1781
    /**
1782
     * Returns description of method result value
1783
     *
1784
     * @return \core_external\external_description
1785
     * @since Moodle 2.4
1786
     */
1787
    public static function import_course_returns() {
1788
        return null;
1789
    }
1790
 
1791
    /**
1792
     * Returns description of method parameters
1793
     *
1794
     * @return external_function_parameters
1795
     * @since Moodle 2.3
1796
     */
1797
    public static function get_categories_parameters() {
1798
        return new external_function_parameters(
1799
            array(
1800
                'criteria' => new external_multiple_structure(
1801
                    new external_single_structure(
1802
                        array(
1803
                            'key' => new external_value(PARAM_ALPHA,
1804
                                         'The category column to search, expected keys (value format) are:'.
1805
                                         '"id" (int) the category id,'.
1806
                                         '"ids" (string) category ids separated by commas,'.
1807
                                         '"name" (string) the category name,'.
1808
                                         '"parent" (int) the parent category id,'.
1809
                                         '"idnumber" (string) category idnumber'.
1810
                                         ' - user must have \'moodle/category:manage\' to search on idnumber,'.
1811
                                         '"visible" (int) whether the returned categories must be visible or hidden. If the key is not passed,
1812
                                             then the function return all categories that the user can see.'.
1813
                                         ' - user must have \'moodle/category:manage\' or \'moodle/category:viewhiddencategories\' to search on visible,'.
1814
                                         '"theme" (string) only return the categories having this theme'.
1815
                                         ' - user must have \'moodle/category:manage\' to search on theme'),
1816
                            'value' => new external_value(PARAM_RAW, 'the value to match')
1817
                        )
1818
                    ), 'criteria', VALUE_DEFAULT, array()
1819
                ),
1820
                'addsubcategories' => new external_value(PARAM_BOOL, 'return the sub categories infos
1821
                                          (1 - default) otherwise only the category info (0)', VALUE_DEFAULT, 1)
1822
            )
1823
        );
1824
    }
1825
 
1826
    /**
1827
     * Get categories
1828
     *
1829
     * @param array $criteria Criteria to match the results
1830
     * @param booln $addsubcategories obtain only the category (false) or its subcategories (true - default)
1831
     * @return array list of categories
1832
     * @since Moodle 2.3
1833
     */
1834
    public static function get_categories($criteria = array(), $addsubcategories = true) {
1835
        global $CFG, $DB;
1836
        require_once($CFG->dirroot . "/course/lib.php");
1837
 
1838
        // Validate parameters.
1839
        $params = self::validate_parameters(self::get_categories_parameters(),
1840
                array('criteria' => $criteria, 'addsubcategories' => $addsubcategories));
1841
 
1842
        // Retrieve the categories.
1843
        $categories = array();
1844
        if (!empty($params['criteria'])) {
1845
 
1846
            $conditions = array();
1847
            $wheres = array();
1848
            foreach ($params['criteria'] as $crit) {
1849
                $key = trim($crit['key']);
1850
 
1851
                // Trying to avoid duplicate keys.
1852
                if (!isset($conditions[$key])) {
1853
 
1854
                    $context = context_system::instance();
1855
                    $value = null;
1856
                    switch ($key) {
1857
                        case 'id':
1858
                            $value = clean_param($crit['value'], PARAM_INT);
1859
                            $conditions[$key] = $value;
1860
                            $wheres[] = $key . " = :" . $key;
1861
                            break;
1862
 
1863
                        case 'ids':
1864
                            $value = clean_param($crit['value'], PARAM_SEQUENCE);
1865
                            $ids = explode(',', $value);
1866
                            list($sqlids, $paramids) = $DB->get_in_or_equal($ids, SQL_PARAMS_NAMED);
1867
                            $conditions = array_merge($conditions, $paramids);
1868
                            $wheres[] = 'id ' . $sqlids;
1869
                            break;
1870
 
1871
                        case 'idnumber':
1872
                            if (has_capability('moodle/category:manage', $context)) {
1873
                                $value = clean_param($crit['value'], PARAM_RAW);
1874
                                $conditions[$key] = $value;
1875
                                $wheres[] = $key . " = :" . $key;
1876
                            } else {
1877
                                // We must throw an exception.
1878
                                // Otherwise the dev client would think no idnumber exists.
1879
                                throw new moodle_exception('criteriaerror',
1880
                                        'webservice', '', null,
1881
                                        'You don\'t have the permissions to search on the "idnumber" field.');
1882
                            }
1883
                            break;
1884
 
1885
                        case 'name':
1886
                            $value = clean_param($crit['value'], PARAM_TEXT);
1887
                            $conditions[$key] = $value;
1888
                            $wheres[] = $key . " = :" . $key;
1889
                            break;
1890
 
1891
                        case 'parent':
1892
                            $value = clean_param($crit['value'], PARAM_INT);
1893
                            $conditions[$key] = $value;
1894
                            $wheres[] = $key . " = :" . $key;
1895
                            break;
1896
 
1897
                        case 'visible':
1898
                            if (has_capability('moodle/category:viewhiddencategories', $context)) {
1899
                                $value = clean_param($crit['value'], PARAM_INT);
1900
                                $conditions[$key] = $value;
1901
                                $wheres[] = $key . " = :" . $key;
1902
                            } else {
1903
                                throw new moodle_exception('criteriaerror',
1904
                                        'webservice', '', null,
1905
                                        'You don\'t have the permissions to search on the "visible" field.');
1906
                            }
1907
                            break;
1908
 
1909
                        case 'theme':
1910
                            if (has_capability('moodle/category:manage', $context)) {
1911
                                $value = clean_param($crit['value'], PARAM_THEME);
1912
                                $conditions[$key] = $value;
1913
                                $wheres[] = $key . " = :" . $key;
1914
                            } else {
1915
                                throw new moodle_exception('criteriaerror',
1916
                                        'webservice', '', null,
1917
                                        'You don\'t have the permissions to search on the "theme" field.');
1918
                            }
1919
                            break;
1920
 
1921
                        default:
1922
                            throw new moodle_exception('criteriaerror',
1923
                                    'webservice', '', null,
1924
                                    'You can not search on this criteria: ' . $key);
1925
                    }
1926
                }
1927
            }
1928
 
1929
            if (!empty($wheres)) {
1930
                $wheres = implode(" AND ", $wheres);
1931
 
1932
                $categories = $DB->get_records_select('course_categories', $wheres, $conditions);
1933
 
1934
                // Retrieve its sub subcategories (all levels).
1935
                if ($categories and !empty($params['addsubcategories'])) {
1936
                    $newcategories = array();
1937
 
1938
                    // Check if we required visible/theme checks.
1939
                    $additionalselect = '';
1940
                    $additionalparams = array();
1941
                    if (isset($conditions['visible'])) {
1942
                        $additionalselect .= ' AND visible = :visible';
1943
                        $additionalparams['visible'] = $conditions['visible'];
1944
                    }
1945
                    if (isset($conditions['theme'])) {
1946
                        $additionalselect .= ' AND theme= :theme';
1947
                        $additionalparams['theme'] = $conditions['theme'];
1948
                    }
1949
 
1950
                    foreach ($categories as $category) {
1951
                        $sqlselect = $DB->sql_like('path', ':path') . $additionalselect;
1952
                        $sqlparams = array('path' => $category->path.'/%') + $additionalparams; // It will NOT include the specified category.
1953
                        $subcategories = $DB->get_records_select('course_categories', $sqlselect, $sqlparams);
1954
                        $newcategories = $newcategories + $subcategories;   // Both arrays have integer as keys.
1955
                    }
1956
                    $categories = $categories + $newcategories;
1957
                }
1958
            }
1959
 
1960
        } else {
1961
            // Retrieve all categories in the database.
1962
            $categories = $DB->get_records('course_categories');
1963
        }
1964
 
1965
        // The not returned categories. key => category id, value => reason of exclusion.
1966
        $excludedcats = array();
1967
 
1968
        // The returned categories.
1969
        $categoriesinfo = array();
1970
 
1971
        // We need to sort the categories by path.
1972
        // The parent cats need to be checked by the algo first.
1973
        usort($categories, "core_course_external::compare_categories_by_path");
1974
 
1975
        foreach ($categories as $category) {
1976
 
1977
            // Check if the category is a child of an excluded category, if yes exclude it too (excluded => do not return).
1978
            $parents = explode('/', $category->path);
1979
            unset($parents[0]); // First key is always empty because path start with / => /1/2/4.
1980
            foreach ($parents as $parentid) {
1981
                // Note: when the parent exclusion was due to the context,
1982
                // the sub category could still be returned.
1983
                if (isset($excludedcats[$parentid]) and $excludedcats[$parentid] != 'context') {
1984
                    $excludedcats[$category->id] = 'parent';
1985
                }
1986
            }
1987
 
1988
            // Check the user can use the category context.
1989
            $context = context_coursecat::instance($category->id);
1990
            try {
1991
                self::validate_context($context);
1992
            } catch (Exception $e) {
1993
                $excludedcats[$category->id] = 'context';
1994
 
1995
                // If it was the requested category then throw an exception.
1996
                if (isset($params['categoryid']) && $category->id == $params['categoryid']) {
1997
                    $exceptionparam = new stdClass();
1998
                    $exceptionparam->message = $e->getMessage();
1999
                    $exceptionparam->catid = $category->id;
2000
                    throw new moodle_exception('errorcatcontextnotvalid', 'webservice', '', $exceptionparam);
2001
                }
2002
            }
2003
 
2004
            // Return the category information.
2005
            if (!isset($excludedcats[$category->id])) {
2006
 
2007
                // Final check to see if the category is visible to the user.
2008
                if (core_course_category::can_view_category($category)) {
2009
 
2010
                    $categoryinfo = array();
2011
                    $categoryinfo['id'] = $category->id;
2012
                    $categoryinfo['name'] = \core_external\util::format_string($category->name, $context);
2013
                    list($categoryinfo['description'], $categoryinfo['descriptionformat']) =
2014
                        \core_external\util::format_text($category->description, $category->descriptionformat,
2015
                                $context, 'coursecat', 'description', null);
2016
                    $categoryinfo['parent'] = $category->parent;
2017
                    $categoryinfo['sortorder'] = $category->sortorder;
2018
                    $categoryinfo['coursecount'] = $category->coursecount;
2019
                    $categoryinfo['depth'] = $category->depth;
2020
                    $categoryinfo['path'] = $category->path;
2021
 
2022
                    // Some fields only returned for admin.
2023
                    if (has_capability('moodle/category:manage', $context)) {
2024
                        $categoryinfo['idnumber'] = $category->idnumber;
2025
                        $categoryinfo['visible'] = $category->visible;
2026
                        $categoryinfo['visibleold'] = $category->visibleold;
2027
                        $categoryinfo['timemodified'] = $category->timemodified;
2028
                        $categoryinfo['theme'] = clean_param($category->theme, PARAM_THEME);
2029
                    }
2030
 
2031
                    $categoriesinfo[] = $categoryinfo;
2032
                } else {
2033
                    $excludedcats[$category->id] = 'visibility';
2034
                }
2035
            }
2036
        }
2037
 
2038
        // Sorting the resulting array so it looks a bit better for the client developer.
2039
        usort($categoriesinfo, "core_course_external::compare_categories_by_sortorder");
2040
 
2041
        return $categoriesinfo;
2042
    }
2043
 
2044
    /**
2045
     * Sort categories array by path
2046
     * private function: only used by get_categories
2047
     *
2048
     * @param stdClass $category1
2049
     * @param stdClass $category2
2050
     * @return int result of strcmp
2051
     * @since Moodle 2.3
2052
     */
2053
    private static function compare_categories_by_path($category1, $category2) {
2054
        return strcmp($category1->path, $category2->path);
2055
    }
2056
 
2057
    /**
2058
     * Sort categories array by sortorder
2059
     * private function: only used by get_categories
2060
     *
2061
     * @param array $category1
2062
     * @param array $category2
2063
     * @return int result of strcmp
2064
     * @since Moodle 2.3
2065
     */
2066
    private static function compare_categories_by_sortorder($category1, $category2) {
2067
        return strcmp($category1['sortorder'], $category2['sortorder']);
2068
    }
2069
 
2070
    /**
2071
     * Returns description of method result value
2072
     *
2073
     * @return \core_external\external_description
2074
     * @since Moodle 2.3
2075
     */
2076
    public static function get_categories_returns() {
2077
        return new external_multiple_structure(
2078
            new external_single_structure(
2079
                array(
2080
                    'id' => new external_value(PARAM_INT, 'category id'),
2081
                    'name' => new external_value(PARAM_RAW, 'category name'),
2082
                    'idnumber' => new external_value(PARAM_RAW, 'category id number', VALUE_OPTIONAL),
2083
                    'description' => new external_value(PARAM_RAW, 'category description'),
2084
                    'descriptionformat' => new external_format_value('description'),
2085
                    'parent' => new external_value(PARAM_INT, 'parent category id'),
2086
                    'sortorder' => new external_value(PARAM_INT, 'category sorting order'),
2087
                    'coursecount' => new external_value(PARAM_INT, 'number of courses in this category'),
2088
                    'visible' => new external_value(PARAM_INT, '1: available, 0:not available', VALUE_OPTIONAL),
2089
                    'visibleold' => new external_value(PARAM_INT, '1: available, 0:not available', VALUE_OPTIONAL),
2090
                    'timemodified' => new external_value(PARAM_INT, 'timestamp', VALUE_OPTIONAL),
2091
                    'depth' => new external_value(PARAM_INT, 'category depth'),
2092
                    'path' => new external_value(PARAM_TEXT, 'category path'),
2093
                    'theme' => new external_value(PARAM_THEME, 'category theme', VALUE_OPTIONAL),
2094
                ), 'List of categories'
2095
            )
2096
        );
2097
    }
2098
 
2099
    /**
2100
     * Returns description of method parameters
2101
     *
2102
     * @return external_function_parameters
2103
     * @since Moodle 2.3
2104
     */
2105
    public static function create_categories_parameters() {
2106
        return new external_function_parameters(
2107
            array(
2108
                'categories' => new external_multiple_structure(
2109
                        new external_single_structure(
2110
                            array(
2111
                                'name' => new external_value(PARAM_TEXT, 'new category name'),
2112
                                'parent' => new external_value(PARAM_INT,
2113
                                        'the parent category id inside which the new category will be created
2114
                                         - set to 0 for a root category',
2115
                                        VALUE_DEFAULT, 0),
2116
                                'idnumber' => new external_value(PARAM_RAW,
2117
                                        'the new category idnumber', VALUE_OPTIONAL),
2118
                                'description' => new external_value(PARAM_RAW,
2119
                                        'the new category description', VALUE_OPTIONAL),
2120
                                'descriptionformat' => new external_format_value('description', VALUE_DEFAULT),
2121
                                'theme' => new external_value(PARAM_THEME,
2122
                                        'the new category theme. This option must be enabled on moodle',
2123
                                        VALUE_OPTIONAL),
2124
                        )
2125
                    )
2126
                )
2127
            )
2128
        );
2129
    }
2130
 
2131
    /**
2132
     * Create categories
2133
     *
2134
     * @param array $categories - see create_categories_parameters() for the array structure
2135
     * @return array - see create_categories_returns() for the array structure
2136
     * @since Moodle 2.3
2137
     */
2138
    public static function create_categories($categories) {
2139
        global $DB;
2140
 
2141
        $params = self::validate_parameters(self::create_categories_parameters(),
2142
                        array('categories' => $categories));
2143
 
2144
        $transaction = $DB->start_delegated_transaction();
2145
 
2146
        $createdcategories = array();
2147
        foreach ($params['categories'] as $category) {
2148
            if ($category['parent']) {
2149
                if (!$DB->record_exists('course_categories', array('id' => $category['parent']))) {
2150
                    throw new moodle_exception('unknowcategory');
2151
                }
2152
                $context = context_coursecat::instance($category['parent']);
2153
            } else {
2154
                $context = context_system::instance();
2155
            }
2156
            self::validate_context($context);
2157
            require_capability('moodle/category:manage', $context);
2158
 
2159
            // this will validate format and throw an exception if there are errors
2160
            util::validate_format($category['descriptionformat']);
2161
 
2162
            $newcategory = core_course_category::create($category);
2163
            $context = context_coursecat::instance($newcategory->id);
2164
 
2165
            $createdcategories[] = array(
2166
                'id' => $newcategory->id,
2167
                'name' => \core_external\util::format_string($newcategory->name, $context),
2168
            );
2169
        }
2170
 
2171
        $transaction->allow_commit();
2172
 
2173
        return $createdcategories;
2174
    }
2175
 
2176
    /**
2177
     * Returns description of method parameters
2178
     *
2179
     * @return external_function_parameters
2180
     * @since Moodle 2.3
2181
     */
2182
    public static function create_categories_returns() {
2183
        return new external_multiple_structure(
2184
            new external_single_structure(
2185
                array(
2186
                    'id' => new external_value(PARAM_INT, 'new category id'),
2187
                    'name' => new external_value(PARAM_RAW, 'new category name'),
2188
                )
2189
            )
2190
        );
2191
    }
2192
 
2193
    /**
2194
     * Returns description of method parameters
2195
     *
2196
     * @return external_function_parameters
2197
     * @since Moodle 2.3
2198
     */
2199
    public static function update_categories_parameters() {
2200
        return new external_function_parameters(
2201
            array(
2202
                'categories' => new external_multiple_structure(
2203
                    new external_single_structure(
2204
                        array(
2205
                            'id'       => new external_value(PARAM_INT, 'course id'),
2206
                            'name' => new external_value(PARAM_TEXT, 'category name', VALUE_OPTIONAL),
2207
                            'idnumber' => new external_value(PARAM_RAW, 'category id number', VALUE_OPTIONAL),
2208
                            'parent' => new external_value(PARAM_INT, 'parent category id', VALUE_OPTIONAL),
2209
                            'description' => new external_value(PARAM_RAW, 'category description', VALUE_OPTIONAL),
2210
                            'descriptionformat' => new external_format_value('description', VALUE_DEFAULT),
2211
                            'theme' => new external_value(PARAM_THEME,
2212
                                    'the category theme. This option must be enabled on moodle', VALUE_OPTIONAL),
2213
                        )
2214
                    )
2215
                )
2216
            )
2217
        );
2218
    }
2219
 
2220
    /**
2221
     * Update categories
2222
     *
2223
     * @param array $categories The list of categories to update
2224
     * @return null
2225
     * @since Moodle 2.3
2226
     */
2227
    public static function update_categories($categories) {
2228
        global $DB;
2229
 
2230
        // Validate parameters.
2231
        $params = self::validate_parameters(self::update_categories_parameters(), array('categories' => $categories));
2232
 
2233
        $transaction = $DB->start_delegated_transaction();
2234
 
2235
        foreach ($params['categories'] as $cat) {
2236
            $category = core_course_category::get($cat['id']);
2237
 
2238
            $categorycontext = context_coursecat::instance($cat['id']);
2239
            self::validate_context($categorycontext);
2240
            require_capability('moodle/category:manage', $categorycontext);
2241
 
2242
            // If the category parent is being changed, check for capability in the new parent category
2243
            if (isset($cat['parent']) && ($cat['parent'] !== $category->parent)) {
2244
                if ($cat['parent'] == 0) {
2245
                    // Creating a top level category requires capability in the system context
2246
                    $parentcontext = context_system::instance();
2247
                } else {
2248
                    // Category context
2249
                    $parentcontext = context_coursecat::instance($cat['parent']);
2250
                }
2251
                self::validate_context($parentcontext);
2252
                require_capability('moodle/category:manage', $parentcontext);
2253
            }
2254
 
2255
            // this will throw an exception if descriptionformat is not valid
2256
            util::validate_format($cat['descriptionformat']);
2257
 
2258
            $category->update($cat);
2259
        }
2260
 
2261
        $transaction->allow_commit();
2262
    }
2263
 
2264
    /**
2265
     * Returns description of method result value
2266
     *
2267
     * @return \core_external\external_description
2268
     * @since Moodle 2.3
2269
     */
2270
    public static function update_categories_returns() {
2271
        return null;
2272
    }
2273
 
2274
    /**
2275
     * Returns description of method parameters
2276
     *
2277
     * @return external_function_parameters
2278
     * @since Moodle 2.3
2279
     */
2280
    public static function delete_categories_parameters() {
2281
        return new external_function_parameters(
2282
            array(
2283
                'categories' => new external_multiple_structure(
2284
                    new external_single_structure(
2285
                        array(
2286
                            'id' => new external_value(PARAM_INT, 'category id to delete'),
2287
                            'newparent' => new external_value(PARAM_INT,
2288
                                'the parent category to move the contents to, if specified', VALUE_OPTIONAL),
2289
                            'recursive' => new external_value(PARAM_BOOL, '1: recursively delete all contents inside this
2290
                                category, 0 (default): move contents to newparent or current parent category (except if parent is root)', VALUE_DEFAULT, 0)
2291
                        )
2292
                    )
2293
                )
2294
            )
2295
        );
2296
    }
2297
 
2298
    /**
2299
     * Delete categories
2300
     *
2301
     * @param array $categories A list of category ids
2302
     * @return array
2303
     * @since Moodle 2.3
2304
     */
2305
    public static function delete_categories($categories) {
2306
        global $CFG, $DB;
2307
        require_once($CFG->dirroot . "/course/lib.php");
2308
 
2309
        // Validate parameters.
2310
        $params = self::validate_parameters(self::delete_categories_parameters(), array('categories' => $categories));
2311
 
2312
        $transaction = $DB->start_delegated_transaction();
2313
 
2314
        foreach ($params['categories'] as $category) {
2315
            $deletecat = core_course_category::get($category['id'], MUST_EXIST);
2316
            $context = context_coursecat::instance($deletecat->id);
2317
            require_capability('moodle/category:manage', $context);
2318
            self::validate_context($context);
2319
            self::validate_context(get_category_or_system_context($deletecat->parent));
2320
 
2321
            if ($category['recursive']) {
2322
                // If recursive was specified, then we recursively delete the category's contents.
2323
                if ($deletecat->can_delete_full()) {
2324
                    $deletecat->delete_full(false);
2325
                } else {
2326
                    throw new moodle_exception('youcannotdeletecategory', '', '', $deletecat->get_formatted_name());
2327
                }
2328
            } else {
2329
                // In this situation, we don't delete the category's contents, we either move it to newparent or parent.
2330
                // If the parent is the root, moving is not supported (because a course must always be inside a category).
2331
                // We must move to an existing category.
2332
                if (!empty($category['newparent'])) {
2333
                    $newparentcat = core_course_category::get($category['newparent']);
2334
                } else {
2335
                    $newparentcat = core_course_category::get($deletecat->parent);
2336
                }
2337
 
2338
                // This operation is not allowed. We must move contents to an existing category.
2339
                if (!$newparentcat->id) {
2340
                    throw new moodle_exception('movecatcontentstoroot');
2341
                }
2342
 
2343
                self::validate_context(context_coursecat::instance($newparentcat->id));
2344
                if ($deletecat->can_move_content_to($newparentcat->id)) {
2345
                    $deletecat->delete_move($newparentcat->id, false);
2346
                } else {
2347
                    throw new moodle_exception('youcannotdeletecategory', '', '', $deletecat->get_formatted_name());
2348
                }
2349
            }
2350
        }
2351
 
2352
        $transaction->allow_commit();
2353
    }
2354
 
2355
    /**
2356
     * Returns description of method parameters
2357
     *
2358
     * @return external_function_parameters
2359
     * @since Moodle 2.3
2360
     */
2361
    public static function delete_categories_returns() {
2362
        return null;
2363
    }
2364
 
2365
    /**
2366
     * Describes the parameters for delete_modules.
2367
     *
2368
     * @return external_function_parameters
2369
     * @since Moodle 2.5
2370
     */
2371
    public static function delete_modules_parameters() {
2372
        return new external_function_parameters (
2373
            array(
2374
                'cmids' => new external_multiple_structure(new external_value(PARAM_INT, 'course module ID',
2375
                        VALUE_REQUIRED, '', NULL_NOT_ALLOWED), 'Array of course module IDs'),
2376
            )
2377
        );
2378
    }
2379
 
2380
    /**
2381
     * Deletes a list of provided module instances.
2382
     *
2383
     * @param array $cmids the course module ids
2384
     * @since Moodle 2.5
2385
     */
2386
    public static function delete_modules($cmids) {
2387
        global $CFG, $DB;
2388
 
2389
        // Require course file containing the course delete module function.
2390
        require_once($CFG->dirroot . "/course/lib.php");
2391
 
2392
        // Clean the parameters.
2393
        $params = self::validate_parameters(self::delete_modules_parameters(), array('cmids' => $cmids));
2394
 
2395
        // Keep track of the course ids we have performed a capability check on to avoid repeating.
2396
        $arrcourseschecked = array();
2397
 
2398
        foreach ($params['cmids'] as $cmid) {
2399
            // Get the course module.
2400
            $cm = $DB->get_record('course_modules', array('id' => $cmid), '*', MUST_EXIST);
2401
 
2402
            // Check if we have not yet confirmed they have permission in this course.
2403
            if (!in_array($cm->course, $arrcourseschecked)) {
2404
                // Ensure the current user has required permission in this course.
2405
                $context = context_course::instance($cm->course);
2406
                self::validate_context($context);
2407
                // Add to the array.
2408
                $arrcourseschecked[] = $cm->course;
2409
            }
2410
 
2411
            // Ensure they can delete this module.
2412
            $modcontext = context_module::instance($cm->id);
2413
            require_capability('moodle/course:manageactivities', $modcontext);
2414
 
2415
            // Delete the module.
2416
            course_delete_module($cm->id);
2417
        }
2418
    }
2419
 
2420
    /**
2421
     * Describes the delete_modules return value.
2422
     *
2423
     * @return external_single_structure
2424
     * @since Moodle 2.5
2425
     */
2426
    public static function delete_modules_returns() {
2427
        return null;
2428
    }
2429
 
2430
    /**
2431
     * Returns description of method parameters
2432
     *
2433
     * @return external_function_parameters
2434
     * @since Moodle 2.9
2435
     */
2436
    public static function view_course_parameters() {
2437
        return new external_function_parameters(
2438
            array(
2439
                'courseid' => new external_value(PARAM_INT, 'id of the course'),
2440
                'sectionnumber' => new external_value(PARAM_INT, 'section number', VALUE_DEFAULT, 0)
2441
            )
2442
        );
2443
    }
2444
 
2445
    /**
2446
     * Trigger the course viewed event.
2447
     *
2448
     * @param int $courseid id of course
2449
     * @param int $sectionnumber sectionnumber (0, 1, 2...)
2450
     * @return array of warnings and status result
2451
     * @since Moodle 2.9
2452
     * @throws moodle_exception
2453
     */
2454
    public static function view_course($courseid, $sectionnumber = 0) {
2455
        global $CFG;
2456
        require_once($CFG->dirroot . "/course/lib.php");
2457
 
2458
        $params = self::validate_parameters(self::view_course_parameters(),
2459
                                            array(
2460
                                                'courseid' => $courseid,
2461
                                                'sectionnumber' => $sectionnumber
2462
                                            ));
2463
 
2464
        $warnings = array();
2465
 
2466
        $course = get_course($params['courseid']);
2467
        $context = context_course::instance($course->id);
2468
        self::validate_context($context);
2469
 
2470
        if (!empty($params['sectionnumber'])) {
2471
 
2472
            // Get section details and check it exists.
2473
            $modinfo = get_fast_modinfo($course);
2474
            $coursesection = $modinfo->get_section_info($params['sectionnumber'], MUST_EXIST);
2475
 
2476
            // Check user is allowed to see it.
2477
            if (!$coursesection->uservisible) {
2478
                require_capability('moodle/course:viewhiddensections', $context);
2479
            }
2480
        }
2481
 
2482
        course_view($context, $params['sectionnumber']);
2483
 
2484
        $result = array();
2485
        $result['status'] = true;
2486
        $result['warnings'] = $warnings;
2487
        return $result;
2488
    }
2489
 
2490
    /**
2491
     * Returns description of method result value
2492
     *
2493
     * @return \core_external\external_description
2494
     * @since Moodle 2.9
2495
     */
2496
    public static function view_course_returns() {
2497
        return new external_single_structure(
2498
            array(
2499
                'status' => new external_value(PARAM_BOOL, 'status: true if success'),
2500
                'warnings' => new external_warnings()
2501
            )
2502
        );
2503
    }
2504
 
2505
    /**
2506
     * Returns description of method parameters
2507
     *
2508
     * @return external_function_parameters
2509
     * @since Moodle 3.0
2510
     */
2511
    public static function search_courses_parameters() {
2512
        return new external_function_parameters(
2513
            array(
2514
                'criterianame'  => new external_value(PARAM_ALPHA, 'criteria name
2515
                                                        (search, modulelist (only admins), blocklist (only admins), tagid)'),
2516
                'criteriavalue' => new external_value(PARAM_RAW, 'criteria value'),
2517
                'page'          => new external_value(PARAM_INT, 'page number (0 based)', VALUE_DEFAULT, 0),
2518
                'perpage'       => new external_value(PARAM_INT, 'items per page', VALUE_DEFAULT, 0),
2519
                'requiredcapabilities' => new external_multiple_structure(
2520
                    new external_value(PARAM_CAPABILITY, 'Capability string used to filter courses by permission'),
2521
                    'Optional list of required capabilities (used to filter the list)', VALUE_DEFAULT, array()
2522
                ),
2523
                'limittoenrolled' => new external_value(PARAM_BOOL, 'limit to enrolled courses', VALUE_DEFAULT, 0),
2524
                'onlywithcompletion' => new external_value(PARAM_BOOL, 'limit to courses where completion is enabled',
2525
                    VALUE_DEFAULT, 0),
2526
            )
2527
        );
2528
    }
2529
 
2530
    /**
2531
     * Return the course information that is public (visible by every one)
2532
     *
2533
     * @param  core_course_list_element $course        course in list object
2534
     * @param  stdClass       $coursecontext course context object
2535
     * @return array the course information
2536
     * @since  Moodle 3.2
2537
     */
2538
    protected static function get_course_public_information(core_course_list_element $course, $coursecontext) {
2539
        global $OUTPUT;
2540
 
2541
        static $categoriescache = array();
2542
 
2543
        // Category information.
2544
        if (!array_key_exists($course->category, $categoriescache)) {
2545
            $categoriescache[$course->category] = core_course_category::get($course->category, IGNORE_MISSING);
2546
        }
2547
        $category = $categoriescache[$course->category];
2548
 
2549
        // Retrieve course overview used files.
2550
        $files = array();
2551
        foreach ($course->get_course_overviewfiles() as $file) {
2552
            $fileurl = moodle_url::make_webservice_pluginfile_url($file->get_contextid(), $file->get_component(),
2553
                                                                    $file->get_filearea(), null, $file->get_filepath(),
2554
                                                                    $file->get_filename())->out(false);
2555
            $files[] = array(
2556
                'filename' => $file->get_filename(),
2557
                'fileurl' => $fileurl,
2558
                'filesize' => $file->get_filesize(),
2559
                'filepath' => $file->get_filepath(),
2560
                'mimetype' => $file->get_mimetype(),
2561
                'timemodified' => $file->get_timemodified(),
2562
            );
2563
        }
2564
 
2565
        // Retrieve the course contacts,
2566
        // we need here the users fullname since if we are not enrolled can be difficult to obtain them via other Web Services.
2567
        $coursecontacts = array();
2568
        foreach ($course->get_course_contacts() as $contact) {
2569
             $coursecontacts[] = array(
2570
                'id' => $contact['user']->id,
2571
                'fullname' => $contact['username'],
2572
                'roles' => array_map(function($role){
2573
                    return array('id' => $role->id, 'name' => $role->displayname);
2574
                }, $contact['roles']),
2575
                'role' => array('id' => $contact['role']->id, 'name' => $contact['role']->displayname),
2576
                'rolename' => $contact['rolename']
2577
             );
2578
        }
2579
 
2580
        // Allowed enrolment methods (maybe we can self-enrol).
2581
        $enroltypes = array();
2582
        $instances = enrol_get_instances($course->id, true);
2583
        foreach ($instances as $instance) {
2584
            $enroltypes[] = $instance->enrol;
2585
        }
2586
 
2587
        // Format summary.
2588
        list($summary, $summaryformat) =
2589
            \core_external\util::format_text($course->summary, $course->summaryformat, $coursecontext, 'course', 'summary', null);
2590
 
2591
        $categoryname = '';
2592
        if (!empty($category)) {
2593
            $categoryname = \core_external\util::format_string($category->name, $category->get_context());
2594
        }
2595
 
2596
        $displayname = get_course_display_name_for_list($course);
2597
        $coursereturns = array();
2598
        $coursereturns['id']                = $course->id;
2599
        $coursereturns['fullname']          = \core_external\util::format_string($course->fullname, $coursecontext);
2600
        $coursereturns['displayname']       = \core_external\util::format_string($displayname, $coursecontext);
2601
        $coursereturns['shortname']         = \core_external\util::format_string($course->shortname, $coursecontext);
2602
        $coursereturns['categoryid']        = $course->category;
2603
        $coursereturns['categoryname']      = $categoryname;
2604
        $coursereturns['summary']           = $summary;
2605
        $coursereturns['summaryformat']     = $summaryformat;
2606
        $coursereturns['summaryfiles']      = util::get_area_files($coursecontext->id, 'course', 'summary', false, false);
2607
        $coursereturns['overviewfiles']     = $files;
2608
        $coursereturns['contacts']          = $coursecontacts;
2609
        $coursereturns['enrollmentmethods'] = $enroltypes;
2610
        $coursereturns['sortorder']         = $course->sortorder;
2611
        $coursereturns['showactivitydates'] = $course->showactivitydates;
2612
        $coursereturns['showcompletionconditions'] = $course->showcompletionconditions;
2613
 
2614
        $handler = core_course\customfield\course_handler::create();
2615
        if ($customfields = $handler->export_instance_data($course->id)) {
2616
            $coursereturns['customfields'] = [];
2617
            foreach ($customfields as $data) {
2618
                $coursereturns['customfields'][] = [
2619
                    'type' => $data->get_type(),
2620
                    'value' => $data->get_value(),
2621
                    'valueraw' => $data->get_data_controller()->get_value(),
2622
                    'name' => $data->get_name(),
2623
                    'shortname' => $data->get_shortname()
2624
                ];
2625
            }
2626
        }
2627
 
2628
        $courseimage = \core_course\external\course_summary_exporter::get_course_image($course);
2629
        if (!$courseimage) {
2630
            $courseimage = $OUTPUT->get_generated_url_for_course($coursecontext);
2631
        }
2632
        $coursereturns['courseimage'] = $courseimage;
2633
 
2634
        return $coursereturns;
2635
    }
2636
 
2637
    /**
2638
     * Search courses following the specified criteria.
2639
     *
2640
     * @param string $criterianame  Criteria name (search, modulelist (only admins), blocklist (only admins), tagid)
2641
     * @param string $criteriavalue Criteria value
2642
     * @param int $page             Page number (for pagination)
2643
     * @param int $perpage          Items per page
2644
     * @param array $requiredcapabilities Optional list of required capabilities (used to filter the list).
2645
     * @param int $limittoenrolled  Limit to only enrolled courses
2646
     * @param int onlywithcompletion Limit to only courses where completion is enabled
2647
     * @return array of course objects and warnings
2648
     * @since Moodle 3.0
2649
     * @throws moodle_exception
2650
     */
2651
    public static function search_courses($criterianame,
2652
                                          $criteriavalue,
2653
                                          $page=0,
2654
                                          $perpage=0,
2655
                                          $requiredcapabilities=array(),
2656
                                          $limittoenrolled=0,
2657
                                          $onlywithcompletion=0) {
2658
        global $CFG;
2659
 
2660
        $warnings = array();
2661
 
2662
        $parameters = array(
2663
            'criterianame'  => $criterianame,
2664
            'criteriavalue' => $criteriavalue,
2665
            'page'          => $page,
2666
            'perpage'       => $perpage,
2667
            'requiredcapabilities' => $requiredcapabilities,
2668
            'limittoenrolled' => $limittoenrolled,
2669
            'onlywithcompletion' => $onlywithcompletion
2670
        );
2671
        $params = self::validate_parameters(self::search_courses_parameters(), $parameters);
2672
        self::validate_context(context_system::instance());
2673
 
2674
        $allowedcriterianames = array('search', 'modulelist', 'blocklist', 'tagid');
2675
        if (!in_array($params['criterianame'], $allowedcriterianames)) {
2676
            throw new invalid_parameter_exception('Invalid value for criterianame parameter (value: '.$params['criterianame'].'),' .
2677
                'allowed values are: '.implode(',', $allowedcriterianames));
2678
        }
2679
 
2680
        if ($params['criterianame'] == 'modulelist' or $params['criterianame'] == 'blocklist') {
2681
            require_capability('moodle/site:config', context_system::instance());
2682
        }
2683
 
2684
        $paramtype = array(
2685
            'search' => PARAM_RAW,
2686
            'modulelist' => PARAM_PLUGIN,
2687
            'blocklist' => PARAM_INT,
2688
            'tagid' => PARAM_INT
2689
        );
2690
        $params['criteriavalue'] = clean_param($params['criteriavalue'], $paramtype[$params['criterianame']]);
2691
 
2692
        // Prepare the search API options.
2693
        $searchcriteria = array();
2694
        $searchcriteria[$params['criterianame']] = $params['criteriavalue'];
2695
        if ($params['onlywithcompletion']) {
2696
            $searchcriteria['onlywithcompletion'] = true;
2697
        }
2698
        if ($params['limittoenrolled']) {
2699
            $searchcriteria['limittoenrolled'] = true;
2700
        }
2701
 
2702
        $options = array();
2703
        if ($params['perpage'] != 0) {
2704
            $offset = $params['page'] * $params['perpage'];
2705
            $options = array('offset' => $offset, 'limit' => $params['perpage']);
2706
        }
2707
 
2708
        // Search the courses.
2709
        $courses = core_course_category::search_courses($searchcriteria, $options, $params['requiredcapabilities']);
2710
        $totalcount = core_course_category::search_courses_count($searchcriteria, $options, $params['requiredcapabilities']);
2711
 
2712
        $finalcourses = array();
2713
        $categoriescache = array();
2714
 
2715
        foreach ($courses as $course) {
2716
            $coursecontext = context_course::instance($course->id);
2717
 
2718
            $finalcourses[] = self::get_course_public_information($course, $coursecontext);
2719
        }
2720
 
2721
        return array(
2722
            'total' => $totalcount,
2723
            'courses' => $finalcourses,
2724
            'warnings' => $warnings
2725
        );
2726
    }
2727
 
2728
    /**
2729
     * Returns a course structure definition
2730
     *
2731
     * @param  boolean $onlypublicdata set to true, to retrieve only fields viewable by anyone when the course is visible
2732
     * @return external_single_structure the course structure
2733
     * @since  Moodle 3.2
2734
     */
2735
    protected static function get_course_structure($onlypublicdata = true) {
2736
        $coursestructure = array(
2737
            'id' => new external_value(PARAM_INT, 'course id'),
2738
            'fullname' => new external_value(PARAM_RAW, 'course full name'),
2739
            'displayname' => new external_value(PARAM_RAW, 'course display name'),
2740
            'shortname' => new external_value(PARAM_RAW, 'course short name'),
2741
            'courseimage' => new external_value(PARAM_URL, 'Course image', VALUE_OPTIONAL),
2742
            'categoryid' => new external_value(PARAM_INT, 'category id'),
2743
            'categoryname' => new external_value(PARAM_RAW, 'category name'),
2744
            'sortorder' => new external_value(PARAM_INT, 'Sort order in the category', VALUE_OPTIONAL),
2745
            'summary' => new external_value(PARAM_RAW, 'summary'),
2746
            'summaryformat' => new external_format_value('summary'),
2747
            'summaryfiles' => new external_files('summary files in the summary field', VALUE_OPTIONAL),
2748
            'overviewfiles' => new external_files('additional overview files attached to this course'),
2749
            'showactivitydates' => new external_value(PARAM_BOOL, 'Whether the activity dates are shown or not'),
2750
            'showcompletionconditions' => new external_value(PARAM_BOOL,
2751
                'Whether the activity completion conditions are shown or not'),
2752
            'contacts' => new external_multiple_structure(
2753
                new external_single_structure(
2754
                    array(
2755
                        'id' => new external_value(PARAM_INT, 'contact user id'),
2756
                        'fullname'  => new external_value(PARAM_NOTAGS, 'contact user fullname'),
2757
                    )
2758
                ),
2759
                'contact users'
2760
            ),
2761
            'enrollmentmethods' => new external_multiple_structure(
2762
                new external_value(PARAM_PLUGIN, 'enrollment method'),
2763
                'enrollment methods list'
2764
            ),
2765
            'customfields' => new external_multiple_structure(
2766
                new external_single_structure(
2767
                    array(
2768
                        'name' => new external_value(PARAM_RAW, 'The name of the custom field'),
2769
                        'shortname' => new external_value(PARAM_RAW,
2770
                            'The shortname of the custom field - to be able to build the field class in the code'),
2771
                        'type'  => new external_value(PARAM_ALPHANUMEXT,
2772
                            'The type of the custom field - text field, checkbox...'),
2773
                        'valueraw' => new external_value(PARAM_RAW, 'The raw value of the custom field'),
2774
                        'value' => new external_value(PARAM_RAW, 'The value of the custom field'),
2775
                    )
2776
                ), 'Custom fields', VALUE_OPTIONAL),
2777
        );
2778
 
2779
        if (!$onlypublicdata) {
2780
            $extra = array(
2781
                'idnumber' => new external_value(PARAM_RAW, 'Id number', VALUE_OPTIONAL),
2782
                'format' => new external_value(PARAM_PLUGIN, 'Course format: weeks, topics, social, site,..', VALUE_OPTIONAL),
2783
                'showgrades' => new external_value(PARAM_INT, '1 if grades are shown, otherwise 0', VALUE_OPTIONAL),
2784
                'newsitems' => new external_value(PARAM_INT, 'Number of recent items appearing on the course page', VALUE_OPTIONAL),
2785
                'startdate' => new external_value(PARAM_INT, 'Timestamp when the course start', VALUE_OPTIONAL),
2786
                'enddate' => new external_value(PARAM_INT, 'Timestamp when the course end', VALUE_OPTIONAL),
2787
                'maxbytes' => new external_value(PARAM_INT, 'Largest size of file that can be uploaded into', VALUE_OPTIONAL),
2788
                'showreports' => new external_value(PARAM_INT, 'Are activity report shown (yes = 1, no =0)', VALUE_OPTIONAL),
2789
                'visible' => new external_value(PARAM_INT, '1: available to student, 0:not available', VALUE_OPTIONAL),
2790
                'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible', VALUE_OPTIONAL),
2791
                'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no', VALUE_OPTIONAL),
2792
                'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id', VALUE_OPTIONAL),
2793
                'enablecompletion' => new external_value(PARAM_INT, 'Completion enabled? 1: yes 0: no', VALUE_OPTIONAL),
2794
                'completionnotify' => new external_value(PARAM_INT, '1: yes 0: no', VALUE_OPTIONAL),
2795
                'lang' => new external_value(PARAM_SAFEDIR, 'Forced course language', VALUE_OPTIONAL),
2796
                'theme' => new external_value(PARAM_PLUGIN, 'Fame of the forced theme', VALUE_OPTIONAL),
2797
                'marker' => new external_value(PARAM_INT, 'Current course marker', VALUE_OPTIONAL),
2798
                'legacyfiles' => new external_value(PARAM_INT, 'If legacy files are enabled', VALUE_OPTIONAL),
2799
                'calendartype' => new external_value(PARAM_PLUGIN, 'Calendar type', VALUE_OPTIONAL),
2800
                'timecreated' => new external_value(PARAM_INT, 'Time when the course was created', VALUE_OPTIONAL),
2801
                'timemodified' => new external_value(PARAM_INT, 'Last time  the course was updated', VALUE_OPTIONAL),
2802
                'requested' => new external_value(PARAM_INT, 'If is a requested course', VALUE_OPTIONAL),
2803
                'cacherev' => new external_value(PARAM_INT, 'Cache revision number', VALUE_OPTIONAL),
2804
                'filters' => new external_multiple_structure(
2805
                    new external_single_structure(
2806
                        array(
2807
                            'filter'  => new external_value(PARAM_PLUGIN, 'Filter plugin name'),
2808
                            'localstate' => new external_value(PARAM_INT, 'Filter state: 1 for on, -1 for off, 0 if inherit'),
2809
                            'inheritedstate' => new external_value(PARAM_INT, '1 or 0 to use when localstate is set to inherit'),
2810
                        )
2811
                    ),
2812
                    'Course filters', VALUE_OPTIONAL
2813
                ),
2814
                'courseformatoptions' => new external_multiple_structure(
2815
                    new external_single_structure(
2816
                        array(
2817
                            'name' => new external_value(PARAM_RAW, 'Course format option name.'),
2818
                            'value' => new external_value(PARAM_RAW, 'Course format option value.'),
2819
                        )
2820
                    ),
2821
                    'Additional options for particular course format.', VALUE_OPTIONAL
2822
                ),
2823
                'communicationroomname' => new external_value(PARAM_TEXT, 'Communication tool room name.', VALUE_OPTIONAL),
2824
                'communicationroomurl' => new external_value(PARAM_RAW, 'Communication tool room URL.', VALUE_OPTIONAL),
2825
            );
2826
            $coursestructure = array_merge($coursestructure, $extra);
2827
        }
2828
        return new external_single_structure($coursestructure);
2829
    }
2830
 
2831
    /**
2832
     * Returns description of method result value
2833
     *
2834
     * @return \core_external\external_description
2835
     * @since Moodle 3.0
2836
     */
2837
    public static function search_courses_returns() {
2838
        return new external_single_structure(
2839
            array(
2840
                'total' => new external_value(PARAM_INT, 'total course count'),
2841
                'courses' => new external_multiple_structure(self::get_course_structure(), 'course'),
2842
                'warnings' => new external_warnings()
2843
            )
2844
        );
2845
    }
2846
 
2847
    /**
2848
     * Returns description of method parameters
2849
     *
2850
     * @return external_function_parameters
2851
     * @since Moodle 3.0
2852
     */
2853
    public static function get_course_module_parameters() {
2854
        return new external_function_parameters(
2855
            array(
2856
                'cmid' => new external_value(PARAM_INT, 'The course module id')
2857
            )
2858
        );
2859
    }
2860
 
2861
    /**
2862
     * Return information about a course module.
2863
     *
2864
     * @param int $cmid the course module id
2865
     * @return array of warnings and the course module
2866
     * @since Moodle 3.0
2867
     * @throws moodle_exception
2868
     */
2869
    public static function get_course_module($cmid) {
2870
        global $CFG, $DB;
2871
 
2872
        $params = self::validate_parameters(self::get_course_module_parameters(), array('cmid' => $cmid));
2873
        $warnings = array();
2874
 
2875
        $cm = get_coursemodule_from_id(null, $params['cmid'], 0, true, MUST_EXIST);
2876
        $context = context_module::instance($cm->id);
2877
        self::validate_context($context);
2878
 
2879
        // If the user has permissions to manage the activity, return all the information.
2880
        if (has_capability('moodle/course:manageactivities', $context)) {
2881
            require_once($CFG->dirroot . '/course/modlib.php');
2882
            require_once($CFG->libdir . '/gradelib.php');
2883
 
2884
            $info = $cm;
2885
            // Get the extra information: grade, advanced grading and outcomes data.
2886
            $course = get_course($cm->course);
2887
            list($newcm, $newcontext, $module, $extrainfo, $cw) = get_moduleinfo_data($cm, $course);
2888
            // Grades.
2889
            $gradeinfo = array('grade', 'gradepass', 'gradecat');
2890
            foreach ($gradeinfo as $gfield) {
2891
                if (isset($extrainfo->{$gfield})) {
2892
                    $info->{$gfield} = $extrainfo->{$gfield};
2893
                }
2894
            }
2895
            if (isset($extrainfo->grade) and $extrainfo->grade < 0) {
2896
                $info->scale = $DB->get_field('scale', 'scale', array('id' => abs($extrainfo->grade)));
2897
            }
2898
            // Advanced grading.
2899
            if (isset($extrainfo->_advancedgradingdata)) {
2900
                $info->advancedgrading = array();
2901
                foreach ($extrainfo as $key => $val) {
2902
                    if (strpos($key, 'advancedgradingmethod_') === 0) {
2903
                        $info->advancedgrading[] = array(
2904
                            'area' => str_replace('advancedgradingmethod_', '', $key),
2905
                            'method' => $val
2906
                        );
2907
                    }
2908
                }
2909
            }
2910
            // Outcomes.
2911
            foreach ($extrainfo as $key => $val) {
2912
                if (strpos($key, 'outcome_') === 0) {
2913
                    if (!isset($info->outcomes)) {
2914
                        $info->outcomes = array();
2915
                    }
2916
                    $id = str_replace('outcome_', '', $key);
2917
                    $outcome = grade_outcome::fetch(array('id' => $id));
2918
                    $scaleitems = $outcome->load_scale();
2919
                    $info->outcomes[] = array(
2920
                        'id' => $id,
2921
                        'name' => \core_external\util::format_string($outcome->get_name(), $context),
2922
                        'scale' => $scaleitems->scale
2923
                    );
2924
                }
2925
            }
2926
        } else {
2927
            // Return information is safe to show to any user.
2928
            $info = new stdClass();
2929
            $info->id = $cm->id;
2930
            $info->course = $cm->course;
2931
            $info->module = $cm->module;
2932
            $info->modname = $cm->modname;
2933
            $info->instance = $cm->instance;
2934
            $info->section = $cm->section;
2935
            $info->sectionnum = $cm->sectionnum;
2936
            $info->groupmode = $cm->groupmode;
2937
            $info->groupingid = $cm->groupingid;
2938
            $info->completion = $cm->completion;
2939
            $info->downloadcontent = $cm->downloadcontent;
2940
        }
2941
        // Format name.
2942
        $info->name = \core_external\util::format_string($cm->name, $context);
2943
        $result = array();
2944
        $result['cm'] = $info;
2945
        $result['warnings'] = $warnings;
2946
        return $result;
2947
    }
2948
 
2949
    /**
2950
     * Returns description of method result value
2951
     *
2952
     * @return \core_external\external_description
2953
     * @since Moodle 3.0
2954
     */
2955
    public static function get_course_module_returns() {
2956
        return new external_single_structure(
2957
            array(
2958
                'cm' => new external_single_structure(
2959
                    array(
2960
                        'id' => new external_value(PARAM_INT, 'The course module id'),
2961
                        'course' => new external_value(PARAM_INT, 'The course id'),
2962
                        'module' => new external_value(PARAM_INT, 'The module type id'),
2963
                        'name' => new external_value(PARAM_RAW, 'The activity name'),
2964
                        'modname' => new external_value(PARAM_COMPONENT, 'The module component name (forum, assign, etc..)'),
2965
                        'instance' => new external_value(PARAM_INT, 'The activity instance id'),
2966
                        'section' => new external_value(PARAM_INT, 'The module section id'),
2967
                        'sectionnum' => new external_value(PARAM_INT, 'The module section number'),
2968
                        'groupmode' => new external_value(PARAM_INT, 'Group mode'),
2969
                        'groupingid' => new external_value(PARAM_INT, 'Grouping id'),
2970
                        'completion' => new external_value(PARAM_INT, 'If completion is enabled'),
2971
                        'idnumber' => new external_value(PARAM_RAW, 'Module id number', VALUE_OPTIONAL),
2972
                        'added' => new external_value(PARAM_INT, 'Time added', VALUE_OPTIONAL),
2973
                        'score' => new external_value(PARAM_INT, 'Score', VALUE_OPTIONAL),
2974
                        'indent' => new external_value(PARAM_INT, 'Indentation', VALUE_OPTIONAL),
2975
                        'visible' => new external_value(PARAM_INT, 'If visible', VALUE_OPTIONAL),
2976
                        'visibleoncoursepage' => new external_value(PARAM_INT, 'If visible on course page', VALUE_OPTIONAL),
2977
                        'visibleold' => new external_value(PARAM_INT, 'Visible old', VALUE_OPTIONAL),
2978
                        'completiongradeitemnumber' => new external_value(PARAM_INT, 'Completion grade item', VALUE_OPTIONAL),
2979
                        'completionpassgrade' => new external_value(PARAM_INT, 'Completion pass grade setting', VALUE_OPTIONAL),
2980
                        'completionview' => new external_value(PARAM_INT, 'Completion view setting', VALUE_OPTIONAL),
2981
                        'completionexpected' => new external_value(PARAM_INT, 'Completion time expected', VALUE_OPTIONAL),
2982
                        'showdescription' => new external_value(PARAM_INT, 'If the description is showed', VALUE_OPTIONAL),
2983
                        'downloadcontent' => new external_value(PARAM_INT, 'The download content value', VALUE_OPTIONAL),
2984
                        'availability' => new external_value(PARAM_RAW, 'Availability settings', VALUE_OPTIONAL),
2985
                        'grade' => new external_value(PARAM_FLOAT, 'Grade (max value or scale id)', VALUE_OPTIONAL),
2986
                        'scale' => new external_value(PARAM_TEXT, 'Scale items (if used)', VALUE_OPTIONAL),
2987
                        'gradepass' => new external_value(PARAM_RAW, 'Grade to pass (float)', VALUE_OPTIONAL),
2988
                        'gradecat' => new external_value(PARAM_INT, 'Grade category', VALUE_OPTIONAL),
2989
                        'advancedgrading' => new external_multiple_structure(
2990
                            new external_single_structure(
2991
                                array(
2992
                                    'area' => new external_value(PARAM_AREA, 'Gradable area name'),
2993
                                    'method'  => new external_value(PARAM_COMPONENT, 'Grading method'),
2994
                                )
2995
                            ),
2996
                            'Advanced grading settings', VALUE_OPTIONAL
2997
                        ),
2998
                        'outcomes' => new external_multiple_structure(
2999
                            new external_single_structure(
3000
                                array(
3001
                                    'id' => new external_value(PARAM_ALPHANUMEXT, 'Outcome id'),
3002
                                    'name'  => new external_value(PARAM_RAW, 'Outcome full name'),
3003
                                    'scale' => new external_value(PARAM_TEXT, 'Scale items')
3004
                                )
3005
                            ),
3006
                            'Outcomes information', VALUE_OPTIONAL
3007
                        ),
3008
                    )
3009
                ),
3010
                'warnings' => new external_warnings()
3011
            )
3012
        );
3013
    }
3014
 
3015
    /**
3016
     * Returns description of method parameters
3017
     *
3018
     * @return external_function_parameters
3019
     * @since Moodle 3.0
3020
     */
3021
    public static function get_course_module_by_instance_parameters() {
3022
        return new external_function_parameters(
3023
            array(
3024
                'module' => new external_value(PARAM_COMPONENT, 'The module name'),
3025
                'instance' => new external_value(PARAM_INT, 'The module instance id')
3026
            )
3027
        );
3028
    }
3029
 
3030
    /**
3031
     * Return information about a course module.
3032
     *
3033
     * @param string $module the module name
3034
     * @param int $instance the activity instance id
3035
     * @return array of warnings and the course module
3036
     * @since Moodle 3.0
3037
     * @throws moodle_exception
3038
     */
3039
    public static function get_course_module_by_instance($module, $instance) {
3040
 
3041
        $params = self::validate_parameters(self::get_course_module_by_instance_parameters(),
3042
                                            array(
3043
                                                'module' => $module,
3044
                                                'instance' => $instance,
3045
                                            ));
3046
 
3047
        $warnings = array();
3048
        $cm = get_coursemodule_from_instance($params['module'], $params['instance'], 0, false, MUST_EXIST);
3049
 
3050
        return self::get_course_module($cm->id);
3051
    }
3052
 
3053
    /**
3054
     * Returns description of method result value
3055
     *
3056
     * @return \core_external\external_description
3057
     * @since Moodle 3.0
3058
     */
3059
    public static function get_course_module_by_instance_returns() {
3060
        return self::get_course_module_returns();
3061
    }
3062
 
3063
    /**
3064
     * Returns description of method parameters
3065
     *
3066
     * @return external_function_parameters
3067
     * @since Moodle 3.2
3068
     */
3069
    public static function get_user_navigation_options_parameters() {
3070
        return new external_function_parameters(
3071
            array(
3072
                'courseids' => new external_multiple_structure(new external_value(PARAM_INT, 'Course id.')),
3073
            )
3074
        );
3075
    }
3076
 
3077
    /**
3078
     * Return a list of navigation options in a set of courses that are avaialable or not for the current user.
3079
     *
3080
     * @param array $courseids a list of course ids
3081
     * @return array of warnings and the options availability
3082
     * @since Moodle 3.2
3083
     * @throws moodle_exception
3084
     */
3085
    public static function get_user_navigation_options($courseids) {
3086
        global $CFG;
3087
        require_once($CFG->dirroot . '/course/lib.php');
3088
 
3089
        // Parameter validation.
3090
        $params = self::validate_parameters(self::get_user_navigation_options_parameters(), array('courseids' => $courseids));
3091
        $courseoptions = array();
3092
 
3093
        list($courses, $warnings) = util::validate_courses($params['courseids'], array(), true);
3094
 
3095
        if (!empty($courses)) {
3096
            foreach ($courses as $course) {
3097
                // Fix the context for the frontpage.
3098
                if ($course->id == SITEID) {
3099
                    $course->context = context_system::instance();
3100
                }
3101
                $navoptions = course_get_user_navigation_options($course->context, $course);
3102
                $options = array();
3103
                foreach ($navoptions as $name => $available) {
3104
                    $options[] = array(
3105
                        'name' => $name,
3106
                        'available' => $available,
3107
                    );
3108
                }
3109
 
3110
                $courseoptions[] = array(
3111
                    'id' => $course->id,
3112
                    'options' => $options
3113
                );
3114
            }
3115
        }
3116
 
3117
        $result = array(
3118
            'courses' => $courseoptions,
3119
            'warnings' => $warnings
3120
        );
3121
        return $result;
3122
    }
3123
 
3124
    /**
3125
     * Returns description of method result value
3126
     *
3127
     * @return \core_external\external_description
3128
     * @since Moodle 3.2
3129
     */
3130
    public static function get_user_navigation_options_returns() {
3131
        return new external_single_structure(
3132
            array(
3133
                'courses' => new external_multiple_structure(
3134
                    new external_single_structure(
3135
                        array(
3136
                            'id' => new external_value(PARAM_INT, 'Course id'),
3137
                            'options' => new external_multiple_structure(
3138
                                new external_single_structure(
3139
                                    array(
3140
                                        'name' => new external_value(PARAM_ALPHANUMEXT, 'Option name'),
3141
                                        'available' => new external_value(PARAM_BOOL, 'Whether the option is available or not'),
3142
                                    )
3143
                                )
3144
                            )
3145
                        )
3146
                    ), 'List of courses'
3147
                ),
3148
                'warnings' => new external_warnings()
3149
            )
3150
        );
3151
    }
3152
 
3153
    /**
3154
     * Returns description of method parameters
3155
     *
3156
     * @return external_function_parameters
3157
     * @since Moodle 3.2
3158
     */
3159
    public static function get_user_administration_options_parameters() {
3160
        return new external_function_parameters(
3161
            array(
3162
                'courseids' => new external_multiple_structure(new external_value(PARAM_INT, 'Course id.')),
3163
            )
3164
        );
3165
    }
3166
 
3167
    /**
3168
     * Return a list of administration options in a set of courses that are available or not for the current user.
3169
     *
3170
     * @param array $courseids a list of course ids
3171
     * @return array of warnings and the options availability
3172
     * @since Moodle 3.2
3173
     * @throws moodle_exception
3174
     */
3175
    public static function get_user_administration_options($courseids) {
3176
        global $CFG;
3177
        require_once($CFG->dirroot . '/course/lib.php');
3178
 
3179
        // Parameter validation.
3180
        $params = self::validate_parameters(self::get_user_administration_options_parameters(), array('courseids' => $courseids));
3181
        $courseoptions = array();
3182
 
3183
        list($courses, $warnings) = util::validate_courses($params['courseids'], array(), true);
3184
 
3185
        if (!empty($courses)) {
3186
            foreach ($courses as $course) {
3187
                $adminoptions = course_get_user_administration_options($course, $course->context);
3188
                $options = array();
3189
                foreach ($adminoptions as $name => $available) {
3190
                    $options[] = array(
3191
                        'name' => $name,
3192
                        'available' => $available,
3193
                    );
3194
                }
3195
 
3196
                $courseoptions[] = array(
3197
                    'id' => $course->id,
3198
                    'options' => $options
3199
                );
3200
            }
3201
        }
3202
 
3203
        $result = array(
3204
            'courses' => $courseoptions,
3205
            'warnings' => $warnings
3206
        );
3207
        return $result;
3208
    }
3209
 
3210
    /**
3211
     * Returns description of method result value
3212
     *
3213
     * @return \core_external\external_description
3214
     * @since Moodle 3.2
3215
     */
3216
    public static function get_user_administration_options_returns() {
3217
        return self::get_user_navigation_options_returns();
3218
    }
3219
 
3220
    /**
3221
     * Returns description of method parameters
3222
     *
3223
     * @return external_function_parameters
3224
     * @since Moodle 3.2
3225
     */
3226
    public static function get_courses_by_field_parameters() {
3227
        return new external_function_parameters(
3228
            array(
3229
                'field' => new external_value(PARAM_ALPHA, 'The field to search can be left empty for all courses or:
3230
                    id: course id
3231
                    ids: comma separated course ids
3232
                    shortname: course short name
3233
                    idnumber: course id number
3234
                    category: category id the course belongs to
3235
                ', VALUE_DEFAULT, ''),
3236
                'value' => new external_value(PARAM_RAW, 'The value to match', VALUE_DEFAULT, '')
3237
            )
3238
        );
3239
    }
3240
 
3241
 
3242
    /**
3243
     * Get courses matching a specific field (id/s, shortname, idnumber, category)
3244
     *
3245
     * @param  string $field field name to search, or empty for all courses
3246
     * @param  string $value value to search
3247
     * @return array list of courses and warnings
3248
     * @throws  invalid_parameter_exception
3249
     * @since Moodle 3.2
3250
     */
3251
    public static function get_courses_by_field($field = '', $value = '') {
3252
        global $DB, $CFG;
3253
        require_once($CFG->dirroot . '/course/lib.php');
3254
        require_once($CFG->libdir . '/filterlib.php');
3255
 
3256
        $params = self::validate_parameters(self::get_courses_by_field_parameters(),
3257
            array(
3258
                'field' => $field,
3259
                'value' => $value,
3260
            )
3261
        );
3262
        $warnings = array();
3263
 
3264
        if (empty($params['field'])) {
3265
            $courses = $DB->get_records('course', null, 'id ASC');
3266
        } else {
3267
            switch ($params['field']) {
3268
                case 'id':
3269
                case 'category':
3270
                    $value = clean_param($params['value'], PARAM_INT);
3271
                    break;
3272
                case 'ids':
3273
                    $value = clean_param($params['value'], PARAM_SEQUENCE);
3274
                    break;
3275
                case 'shortname':
3276
                    $value = clean_param($params['value'], PARAM_TEXT);
3277
                    break;
3278
                case 'idnumber':
3279
                    $value = clean_param($params['value'], PARAM_RAW);
3280
                    break;
3281
                default:
3282
                    throw new invalid_parameter_exception('Invalid field name');
3283
            }
3284
 
3285
            if ($params['field'] === 'ids') {
3286
                // Preload categories to avoid loading one at a time.
3287
                $courseids = explode(',', $value);
3288
                list ($listsql, $listparams) = $DB->get_in_or_equal($courseids);
3289
                $categoryids = $DB->get_fieldset_sql("
3290
                        SELECT DISTINCT cc.id
3291
                          FROM {course} c
3292
                          JOIN {course_categories} cc ON cc.id = c.category
3293
                         WHERE c.id $listsql", $listparams);
3294
                core_course_category::get_many($categoryids);
3295
 
3296
                // Load and validate all courses. This is called because it loads the courses
3297
                // more efficiently.
3298
                list ($courses, $warnings) = util::validate_courses($courseids, [],
3299
                        false, true);
3300
            } else {
3301
                $courses = $DB->get_records('course', array($params['field'] => $value), 'id ASC');
3302
            }
3303
        }
3304
 
3305
        $iscommapiavailable = \core_communication\api::is_available();
3306
 
3307
        $coursesdata = array();
3308
        foreach ($courses as $course) {
3309
            $context = context_course::instance($course->id);
3310
            $canupdatecourse = has_capability('moodle/course:update', $context);
3311
            $canviewhiddencourses = has_capability('moodle/course:viewhiddencourses', $context);
3312
 
3313
            // Check if the course is visible in the site for the user.
3314
            if (!$course->visible and !$canviewhiddencourses and !$canupdatecourse) {
3315
                continue;
3316
            }
3317
            // Get the public course information, even if we are not enrolled.
3318
            $courseinlist = new core_course_list_element($course);
3319
 
3320
            // Now, check if we have access to the course, unless it was already checked.
3321
            try {
3322
                if (empty($course->contextvalidated)) {
3323
                    self::validate_context($context);
3324
                }
3325
            } catch (Exception $e) {
3326
                // User can not access the course, check if they can see the public information about the course and return it.
3327
                if (core_course_category::can_view_course_info($course)) {
3328
                    $coursesdata[$course->id] = self::get_course_public_information($courseinlist, $context);
3329
                }
3330
                continue;
3331
            }
3332
            $coursesdata[$course->id] = self::get_course_public_information($courseinlist, $context);
3333
            // Return information for any user that can access the course.
3334
            $coursefields = array('format', 'showgrades', 'newsitems', 'startdate', 'enddate', 'maxbytes', 'showreports', 'visible',
3335
                'groupmode', 'groupmodeforce', 'defaultgroupingid', 'enablecompletion', 'completionnotify', 'lang', 'theme',
3336
                'marker');
3337
 
3338
            // Course filters.
3339
            $coursesdata[$course->id]['filters'] = filter_get_available_in_context($context);
3340
 
3341
            // Information for managers only.
3342
            if ($canupdatecourse) {
3343
                $managerfields = array('idnumber', 'legacyfiles', 'calendartype', 'timecreated', 'timemodified', 'requested',
3344
                    'cacherev');
3345
                $coursefields = array_merge($coursefields, $managerfields);
3346
            }
3347
 
3348
            // Populate fields.
3349
            foreach ($coursefields as $field) {
3350
                $coursesdata[$course->id][$field] = $course->{$field};
3351
            }
3352
 
3353
            // Clean lang and auth fields for external functions (it may content uninstalled themes or language packs).
3354
            if (isset($coursesdata[$course->id]['theme'])) {
3355
                $coursesdata[$course->id]['theme'] = clean_param($coursesdata[$course->id]['theme'], PARAM_THEME);
3356
            }
3357
            if (isset($coursesdata[$course->id]['lang'])) {
3358
                $coursesdata[$course->id]['lang'] = clean_param($coursesdata[$course->id]['lang'], PARAM_LANG);
3359
            }
3360
 
3361
            $courseformatoptions = course_get_format($course)->get_config_for_external();
3362
            foreach ($courseformatoptions as $key => $value) {
3363
                $coursesdata[$course->id]['courseformatoptions'][] = array(
3364
                    'name' => $key,
3365
                    'value' => $value
3366
                );
3367
            }
3368
 
3369
            // Communication tools for the course.
3370
            if ($iscommapiavailable) {
3371
                $communication = \core_communication\api::load_by_instance(
3372
                    context: $context,
3373
                    component: 'core_course',
3374
                    instancetype: 'coursecommunication',
3375
                    instanceid: $course->id
3376
                );
3377
                if ($communication->get_provider()) {
3378
                    $coursesdata[$course->id]['communicationroomname'] = \core_external\util::format_string($communication->get_room_name(), $context);
3379
                    // This will be usually an URL, however, it is better to consider that can be anything a plugin might return, this is why we will use PARAM_RAW.
3380
                    $coursesdata[$course->id]['communicationroomurl'] = $communication->get_communication_room_url();
3381
                }
3382
            }
3383
        }
3384
 
3385
        return array(
3386
            'courses' => $coursesdata,
3387
            'warnings' => $warnings
3388
        );
3389
    }
3390
 
3391
    /**
3392
     * Returns description of method result value
3393
     *
3394
     * @return \core_external\external_description
3395
     * @since Moodle 3.2
3396
     */
3397
    public static function get_courses_by_field_returns() {
3398
        // Course structure, including not only public viewable fields.
3399
        return new external_single_structure(
3400
            array(
3401
                'courses' => new external_multiple_structure(self::get_course_structure(false), 'Course'),
3402
                'warnings' => new external_warnings()
3403
            )
3404
        );
3405
    }
3406
 
3407
    /**
3408
     * Returns description of method parameters
3409
     *
3410
     * @return external_function_parameters
3411
     * @since Moodle 3.2
3412
     */
3413
    public static function check_updates_parameters() {
3414
        return new external_function_parameters(
3415
            array(
3416
                'courseid' => new external_value(PARAM_INT, 'Course id to check'),
3417
                'tocheck' => new external_multiple_structure(
3418
                    new external_single_structure(
3419
                        array(
3420
                            'contextlevel' => new external_value(PARAM_ALPHA, 'The context level for the file location.
3421
                                                                                Only module supported right now.'),
3422
                            'id' => new external_value(PARAM_INT, 'Context instance id'),
3423
                            'since' => new external_value(PARAM_INT, 'Check updates since this time stamp'),
3424
                        )
3425
                    ),
3426
                    'Instances to check'
3427
                ),
3428
                'filter' => new external_multiple_structure(
3429
                    new external_value(PARAM_ALPHANUM, 'Area name: configuration, fileareas, completion, ratings, comments,
3430
                                                        gradeitems, outcomes'),
3431
                    'Check only for updates in these areas', VALUE_DEFAULT, array()
3432
                )
3433
            )
3434
        );
3435
    }
3436
 
3437
    /**
3438
     * Check if there is updates affecting the user for the given course and contexts.
3439
     * Right now only modules are supported.
3440
     * This WS calls mod_check_updates_since for each module to check if there is any update the user should we aware of.
3441
     *
3442
     * @param int $courseid the list of modules to check
3443
     * @param array $tocheck the list of modules to check
3444
     * @param array $filter check only for updates in these areas
3445
     * @return array list of updates and warnings
3446
     * @throws moodle_exception
3447
     * @since Moodle 3.2
3448
     */
3449
    public static function check_updates($courseid, $tocheck, $filter = array()) {
3450
        global $CFG, $DB;
3451
        require_once($CFG->dirroot . "/course/lib.php");
3452
 
3453
        $params = self::validate_parameters(
3454
            self::check_updates_parameters(),
3455
            array(
3456
                'courseid' => $courseid,
3457
                'tocheck' => $tocheck,
3458
                'filter' => $filter,
3459
            )
3460
        );
3461
 
3462
        $course = get_course($params['courseid']);
3463
        $context = context_course::instance($course->id);
3464
        self::validate_context($context);
3465
 
3466
        list($instances, $warnings) = course_check_updates($course, $params['tocheck'], $filter);
3467
 
3468
        $instancesformatted = array();
3469
        foreach ($instances as $instance) {
3470
            $updates = array();
3471
            foreach ($instance['updates'] as $name => $data) {
3472
                if (empty($data->updated)) {
3473
                    continue;
3474
                }
3475
                $updatedata = array(
3476
                    'name' => $name,
3477
                );
3478
                if (!empty($data->timeupdated)) {
3479
                    $updatedata['timeupdated'] = $data->timeupdated;
3480
                }
3481
                if (!empty($data->itemids)) {
3482
                    $updatedata['itemids'] = $data->itemids;
3483
                }
3484
                $updates[] = $updatedata;
3485
            }
3486
            if (!empty($updates)) {
3487
                $instancesformatted[] = array(
3488
                    'contextlevel' => $instance['contextlevel'],
3489
                    'id' => $instance['id'],
3490
                    'updates' => $updates
3491
                );
3492
            }
3493
        }
3494
 
3495
        return array(
3496
            'instances' => $instancesformatted,
3497
            'warnings' => $warnings
3498
        );
3499
    }
3500
 
3501
    /**
3502
     * Returns description of method result value
3503
     *
3504
     * @return \core_external\external_description
3505
     * @since Moodle 3.2
3506
     */
3507
    public static function check_updates_returns() {
3508
        return new external_single_structure(
3509
            array(
3510
                'instances' => new external_multiple_structure(
3511
                    new external_single_structure(
3512
                        array(
3513
                            'contextlevel' => new external_value(PARAM_ALPHA, 'The context level'),
3514
                            'id' => new external_value(PARAM_INT, 'Instance id'),
3515
                            'updates' => new external_multiple_structure(
3516
                                new external_single_structure(
3517
                                    array(
3518
                                        'name' => new external_value(PARAM_ALPHANUMEXT, 'Name of the area updated.'),
3519
                                        'timeupdated' => new external_value(PARAM_INT, 'Last time was updated', VALUE_OPTIONAL),
3520
                                        'itemids' => new external_multiple_structure(
3521
                                            new external_value(PARAM_INT, 'Instance id'),
3522
                                            'The ids of the items updated',
3523
                                            VALUE_OPTIONAL
3524
                                        )
3525
                                    )
3526
                                )
3527
                            )
3528
                        )
3529
                    )
3530
                ),
3531
                'warnings' => new external_warnings()
3532
            )
3533
        );
3534
    }
3535
 
3536
    /**
3537
     * Returns description of method parameters
3538
     *
3539
     * @return external_function_parameters
3540
     * @since Moodle 3.3
3541
     */
3542
    public static function get_updates_since_parameters() {
3543
        return new external_function_parameters(
3544
            array(
3545
                'courseid' => new external_value(PARAM_INT, 'Course id to check'),
3546
                'since' => new external_value(PARAM_INT, 'Check updates since this time stamp'),
3547
                'filter' => new external_multiple_structure(
3548
                    new external_value(PARAM_ALPHANUM, 'Area name: configuration, fileareas, completion, ratings, comments,
3549
                                                        gradeitems, outcomes'),
3550
                    'Check only for updates in these areas', VALUE_DEFAULT, array()
3551
                )
3552
            )
3553
        );
3554
    }
3555
 
3556
    /**
3557
     * Check if there are updates affecting the user for the given course since the given time stamp.
3558
     *
3559
     * This function is a wrapper of self::check_updates for retrieving all the updates since a given time for all the activities.
3560
     *
3561
     * @param int $courseid the list of modules to check
3562
     * @param int $since check updates since this time stamp
3563
     * @param array $filter check only for updates in these areas
3564
     * @return array list of updates and warnings
3565
     * @throws moodle_exception
3566
     * @since Moodle 3.3
3567
     */
3568
    public static function get_updates_since($courseid, $since, $filter = array()) {
3569
        global $CFG, $DB;
3570
 
3571
        $params = self::validate_parameters(
3572
            self::get_updates_since_parameters(),
3573
            array(
3574
                'courseid' => $courseid,
3575
                'since' => $since,
3576
                'filter' => $filter,
3577
            )
3578
        );
3579
 
3580
        $course = get_course($params['courseid']);
3581
        $modinfo = get_fast_modinfo($course);
3582
        $tocheck = array();
3583
 
3584
        // Retrieve all the visible course modules for the current user.
3585
        $cms = $modinfo->get_cms();
3586
        foreach ($cms as $cm) {
3587
            if (!$cm->uservisible) {
3588
                continue;
3589
            }
3590
            $tocheck[] = array(
3591
                'id' => $cm->id,
3592
                'contextlevel' => 'module',
3593
                'since' => $params['since'],
3594
            );
3595
        }
3596
 
3597
        return self::check_updates($course->id, $tocheck, $params['filter']);
3598
    }
3599
 
3600
    /**
3601
     * Returns description of method result value
3602
     *
3603
     * @return \core_external\external_description
3604
     * @since Moodle 3.3
3605
     */
3606
    public static function get_updates_since_returns() {
3607
        return self::check_updates_returns();
3608
    }
3609
 
3610
    /**
3611
     * Parameters for function edit_module()
3612
     *
3613
     * @since Moodle 3.3
3614
     * @return external_function_parameters
3615
     */
3616
    public static function edit_module_parameters() {
3617
        return new external_function_parameters(
3618
            array(
3619
                'action' => new external_value(PARAM_ALPHA,
3620
                    'action: hide, show, stealth, duplicate, delete, moveleft, moveright, group...', VALUE_REQUIRED),
3621
                'id' => new external_value(PARAM_INT, 'course module id', VALUE_REQUIRED),
3622
                'sectionreturn' => new external_value(PARAM_INT, 'section to return to', VALUE_DEFAULT, null),
3623
            ));
3624
    }
3625
 
3626
    /**
3627
     * Performs one of the edit module actions and return new html for AJAX
3628
     *
3629
     * Returns html to replace the current module html with, for example:
3630
     * - empty string for "delete" action,
3631
     * - two modules html for "duplicate" action
3632
     * - updated module html for everything else
3633
     *
3634
     * Throws exception if operation is not permitted/possible
3635
     *
3636
     * @since Moodle 3.3
3637
     * @param string $action
3638
     * @param int $id
3639
     * @param null|int $sectionreturn
3640
     * @return string
3641
     */
3642
    public static function edit_module($action, $id, $sectionreturn = null) {
3643
        global $PAGE, $DB;
3644
        // Validate and normalize parameters.
3645
        $params = self::validate_parameters(self::edit_module_parameters(),
3646
            array('action' => $action, 'id' => $id, 'sectionreturn' => $sectionreturn));
3647
        $action = $params['action'];
3648
        $id = $params['id'];
3649
        $sectionreturn = $params['sectionreturn'];
3650
 
3651
        // Set of permissions an editing user may have.
3652
        $contextarray = [
3653
                'moodle/course:update',
3654
                'moodle/course:manageactivities',
3655
                'moodle/course:activityvisibility',
3656
                'moodle/course:sectionvisibility',
3657
                'moodle/course:movesections',
3658
                'moodle/course:setcurrentsection',
3659
        ];
3660
        $PAGE->set_other_editing_capability($contextarray);
3661
 
3662
        list($course, $cm) = get_course_and_cm_from_cmid($id);
3663
        $modcontext = context_module::instance($cm->id);
3664
        $coursecontext = context_course::instance($course->id);
3665
        self::validate_context($modcontext);
3666
        $format = course_get_format($course);
3667
        if (!is_null($sectionreturn)) {
3668
            $format->set_sectionnum($sectionreturn);
3669
        }
3670
        $renderer = $format->get_renderer($PAGE);
3671
 
3672
        switch($action) {
3673
            case 'hide':
3674
            case 'show':
3675
            case 'stealth':
3676
                require_capability('moodle/course:activityvisibility', $modcontext);
3677
                $visible = ($action === 'hide') ? 0 : 1;
3678
                $visibleoncoursepage = ($action === 'stealth') ? 0 : 1;
3679
                set_coursemodule_visible($id, $visible, $visibleoncoursepage);
3680
                \core\event\course_module_updated::create_from_cm($cm, $modcontext)->trigger();
3681
                break;
3682
            case 'duplicate':
3683
                require_capability('moodle/course:manageactivities', $coursecontext);
3684
                require_capability('moodle/backup:backuptargetimport', $coursecontext);
3685
                require_capability('moodle/restore:restoretargetimport', $coursecontext);
3686
                if (!course_allowed_module($course, $cm->modname)) {
3687
                    throw new moodle_exception('No permission to create that activity');
3688
                }
3689
                if ($newcm = duplicate_module($course, $cm)) {
3690
 
3691
                    $modinfo = $format->get_modinfo();
3692
                    $section = $modinfo->get_section_info($newcm->sectionnum);
3693
                    $cm = $modinfo->get_cm($id);
3694
 
3695
                    // Get both original and new element html.
3696
                    $result = $renderer->course_section_updated_cm_item($format, $section, $cm);
3697
                    $result .= $renderer->course_section_updated_cm_item($format, $section, $newcm);
3698
                    return $result;
3699
                }
3700
                break;
3701
            case 'groupsseparate':
3702
            case 'groupsvisible':
3703
            case 'groupsnone':
3704
                require_capability('moodle/course:manageactivities', $modcontext);
3705
                if ($action === 'groupsseparate') {
3706
                    $newgroupmode = SEPARATEGROUPS;
3707
                } else if ($action === 'groupsvisible') {
3708
                    $newgroupmode = VISIBLEGROUPS;
3709
                } else {
3710
                    $newgroupmode = NOGROUPS;
3711
                }
3712
                if (set_coursemodule_groupmode($cm->id, $newgroupmode)) {
3713
                    \core\event\course_module_updated::create_from_cm($cm, $modcontext)->trigger();
3714
                }
3715
                break;
3716
            case 'moveleft':
3717
            case 'moveright':
3718
                require_capability('moodle/course:manageactivities', $modcontext);
3719
                $indent = $cm->indent + (($action === 'moveright') ? 1 : -1);
3720
                if ($cm->indent >= 0) {
3721
                    $DB->update_record('course_modules', array('id' => $cm->id, 'indent' => $indent));
3722
                    rebuild_course_cache($cm->course);
3723
                }
3724
                break;
3725
            case 'delete':
3726
                require_capability('moodle/course:manageactivities', $modcontext);
3727
                course_delete_module($cm->id, true);
3728
                return '';
3729
            default:
3730
                throw new coding_exception('Unrecognised action');
3731
        }
3732
 
3733
        $modinfo = $format->get_modinfo();
3734
        $section = $modinfo->get_section_info($cm->sectionnum);
3735
        $cm = $modinfo->get_cm($id);
3736
        return $renderer->course_section_updated_cm_item($format, $section, $cm);
3737
    }
3738
 
3739
    /**
3740
     * Return structure for edit_module()
3741
     *
3742
     * @since Moodle 3.3
3743
     * @return \core_external\external_description
3744
     */
3745
    public static function edit_module_returns() {
3746
        return new external_value(PARAM_RAW, 'html to replace the current module with');
3747
    }
3748
 
3749
    /**
3750
     * Parameters for function get_module()
3751
     *
3752
     * @since Moodle 3.3
3753
     * @return external_function_parameters
3754
     */
3755
    public static function get_module_parameters() {
3756
        return new external_function_parameters(
3757
            array(
3758
                'id' => new external_value(PARAM_INT, 'course module id', VALUE_REQUIRED),
3759
                'sectionreturn' => new external_value(PARAM_INT, 'section to return to', VALUE_DEFAULT, null),
3760
            ));
3761
    }
3762
 
3763
    /**
3764
     * Returns html for displaying one activity module on course page
3765
     *
3766
     * @since Moodle 3.3
3767
     * @param int $id
3768
     * @param null|int $sectionreturn
3769
     * @return string
3770
     */
3771
    public static function get_module($id, $sectionreturn = null) {
3772
        global $PAGE;
3773
        // Validate and normalize parameters.
3774
        $params = self::validate_parameters(self::get_module_parameters(),
3775
            array('id' => $id, 'sectionreturn' => $sectionreturn));
3776
        $id = $params['id'];
3777
        $sectionreturn = $params['sectionreturn'];
3778
 
3779
        // Set of permissions an editing user may have.
3780
        $contextarray = [
3781
            'moodle/course:update',
3782
            'moodle/course:manageactivities',
3783
            'moodle/course:activityvisibility',
3784
            'moodle/course:sectionvisibility',
3785
            'moodle/course:movesections',
3786
            'moodle/course:setcurrentsection',
3787
        ];
3788
        $PAGE->set_other_editing_capability($contextarray);
3789
 
3790
        // Validate access to the course (note, this is html for the course view page, we don't validate access to the module).
3791
        list($course, $cm) = get_course_and_cm_from_cmid($id);
3792
        self::validate_context(context_course::instance($course->id));
3793
 
3794
        $format = course_get_format($course);
3795
        if (!is_null($sectionreturn)) {
3796
            $format->set_sectionnum($sectionreturn);
3797
        }
3798
        $renderer = $format->get_renderer($PAGE);
3799
 
3800
        $modinfo = $format->get_modinfo();
3801
        $section = $modinfo->get_section_info($cm->sectionnum);
3802
        return $renderer->course_section_updated_cm_item($format, $section, $cm);
3803
    }
3804
 
3805
    /**
3806
     * Return structure for get_module()
3807
     *
3808
     * @since Moodle 3.3
3809
     * @return \core_external\external_description
3810
     */
3811
    public static function get_module_returns() {
3812
        return new external_value(PARAM_RAW, 'html to replace the current module with');
3813
    }
3814
 
3815
    /**
3816
     * Parameters for function edit_section()
3817
     *
3818
     * @since Moodle 3.3
3819
     * @return external_function_parameters
3820
     */
3821
    public static function edit_section_parameters() {
3822
        return new external_function_parameters(
3823
            array(
3824
                'action' => new external_value(PARAM_ALPHA, 'action: hide, show, stealth, setmarker, removemarker', VALUE_REQUIRED),
3825
                'id' => new external_value(PARAM_INT, 'course section id', VALUE_REQUIRED),
3826
                'sectionreturn' => new external_value(PARAM_INT, 'section to return to', VALUE_DEFAULT, null),
3827
            ));
3828
    }
3829
 
3830
    /**
3831
     * Performs one of the edit section actions
3832
     *
3833
     * @since Moodle 3.3
3834
     * @param string $action
3835
     * @param int $id section id
3836
     * @param int $sectionreturn section to return to
3837
     * @return string
3838
     */
3839
    public static function edit_section($action, $id, $sectionreturn) {
3840
        global $DB;
3841
        // Validate and normalize parameters.
3842
        $params = self::validate_parameters(self::edit_section_parameters(),
3843
            array('action' => $action, 'id' => $id, 'sectionreturn' => $sectionreturn));
3844
        $action = $params['action'];
3845
        $id = $params['id'];
3846
        $sr = $params['sectionreturn'];
3847
 
3848
        $section = $DB->get_record('course_sections', array('id' => $id), '*', MUST_EXIST);
3849
        $coursecontext = context_course::instance($section->course);
3850
        self::validate_context($coursecontext);
3851
 
3852
        $rv = course_get_format($section->course)->section_action($section, $action, $sectionreturn);
3853
        if ($rv) {
3854
            return json_encode($rv);
3855
        } else {
3856
            return null;
3857
        }
3858
    }
3859
 
3860
    /**
3861
     * Return structure for edit_section()
3862
     *
3863
     * @since Moodle 3.3
3864
     * @return \core_external\external_description
3865
     */
3866
    public static function edit_section_returns() {
3867
        return new external_value(PARAM_RAW, 'Additional data for javascript (JSON-encoded string)');
3868
    }
3869
 
3870
    /**
3871
     * Returns description of method parameters
3872
     *
3873
     * @return external_function_parameters
3874
     */
3875
    public static function get_enrolled_courses_by_timeline_classification_parameters() {
3876
        return new external_function_parameters(
3877
            array(
3878
                'classification' => new external_value(PARAM_ALPHA, 'future, inprogress, or past'),
3879
                'limit' => new external_value(PARAM_INT, 'Result set limit', VALUE_DEFAULT, 0),
3880
                'offset' => new external_value(PARAM_INT, 'Result set offset', VALUE_DEFAULT, 0),
3881
                'sort' => new external_value(PARAM_TEXT, 'Sort string', VALUE_DEFAULT, null),
3882
                'customfieldname' => new external_value(PARAM_ALPHANUMEXT, 'Used when classification = customfield',
3883
                    VALUE_DEFAULT, null),
3884
                'customfieldvalue' => new external_value(PARAM_RAW, 'Used when classification = customfield',
3885
                    VALUE_DEFAULT, null),
3886
                'searchvalue' => new external_value(PARAM_RAW, 'The value a user wishes to search against',
3887
                    VALUE_DEFAULT, null),
3888
                'requiredfields' => new core_external\external_multiple_structure(
3889
                    new external_value(PARAM_ALPHANUMEXT, 'Field name to be included from the results', VALUE_DEFAULT),
3890
                    'Array of the only field names that need to be returned. If empty, all fields will be returned.',
3891
                    VALUE_DEFAULT, []
3892
                ),
3893
            )
3894
        );
3895
    }
3896
 
3897
    /**
3898
     * Get courses matching the given timeline classification.
3899
     *
3900
     * NOTE: The offset applies to the unfiltered full set of courses before the classification
3901
     * filtering is done.
3902
     * E.g.
3903
     * If the user is enrolled in 5 courses:
3904
     * c1, c2, c3, c4, and c5
3905
     * And c4 and c5 are 'future' courses
3906
     *
3907
     * If a request comes in for future courses with an offset of 1 it will mean that
3908
     * c1 is skipped (because the offset applies *before* the classification filtering)
3909
     * and c4 and c5 will be return.
3910
     *
3911
     * @param string $classification past, inprogress, or future
3912
     * @param int $limit Result set limit
3913
     * @param int $offset Offset the full course set before timeline classification is applied
3914
     * @param string|null $sort SQL sort string for results
3915
     * @param string|null $customfieldname
3916
     * @param string|null $customfieldvalue
3917
     * @param string|null $searchvalue
3918
     * @param array $requiredfields Array of the only field names that need to be returned. If empty, all fields will be returned.
3919
     * @return array list of courses and warnings
3920
     */
3921
    public static function get_enrolled_courses_by_timeline_classification(
3922
        string $classification,
3923
        int $limit = 0,
3924
        int $offset = 0,
3925
        string $sort = null,
3926
        string $customfieldname = null,
3927
        string $customfieldvalue = null,
3928
        string $searchvalue = null,
3929
        array $requiredfields = []
3930
    ) {
3931
        global $CFG, $PAGE, $USER;
3932
        require_once($CFG->dirroot . '/course/lib.php');
3933
 
3934
        $params = self::validate_parameters(self::get_enrolled_courses_by_timeline_classification_parameters(),
3935
            array(
3936
                'classification' => $classification,
3937
                'limit' => $limit,
3938
                'offset' => $offset,
3939
                'sort' => $sort,
3940
                'customfieldvalue' => $customfieldvalue,
3941
                'searchvalue' => $searchvalue,
3942
                'requiredfields' => $requiredfields,
3943
            )
3944
        );
3945
 
3946
        $classification = $params['classification'];
3947
        $limit = $params['limit'];
3948
        $offset = $params['offset'];
3949
        $sort = $params['sort'];
3950
        $customfieldvalue = $params['customfieldvalue'];
3951
        $searchvalue = clean_param($params['searchvalue'], PARAM_TEXT);
3952
        $requiredfields = $params['requiredfields'];
3953
 
3954
        switch($classification) {
3955
            case COURSE_TIMELINE_ALLINCLUDINGHIDDEN:
3956
                break;
3957
            case COURSE_TIMELINE_ALL:
3958
                break;
3959
            case COURSE_TIMELINE_PAST:
3960
                break;
3961
            case COURSE_TIMELINE_INPROGRESS:
3962
                break;
3963
            case COURSE_TIMELINE_FUTURE:
3964
                break;
3965
            case COURSE_FAVOURITES:
3966
                break;
3967
            case COURSE_TIMELINE_HIDDEN:
3968
                break;
3969
            case COURSE_TIMELINE_SEARCH:
3970
                break;
3971
            case COURSE_CUSTOMFIELD:
3972
                break;
3973
            default:
3974
                throw new invalid_parameter_exception('Invalid classification');
3975
        }
3976
 
3977
        self::validate_context(context_user::instance($USER->id));
3978
        $exporterfields = array_keys(course_summary_exporter::define_properties());
3979
        // Get the required properties from the exporter fields based on the required fields.
3980
        $requiredproperties = array_intersect($exporterfields, $requiredfields);
3981
        // If the resulting required properties is empty, fall back to the exporter fields.
3982
        if (empty($requiredproperties)) {
3983
            $requiredproperties = $exporterfields;
3984
        }
3985
 
3986
        $fields = join(',', $requiredproperties);
3987
        $hiddencourses = get_hidden_courses_on_timeline();
3988
 
3989
        // If the timeline requires really all courses, get really all courses.
3990
        if ($classification == COURSE_TIMELINE_ALLINCLUDINGHIDDEN) {
3991
            $courses = course_get_enrolled_courses_for_logged_in_user(0, $offset, $sort, $fields, COURSE_DB_QUERY_LIMIT);
3992
 
3993
            // Otherwise if the timeline requires the hidden courses then restrict the result to only $hiddencourses.
3994
        } else if ($classification == COURSE_TIMELINE_HIDDEN) {
3995
            $courses = course_get_enrolled_courses_for_logged_in_user(0, $offset, $sort, $fields,
3996
                COURSE_DB_QUERY_LIMIT, $hiddencourses);
3997
 
3998
            // Otherwise get the requested courses and exclude the hidden courses.
3999
        } else if ($classification == COURSE_TIMELINE_SEARCH) {
4000
            // Prepare the search API options.
4001
            $searchcriteria['search'] = $searchvalue;
4002
            $options = ['idonly' => true];
4003
            $courses = course_get_enrolled_courses_for_logged_in_user_from_search(
4004
                0,
4005
                $offset,
4006
                $sort,
4007
                $fields,
4008
                COURSE_DB_QUERY_LIMIT,
4009
                $searchcriteria,
4010
                $options
4011
            );
4012
        } else {
4013
            $courses = course_get_enrolled_courses_for_logged_in_user(0, $offset, $sort, $fields,
4014
                COURSE_DB_QUERY_LIMIT, [], $hiddencourses);
4015
        }
4016
 
4017
        $favouritecourseids = [];
4018
        $ufservice = \core_favourites\service_factory::get_service_for_user_context(\context_user::instance($USER->id));
4019
        $favourites = $ufservice->find_favourites_by_type('core_course', 'courses');
4020
 
4021
        if ($favourites) {
4022
            $favouritecourseids = array_map(
4023
                function($favourite) {
4024
                    return $favourite->itemid;
4025
                }, $favourites);
4026
        }
4027
 
4028
        if ($classification == COURSE_FAVOURITES) {
4029
            list($filteredcourses, $processedcount) = course_filter_courses_by_favourites(
4030
                $courses,
4031
                $favouritecourseids,
4032
                $limit
4033
            );
4034
        } else if ($classification == COURSE_CUSTOMFIELD) {
4035
            list($filteredcourses, $processedcount) = course_filter_courses_by_customfield(
4036
                $courses,
4037
                $customfieldname,
4038
                $customfieldvalue,
4039
                $limit
4040
            );
4041
        } else {
4042
            list($filteredcourses, $processedcount) = course_filter_courses_by_timeline_classification(
4043
                $courses,
4044
                $classification,
4045
                $limit
4046
            );
4047
        }
4048
 
4049
        $renderer = $PAGE->get_renderer('core');
4050
        $formattedcourses = array_map(function($course) use ($renderer, $favouritecourseids) {
4051
            if ($course == null) {
4052
                return;
4053
            }
4054
            context_helper::preload_from_record($course);
4055
            $context = context_course::instance($course->id);
4056
            $isfavourite = false;
4057
            if (in_array($course->id, $favouritecourseids)) {
4058
                $isfavourite = true;
4059
            }
4060
            $exporter = new course_summary_exporter($course, ['context' => $context, 'isfavourite' => $isfavourite]);
4061
            return $exporter->export($renderer);
4062
        }, $filteredcourses);
4063
 
4064
        $formattedcourses = array_filter($formattedcourses, function($course) {
4065
            if ($course != null) {
4066
                return $course;
4067
            }
4068
        });
4069
 
4070
        return [
4071
            'courses' => $formattedcourses,
4072
            'nextoffset' => $offset + $processedcount
4073
        ];
4074
    }
4075
 
4076
    /**
4077
     * Returns description of method result value
4078
     *
4079
     * @return \core_external\external_description
4080
     */
4081
    public static function get_enrolled_courses_by_timeline_classification_returns() {
4082
        return new external_single_structure(
4083
            array(
4084
                'courses' => new external_multiple_structure(course_summary_exporter::get_read_structure(), 'Course'),
4085
                'nextoffset' => new external_value(PARAM_INT, 'Offset for the next request')
4086
            )
4087
        );
4088
    }
4089
 
4090
    /**
4091
     * Returns description of method parameters
4092
     *
4093
     * @return external_function_parameters
4094
     */
4095
    public static function set_favourite_courses_parameters() {
4096
        return new external_function_parameters(
4097
            array(
4098
                'courses' => new external_multiple_structure(
4099
                    new external_single_structure(
4100
                        array(
4101
                            'id' => new external_value(PARAM_INT, 'course ID'),
4102
                            'favourite' => new external_value(PARAM_BOOL, 'favourite status')
4103
                        )
4104
                    )
4105
                )
4106
            )
4107
        );
4108
    }
4109
 
4110
    /**
4111
     * Set the course favourite status for an array of courses.
4112
     *
4113
     * @param  array $courses List with course id's and favourite status.
4114
     * @return array Array with an array of favourite courses.
4115
     */
4116
    public static function set_favourite_courses(
4117
        array $courses
4118
    ) {
4119
        global $USER;
4120
 
4121
        $params = self::validate_parameters(self::set_favourite_courses_parameters(),
4122
            array(
4123
                'courses' => $courses
4124
            )
4125
        );
4126
 
4127
        $warnings = [];
4128
 
4129
        $ufservice = \core_favourites\service_factory::get_service_for_user_context(\context_user::instance($USER->id));
4130
 
4131
        foreach ($params['courses'] as $course) {
4132
 
4133
            $warning = [];
4134
 
4135
            $favouriteexists = $ufservice->favourite_exists('core_course', 'courses', $course['id'],
4136
                    \context_course::instance($course['id']));
4137
 
4138
            if ($course['favourite']) {
4139
                if (!$favouriteexists) {
4140
                    try {
4141
                        $ufservice->create_favourite('core_course', 'courses', $course['id'],
4142
                                \context_course::instance($course['id']));
4143
                    } catch (Exception $e) {
4144
                        $warning['courseid'] = $course['id'];
4145
                        if ($e instanceof moodle_exception) {
4146
                            $warning['warningcode'] = $e->errorcode;
4147
                        } else {
4148
                            $warning['warningcode'] = $e->getCode();
4149
                        }
4150
                        $warning['message'] = $e->getMessage();
4151
                        $warnings[] = $warning;
4152
                        $warnings[] = $warning;
4153
                    }
4154
                } else {
4155
                    $warning['courseid'] = $course['id'];
4156
                    $warning['warningcode'] = 'coursealreadyfavourited';
4157
                    $warning['message'] = 'Course already favourited';
4158
                    $warnings[] = $warning;
4159
                }
4160
            } else {
4161
                if ($favouriteexists) {
4162
                    try {
4163
                        $ufservice->delete_favourite('core_course', 'courses', $course['id'],
4164
                                \context_course::instance($course['id']));
4165
                    } catch (Exception $e) {
4166
                        $warning['courseid'] = $course['id'];
4167
                        if ($e instanceof moodle_exception) {
4168
                            $warning['warningcode'] = $e->errorcode;
4169
                        } else {
4170
                            $warning['warningcode'] = $e->getCode();
4171
                        }
4172
                        $warning['message'] = $e->getMessage();
4173
                        $warnings[] = $warning;
4174
                        $warnings[] = $warning;
4175
                    }
4176
                } else {
4177
                    $warning['courseid'] = $course['id'];
4178
                    $warning['warningcode'] = 'cannotdeletefavourite';
4179
                    $warning['message'] = 'Could not delete favourite status for course';
4180
                    $warnings[] = $warning;
4181
                }
4182
            }
4183
        }
4184
 
4185
        return [
4186
            'warnings' => $warnings
4187
        ];
4188
    }
4189
 
4190
    /**
4191
     * Returns description of method result value
4192
     *
4193
     * @return \core_external\external_description
4194
     */
4195
    public static function set_favourite_courses_returns() {
4196
        return new external_single_structure(
4197
            array(
4198
                'warnings' => new external_warnings()
4199
            )
4200
        );
4201
    }
4202
 
4203
    /**
4204
     * Returns description of method parameters
4205
     *
4206
     * @return external_function_parameters
4207
     * @since Moodle 3.6
4208
     */
4209
    public static function get_recent_courses_parameters() {
4210
        return new external_function_parameters(
4211
            array(
4212
                'userid' => new external_value(PARAM_INT, 'id of the user, default to current user', VALUE_DEFAULT, 0),
4213
                'limit' => new external_value(PARAM_INT, 'result set limit', VALUE_DEFAULT, 0),
4214
                'offset' => new external_value(PARAM_INT, 'Result set offset', VALUE_DEFAULT, 0),
4215
                'sort' => new external_value(PARAM_TEXT, 'Sort string', VALUE_DEFAULT, null)
4216
            )
4217
        );
4218
    }
4219
 
4220
    /**
4221
     * Get last accessed courses adding additional course information like images.
4222
     *
4223
     * @param int $userid User id from which the courses will be obtained
4224
     * @param int $limit Restrict result set to this amount
4225
     * @param int $offset Skip this number of records from the start of the result set
4226
     * @param string|null $sort SQL string for sorting
4227
     * @return array List of courses
4228
     * @throws  invalid_parameter_exception
4229
     */
4230
    public static function get_recent_courses(int $userid = 0, int $limit = 0, int $offset = 0, string $sort = null) {
4231
        global $USER, $PAGE;
4232
 
4233
        if (empty($userid)) {
4234
            $userid = $USER->id;
4235
        }
4236
 
4237
        $params = self::validate_parameters(self::get_recent_courses_parameters(),
4238
            array(
4239
                'userid' => $userid,
4240
                'limit' => $limit,
4241
                'offset' => $offset,
4242
                'sort' => $sort
4243
            )
4244
        );
4245
 
4246
        $userid = $params['userid'];
4247
        $limit = $params['limit'];
4248
        $offset = $params['offset'];
4249
        $sort = $params['sort'];
4250
 
4251
        $usercontext = context_user::instance($userid);
4252
 
4253
        self::validate_context($usercontext);
4254
 
4255
        if ($userid != $USER->id and !has_capability('moodle/user:viewdetails', $usercontext)) {
4256
            return array();
4257
        }
4258
 
4259
        $courses = course_get_recent_courses($userid, $limit, $offset, $sort);
4260
 
4261
        $renderer = $PAGE->get_renderer('core');
4262
 
4263
        $recentcourses = array_map(function($course) use ($renderer) {
4264
            context_helper::preload_from_record($course);
4265
            $context = context_course::instance($course->id);
4266
            $isfavourite = !empty($course->component);
4267
            $exporter = new course_summary_exporter($course, ['context' => $context, 'isfavourite' => $isfavourite]);
4268
            return $exporter->export($renderer);
4269
        }, $courses);
4270
 
4271
        return $recentcourses;
4272
    }
4273
 
4274
    /**
4275
     * Returns description of method result value
4276
     *
4277
     * @return \core_external\external_description
4278
     * @since Moodle 3.6
4279
     */
4280
    public static function get_recent_courses_returns() {
4281
        return new external_multiple_structure(course_summary_exporter::get_read_structure(), 'Courses');
4282
    }
4283
 
4284
    /**
4285
     * Returns description of method parameters
4286
     *
4287
     * @return external_function_parameters
4288
     */
4289
    public static function get_enrolled_users_by_cmid_parameters() {
4290
        return new external_function_parameters([
4291
            'cmid' => new external_value(PARAM_INT, 'id of the course module', VALUE_REQUIRED),
4292
            'groupid' => new external_value(PARAM_INT, 'id of the group', VALUE_DEFAULT, 0),
4293
            'onlyactive' => new external_value(PARAM_BOOL, 'whether to return only active users or all.',
4294
                VALUE_DEFAULT, false),
4295
        ]);
4296
    }
4297
 
4298
    /**
4299
     * Get all users in a course for a given cmid.
4300
     *
4301
     * @param int $cmid Course Module id from which the users will be obtained
4302
     * @param int $groupid Group id from which the users will be obtained
4303
     * @param bool $onlyactive Whether to return only the active enrolled users or all enrolled users in the course.
4304
     * @return array List of users
4305
     * @throws invalid_parameter_exception
4306
     */
4307
    public static function get_enrolled_users_by_cmid(int $cmid, int $groupid = 0, bool $onlyactive = false) {
4308
    global $PAGE;
4309
        $warnings = [];
4310
 
4311
        self::validate_parameters(self::get_enrolled_users_by_cmid_parameters(), [
4312
                'cmid' => $cmid,
4313
                'groupid' => $groupid,
4314
                'onlyactive' => $onlyactive,
4315
        ]);
4316
 
4317
        list($course, $cm) = get_course_and_cm_from_cmid($cmid);
4318
        $coursecontext = context_course::instance($course->id);
4319
        self::validate_context($coursecontext);
4320
 
4321
        $enrolledusers = get_enrolled_users($coursecontext, '', $groupid, 'u.*', null, 0, 0, $onlyactive);
4322
 
4323
        $users = array_map(function ($user) use ($PAGE) {
4324
            $user->fullname = fullname($user);
4325
            $userpicture = new user_picture($user);
4326
            $userpicture->size = 1;
4327
            $user->profileimage = $userpicture->get_url($PAGE)->out(false);
4328
            return $user;
4329
        }, $enrolledusers);
4330
        sort($users);
4331
 
4332
        return [
4333
            'users' => $users,
4334
            'warnings' => $warnings,
4335
        ];
4336
    }
4337
 
4338
    /**
4339
     * Returns description of method result value
4340
     *
4341
     * @return \core_external\external_description
4342
     */
4343
    public static function get_enrolled_users_by_cmid_returns() {
4344
        return new external_single_structure([
4345
            'users' => new external_multiple_structure(self::user_description()),
4346
            'warnings' => new external_warnings(),
4347
        ]);
4348
    }
4349
 
4350
    /**
4351
     * Create user return value description.
4352
     *
4353
     * @return \core_external\external_description
4354
     */
4355
    public static function user_description() {
4356
        $userfields = array(
4357
            'id'    => new external_value(core_user::get_property_type('id'), 'ID of the user'),
4358
            'profileimage' => new external_value(PARAM_URL, 'The location of the users larger image', VALUE_OPTIONAL),
4359
            'fullname' => new external_value(PARAM_TEXT, 'The full name of the user', VALUE_OPTIONAL),
4360
            'firstname'   => new external_value(
4361
                    core_user::get_property_type('firstname'),
4362
                        'The first name(s) of the user',
4363
                        VALUE_OPTIONAL),
4364
            'lastname'    => new external_value(
4365
                    core_user::get_property_type('lastname'),
4366
                        'The family name of the user',
4367
                        VALUE_OPTIONAL),
4368
        );
4369
        return new external_single_structure($userfields);
4370
    }
4371
 
4372
    /**
4373
     * Returns description of method parameters.
4374
     *
4375
     * @return external_function_parameters
4376
     */
4377
    public static function add_content_item_to_user_favourites_parameters() {
4378
        return new external_function_parameters([
4379
            'componentname' => new external_value(PARAM_TEXT,
4380
                'frankenstyle name of the component to which the content item belongs', VALUE_REQUIRED),
4381
            'contentitemid' => new external_value(PARAM_INT, 'id of the content item', VALUE_REQUIRED, '', NULL_NOT_ALLOWED)
4382
        ]);
4383
    }
4384
 
4385
    /**
4386
     * Add a content item to a user's favourites.
4387
     *
4388
     * @param string $componentname the name of the component from which this content item originates.
4389
     * @param int $contentitemid the id of the content item.
4390
     * @return stdClass the exporter content item.
4391
     */
4392
    public static function add_content_item_to_user_favourites(string $componentname, int $contentitemid) {
4393
        global $USER;
4394
 
4395
        [
4396
            'componentname' => $componentname,
4397
            'contentitemid' => $contentitemid,
4398
        ] = self::validate_parameters(self::add_content_item_to_user_favourites_parameters(),
4399
            [
4400
                'componentname' => $componentname,
4401
                'contentitemid' => $contentitemid,
4402
            ]
4403
        );
4404
 
4405
        self::validate_context(context_user::instance($USER->id));
4406
 
4407
        $contentitemservice = \core_course\local\factory\content_item_service_factory::get_content_item_service();
4408
 
4409
        return $contentitemservice->add_to_user_favourites($USER, $componentname, $contentitemid);
4410
    }
4411
 
4412
    /**
4413
     * Returns description of method result value.
4414
     *
4415
     * @return \core_external\external_description
4416
     */
4417
    public static function add_content_item_to_user_favourites_returns() {
4418
        return \core_course\local\exporters\course_content_item_exporter::get_read_structure();
4419
    }
4420
 
4421
    /**
4422
     * Returns description of method parameters.
4423
     *
4424
     * @return external_function_parameters
4425
     */
4426
    public static function remove_content_item_from_user_favourites_parameters() {
4427
        return new external_function_parameters([
4428
            'componentname' => new external_value(PARAM_TEXT,
4429
                'frankenstyle name of the component to which the content item belongs', VALUE_REQUIRED),
4430
            'contentitemid' => new external_value(PARAM_INT, 'id of the content item', VALUE_REQUIRED, '', NULL_NOT_ALLOWED),
4431
        ]);
4432
    }
4433
 
4434
    /**
4435
     * Remove a content item from a user's favourites.
4436
     *
4437
     * @param string $componentname the name of the component from which this content item originates.
4438
     * @param int $contentitemid the id of the content item.
4439
     * @return stdClass the exported content item.
4440
     */
4441
    public static function remove_content_item_from_user_favourites(string $componentname, int $contentitemid) {
4442
        global $USER;
4443
 
4444
        [
4445
            'componentname' => $componentname,
4446
            'contentitemid' => $contentitemid,
4447
        ] = self::validate_parameters(self::remove_content_item_from_user_favourites_parameters(),
4448
            [
4449
                'componentname' => $componentname,
4450
                'contentitemid' => $contentitemid,
4451
            ]
4452
        );
4453
 
4454
        self::validate_context(context_user::instance($USER->id));
4455
 
4456
        $contentitemservice = \core_course\local\factory\content_item_service_factory::get_content_item_service();
4457
 
4458
        return $contentitemservice->remove_from_user_favourites($USER, $componentname, $contentitemid);
4459
    }
4460
 
4461
    /**
4462
     * Returns description of method result value.
4463
     *
4464
     * @return \core_external\external_description
4465
     */
4466
    public static function remove_content_item_from_user_favourites_returns() {
4467
        return \core_course\local\exporters\course_content_item_exporter::get_read_structure();
4468
    }
4469
 
4470
    /**
4471
     * Returns description of method result value
4472
     *
4473
     * @return \core_external\external_description
4474
     */
4475
    public static function get_course_content_items_returns() {
4476
        return new external_single_structure([
4477
            'content_items' => new external_multiple_structure(
4478
                \core_course\local\exporters\course_content_item_exporter::get_read_structure()
4479
            ),
4480
        ]);
4481
    }
4482
 
4483
    /**
4484
     * Returns description of method parameters
4485
     *
4486
     * @return external_function_parameters
4487
     */
4488
    public static function get_course_content_items_parameters() {
4489
        return new external_function_parameters([
4490
            'courseid' => new external_value(PARAM_INT, 'ID of the course', VALUE_REQUIRED),
4491
        ]);
4492
    }
4493
 
4494
    /**
4495
     * Given a course ID fetch all accessible modules for that course
4496
     *
4497
     * @param int $courseid The course we want to fetch the modules for
4498
     * @return array Contains array of modules and their metadata
4499
     */
4500
    public static function get_course_content_items(int $courseid) {
4501
        global $USER;
4502
 
4503
        [
4504
            'courseid' => $courseid,
4505
        ] = self::validate_parameters(self::get_course_content_items_parameters(), [
4506
            'courseid' => $courseid,
4507
        ]);
4508
 
4509
        $coursecontext = context_course::instance($courseid);
4510
        self::validate_context($coursecontext);
4511
        $course = get_course($courseid);
4512
 
4513
        $contentitemservice = \core_course\local\factory\content_item_service_factory::get_content_item_service();
4514
 
4515
        $contentitems = $contentitemservice->get_content_items_for_user_in_course($USER, $course);
4516
        return ['content_items' => $contentitems];
4517
    }
4518
 
4519
    /**
4520
     * Returns description of method parameters.
4521
     *
4522
     * @return external_function_parameters
4523
     */
4524
    public static function toggle_activity_recommendation_parameters() {
4525
        return new external_function_parameters([
4526
            'area' => new external_value(PARAM_TEXT, 'The favourite area (itemtype)', VALUE_REQUIRED),
4527
            'id' => new external_value(PARAM_INT, 'id of the activity or whatever', VALUE_REQUIRED),
4528
        ]);
4529
    }
4530
 
4531
    /**
4532
     * Update the recommendation for an activity item.
4533
     *
4534
     * @param  string $area identifier for this activity.
4535
     * @param  int $id Associated id. This is needed in conjunction with the area to find the recommendation.
4536
     * @return array some warnings or something.
4537
     */
4538
    public static function toggle_activity_recommendation(string $area, int $id): array {
4539
        ['area' => $area, 'id' => $id] = self::validate_parameters(self::toggle_activity_recommendation_parameters(),
4540
                ['area' => $area, 'id' => $id]);
4541
 
4542
        $context = context_system::instance();
4543
        self::validate_context($context);
4544
 
4545
        require_capability('moodle/course:recommendactivity', $context);
4546
 
4547
        $manager = \core_course\local\factory\content_item_service_factory::get_content_item_service();
4548
 
4549
        $status = $manager->toggle_recommendation($area, $id);
4550
        return ['id' => $id, 'area' => $area, 'status' => $status];
4551
    }
4552
 
4553
    /**
4554
     * Returns warnings.
4555
     *
4556
     * @return \core_external\external_description
4557
     */
4558
    public static function toggle_activity_recommendation_returns() {
4559
        return new external_single_structure(
4560
            [
4561
                'id' => new external_value(PARAM_INT, 'id of the activity or whatever'),
4562
                'area' => new external_value(PARAM_TEXT, 'The favourite area (itemtype)'),
4563
                'status' => new external_value(PARAM_BOOL, 'If created or deleted'),
4564
            ]
4565
        );
4566
    }
4567
 
4568
    /**
4569
     * Returns description of method parameters
4570
     *
4571
     * @return external_function_parameters
4572
     */
4573
    public static function get_activity_chooser_footer_parameters() {
4574
        return new external_function_parameters([
4575
            'courseid' => new external_value(PARAM_INT, 'ID of the course', VALUE_REQUIRED),
4576
            'sectionid' => new external_value(PARAM_INT, 'ID of the section', VALUE_REQUIRED),
4577
        ]);
4578
    }
4579
 
4580
    /**
4581
     * Given a course ID we need to build up a footre for the chooser.
4582
     *
4583
     * @param int $courseid The course we want to fetch the modules for
4584
     * @param int $sectionid The section we want to fetch the modules for
4585
     * @return array
4586
     */
4587
    public static function get_activity_chooser_footer(int $courseid, int $sectionid) {
4588
        [
4589
            'courseid' => $courseid,
4590
            'sectionid' => $sectionid,
4591
        ] = self::validate_parameters(self::get_activity_chooser_footer_parameters(), [
4592
            'courseid' => $courseid,
4593
            'sectionid' => $sectionid,
4594
        ]);
4595
 
4596
        $coursecontext = context_course::instance($courseid);
4597
        self::validate_context($coursecontext);
4598
 
4599
        $activeplugin = get_config('core', 'activitychooseractivefooter');
4600
 
4601
        if ($activeplugin !== COURSE_CHOOSER_FOOTER_NONE) {
4602
            $footerdata = component_callback($activeplugin, 'custom_chooser_footer', [$courseid, $sectionid]);
4603
            return [
4604
                'footer' => true,
4605
                'customfooterjs' => $footerdata->get_footer_js_file(),
4606
                'customfootertemplate' => $footerdata->get_footer_template(),
4607
                'customcarouseltemplate' => $footerdata->get_carousel_template(),
4608
            ];
4609
        } else {
4610
            return [
4611
                'footer' => false,
4612
            ];
4613
        }
4614
    }
4615
 
4616
    /**
4617
     * Returns description of method result value
4618
     *
4619
     * @return \core_external\external_description
4620
     */
4621
    public static function get_activity_chooser_footer_returns() {
4622
        return new external_single_structure(
4623
            [
4624
                'footer' => new external_value(PARAM_BOOL, 'Is a footer being return by this request?', VALUE_REQUIRED),
4625
                'customfooterjs' => new external_value(PARAM_RAW, 'The path to the plugin JS file', VALUE_OPTIONAL),
4626
                'customfootertemplate' => new external_value(PARAM_RAW, 'The prerendered footer', VALUE_OPTIONAL),
4627
                'customcarouseltemplate' => new external_value(PARAM_RAW, 'Either "" or the prerendered carousel page',
4628
                    VALUE_OPTIONAL),
4629
            ]
4630
        );
4631
    }
4632
}