MDL-76792 course: Remove unnecessary include and checks
[moodle.git] / course / externallib.php
blob8344993c0852e80e7c007570b61521851c920261
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.
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/>.
18 /**
19 * External course API
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
27 defined('MOODLE_INTERNAL') || die;
29 use core_course\external\course_summary_exporter;
30 use core_external\external_api;
31 use core_external\external_files;
32 use core_external\external_format_value;
33 use core_external\external_function_parameters;
34 use core_external\external_multiple_structure;
35 use core_external\external_single_structure;
36 use core_external\external_value;
37 use core_external\external_warnings;
38 use core_external\util;
39 require_once(__DIR__ . "/lib.php");
41 /**
42 * Course external functions
44 * @package core_course
45 * @category external
46 * @copyright 2011 Jerome Mouneyrac
47 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
48 * @since Moodle 2.2
50 class core_course_external extends external_api {
52 /**
53 * Returns description of method parameters
55 * @return external_function_parameters
56 * @since Moodle 2.9 Options available
57 * @since Moodle 2.2
59 public static function get_course_contents_parameters() {
60 return new external_function_parameters(
61 array('courseid' => new external_value(PARAM_INT, 'course id'),
62 'options' => new external_multiple_structure (
63 new external_single_structure(
64 array(
65 'name' => new external_value(PARAM_ALPHANUM,
66 'The expected keys (value format) are:
67 excludemodules (bool) Do not return modules, return only the sections structure
68 excludecontents (bool) Do not return module contents (i.e: files inside a resource)
69 includestealthmodules (bool) Return stealth modules for students in a special
70 section (with id -1)
71 sectionid (int) Return only this section
72 sectionnumber (int) Return only this section with number (order)
73 cmid (int) Return only this module information (among the whole sections structure)
74 modname (string) Return only modules with this name "label, forum, etc..."
75 modid (int) Return only the module with this id (to be used with modname'),
76 'value' => new external_value(PARAM_RAW, 'the value of the option,
77 this param is personaly validated in the external function.')
79 ), 'Options, used since Moodle 2.9', VALUE_DEFAULT, array())
84 /**
85 * Get course contents
87 * @param int $courseid course id
88 * @param array $options Options for filtering the results, used since Moodle 2.9
89 * @return array
90 * @since Moodle 2.9 Options available
91 * @since Moodle 2.2
93 public static function get_course_contents($courseid, $options = array()) {
94 global $CFG, $DB, $USER, $PAGE;
95 require_once($CFG->dirroot . "/course/lib.php");
96 require_once($CFG->libdir . '/completionlib.php');
98 //validate parameter
99 $params = self::validate_parameters(self::get_course_contents_parameters(),
100 array('courseid' => $courseid, 'options' => $options));
102 $filters = array();
103 if (!empty($params['options'])) {
105 foreach ($params['options'] as $option) {
106 $name = trim($option['name']);
107 // Avoid duplicated options.
108 if (!isset($filters[$name])) {
109 switch ($name) {
110 case 'excludemodules':
111 case 'excludecontents':
112 case 'includestealthmodules':
113 $value = clean_param($option['value'], PARAM_BOOL);
114 $filters[$name] = $value;
115 break;
116 case 'sectionid':
117 case 'sectionnumber':
118 case 'cmid':
119 case 'modid':
120 $value = clean_param($option['value'], PARAM_INT);
121 if (is_numeric($value)) {
122 $filters[$name] = $value;
123 } else {
124 throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
126 break;
127 case 'modname':
128 $value = clean_param($option['value'], PARAM_PLUGIN);
129 if ($value) {
130 $filters[$name] = $value;
131 } else {
132 throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
134 break;
135 default:
136 throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
142 //retrieve the course
143 $course = $DB->get_record('course', array('id' => $params['courseid']), '*', MUST_EXIST);
145 // now security checks
146 $context = context_course::instance($course->id, IGNORE_MISSING);
147 try {
148 self::validate_context($context);
149 } catch (Exception $e) {
150 $exceptionparam = new stdClass();
151 $exceptionparam->message = $e->getMessage();
152 $exceptionparam->courseid = $course->id;
153 throw new moodle_exception('errorcoursecontextnotvalid', 'webservice', '', $exceptionparam);
156 $canupdatecourse = has_capability('moodle/course:update', $context);
158 //create return value
159 $coursecontents = array();
161 if ($canupdatecourse or $course->visible
162 or has_capability('moodle/course:viewhiddencourses', $context)) {
164 //retrieve sections
165 $modinfo = get_fast_modinfo($course);
166 $sections = $modinfo->get_section_info_all();
167 $courseformat = course_get_format($course);
168 $coursenumsections = $courseformat->get_last_section_number();
169 $stealthmodules = array(); // Array to keep all the modules available but not visible in a course section/topic.
171 $completioninfo = new completion_info($course);
173 //for each sections (first displayed to last displayed)
174 $modinfosections = $modinfo->get_sections();
175 foreach ($sections as $key => $section) {
177 // This becomes true when we are filtering and we found the value to filter with.
178 $sectionfound = false;
180 // Filter by section id.
181 if (!empty($filters['sectionid'])) {
182 if ($section->id != $filters['sectionid']) {
183 continue;
184 } else {
185 $sectionfound = true;
189 // Filter by section number. Note that 0 is a valid section number.
190 if (isset($filters['sectionnumber'])) {
191 if ($key != $filters['sectionnumber']) {
192 continue;
193 } else {
194 $sectionfound = true;
198 // reset $sectioncontents
199 $sectionvalues = array();
200 $sectionvalues['id'] = $section->id;
201 $sectionvalues['name'] = get_section_name($course, $section);
202 $sectionvalues['visible'] = $section->visible;
204 $options = (object) array('noclean' => true);
205 list($sectionvalues['summary'], $sectionvalues['summaryformat']) =
206 \core_external\util::format_text($section->summary, $section->summaryformat,
207 $context, 'course', 'section', $section->id, $options);
208 $sectionvalues['section'] = $section->section;
209 $sectionvalues['hiddenbynumsections'] = $section->section > $coursenumsections ? 1 : 0;
210 $sectionvalues['uservisible'] = $section->uservisible;
211 if (!empty($section->availableinfo)) {
212 $sectionvalues['availabilityinfo'] = \core_availability\info::format_info($section->availableinfo, $course);
215 $sectioncontents = array();
217 // For each module of the section.
218 if (empty($filters['excludemodules']) and !empty($modinfosections[$section->section])) {
219 foreach ($modinfosections[$section->section] as $cmid) {
220 $cm = $modinfo->cms[$cmid];
221 $cminfo = cm_info::create($cm);
222 $activitydates = \core\activity_dates::get_dates_for_module($cminfo, $USER->id);
224 // Stop here if the module is not visible to the user on the course main page:
225 // The user can't access the module and the user can't view the module on the course page.
226 if (!$cm->uservisible && !$cm->is_visible_on_course_page()) {
227 continue;
230 // This becomes true when we are filtering and we found the value to filter with.
231 $modfound = false;
233 // Filter by cmid.
234 if (!empty($filters['cmid'])) {
235 if ($cmid != $filters['cmid']) {
236 continue;
237 } else {
238 $modfound = true;
242 // Filter by module name and id.
243 if (!empty($filters['modname'])) {
244 if ($cm->modname != $filters['modname']) {
245 continue;
246 } else if (!empty($filters['modid'])) {
247 if ($cm->instance != $filters['modid']) {
248 continue;
249 } else {
250 // Note that if we are only filtering by modname we don't break the loop.
251 $modfound = true;
256 $module = array();
258 $modcontext = context_module::instance($cm->id);
260 //common info (for people being able to see the module or availability dates)
261 $module['id'] = $cm->id;
262 $module['name'] = \core_external\util::format_string($cm->name, $modcontext);
263 $module['instance'] = $cm->instance;
264 $module['contextid'] = $modcontext->id;
265 $module['modname'] = (string) $cm->modname;
266 $module['modplural'] = (string) $cm->modplural;
267 $module['modicon'] = $cm->get_icon_url()->out(false);
268 $module['indent'] = $cm->indent;
269 $module['onclick'] = $cm->onclick;
270 $module['afterlink'] = $cm->afterlink;
271 $module['customdata'] = json_encode($cm->customdata);
272 $module['completion'] = $cm->completion;
273 $module['downloadcontent'] = $cm->downloadcontent;
274 $module['noviewlink'] = plugin_supports('mod', $cm->modname, FEATURE_NO_VIEW_LINK, false);
275 $module['dates'] = $activitydates;
277 // Check module completion.
278 $completion = $completioninfo->is_enabled($cm);
279 if ($completion != COMPLETION_DISABLED) {
280 $exporter = new \core_completion\external\completion_info_exporter($course, $cm, $USER->id);
281 $renderer = $PAGE->get_renderer('core');
282 $modulecompletiondata = (array)$exporter->export($renderer);
283 $module['completiondata'] = $modulecompletiondata;
286 if (!empty($cm->showdescription) or $module['noviewlink']) {
287 // We want to use the external format. However from reading get_formatted_content(), $cm->content format is always FORMAT_HTML.
288 $options = array('noclean' => true);
289 list($module['description'], $descriptionformat) = \core_external\util::format_text($cm->content,
290 FORMAT_HTML, $modcontext, $cm->modname, 'intro', $cm->id, $options);
293 //url of the module
294 $url = $cm->url;
295 if ($url) { //labels don't have url
296 $module['url'] = $url->out(false);
299 $canviewhidden = has_capability('moodle/course:viewhiddenactivities',
300 context_module::instance($cm->id));
301 //user that can view hidden module should know about the visibility
302 $module['visible'] = $cm->visible;
303 $module['visibleoncoursepage'] = $cm->visibleoncoursepage;
304 $module['uservisible'] = $cm->uservisible;
305 if (!empty($cm->availableinfo)) {
306 $module['availabilityinfo'] = \core_availability\info::format_info($cm->availableinfo, $course);
309 // Availability date (also send to user who can see hidden module).
310 if ($CFG->enableavailability && ($canviewhidden || $canupdatecourse)) {
311 $module['availability'] = $cm->availability;
314 // Return contents only if the user can access to the module.
315 if ($cm->uservisible) {
316 $baseurl = 'webservice/pluginfile.php';
318 // Call $modulename_export_contents (each module callback take care about checking the capabilities).
319 require_once($CFG->dirroot . '/mod/' . $cm->modname . '/lib.php');
320 $getcontentfunction = $cm->modname.'_export_contents';
321 if (function_exists($getcontentfunction)) {
322 $contents = $getcontentfunction($cm, $baseurl);
323 $module['contentsinfo'] = array(
324 'filescount' => count($contents),
325 'filessize' => 0,
326 'lastmodified' => 0,
327 'mimetypes' => array(),
329 foreach ($contents as $content) {
330 // Check repository file (only main file).
331 if (!isset($module['contentsinfo']['repositorytype'])) {
332 $module['contentsinfo']['repositorytype'] =
333 isset($content['repositorytype']) ? $content['repositorytype'] : '';
335 if (isset($content['filesize'])) {
336 $module['contentsinfo']['filessize'] += $content['filesize'];
338 if (isset($content['timemodified']) &&
339 ($content['timemodified'] > $module['contentsinfo']['lastmodified'])) {
341 $module['contentsinfo']['lastmodified'] = $content['timemodified'];
343 if (isset($content['mimetype'])) {
344 $module['contentsinfo']['mimetypes'][$content['mimetype']] = $content['mimetype'];
348 if (empty($filters['excludecontents']) and !empty($contents)) {
349 $module['contents'] = $contents;
350 } else {
351 $module['contents'] = array();
356 // Assign result to $sectioncontents, there is an exception,
357 // stealth activities in non-visible sections for students go to a special section.
358 if (!empty($filters['includestealthmodules']) && !$section->uservisible && $cm->is_stealth()) {
359 $stealthmodules[] = $module;
360 } else {
361 $sectioncontents[] = $module;
364 // If we just did a filtering, break the loop.
365 if ($modfound) {
366 break;
371 $sectionvalues['modules'] = $sectioncontents;
373 // assign result to $coursecontents
374 $coursecontents[$key] = $sectionvalues;
376 // Break the loop if we are filtering.
377 if ($sectionfound) {
378 break;
382 // Now that we have iterated over all the sections and activities, check the visibility.
383 // We didn't this before to be able to retrieve stealth activities.
384 foreach ($coursecontents as $sectionnumber => $sectioncontents) {
385 $section = $sections[$sectionnumber];
387 if (!$courseformat->is_section_visible($section)) {
388 unset($coursecontents[$sectionnumber]);
389 continue;
392 // Remove section and modules information if the section is not visible for the user.
393 if (!$section->uservisible) {
394 $coursecontents[$sectionnumber]['modules'] = array();
395 // Remove summary information if the section is completely hidden only,
396 // even if the section is not user visible, the summary is always displayed among the availability information.
397 if (!$section->visible) {
398 $coursecontents[$sectionnumber]['summary'] = '';
403 // Include stealth modules in special section (without any info).
404 if (!empty($stealthmodules)) {
405 $coursecontents[] = array(
406 'id' => -1,
407 'name' => '',
408 'summary' => '',
409 'summaryformat' => FORMAT_MOODLE,
410 'modules' => $stealthmodules
415 return $coursecontents;
419 * Returns description of method result value
421 * @return \core_external\external_description
422 * @since Moodle 2.2
424 public static function get_course_contents_returns() {
425 $completiondefinition = \core_completion\external\completion_info_exporter::get_read_structure(VALUE_DEFAULT, []);
427 return new external_multiple_structure(
428 new external_single_structure(
429 array(
430 'id' => new external_value(PARAM_INT, 'Section ID'),
431 'name' => new external_value(PARAM_RAW, 'Section name'),
432 'visible' => new external_value(PARAM_INT, 'is the section visible', VALUE_OPTIONAL),
433 'summary' => new external_value(PARAM_RAW, 'Section description'),
434 'summaryformat' => new external_format_value('summary'),
435 'section' => new external_value(PARAM_INT, 'Section number inside the course', VALUE_OPTIONAL),
436 'hiddenbynumsections' => new external_value(PARAM_INT, 'Whether is a section hidden in the course format',
437 VALUE_OPTIONAL),
438 'uservisible' => new external_value(PARAM_BOOL, 'Is the section visible for the user?', VALUE_OPTIONAL),
439 'availabilityinfo' => new external_value(PARAM_RAW, 'Availability information.', VALUE_OPTIONAL),
440 'modules' => new external_multiple_structure(
441 new external_single_structure(
442 array(
443 'id' => new external_value(PARAM_INT, 'activity id'),
444 'url' => new external_value(PARAM_URL, 'activity url', VALUE_OPTIONAL),
445 'name' => new external_value(PARAM_RAW, 'activity module name'),
446 'instance' => new external_value(PARAM_INT, 'instance id', VALUE_OPTIONAL),
447 'contextid' => new external_value(PARAM_INT, 'Activity context id.', VALUE_OPTIONAL),
448 'description' => new external_value(PARAM_RAW, 'activity description', VALUE_OPTIONAL),
449 'visible' => new external_value(PARAM_INT, 'is the module visible', VALUE_OPTIONAL),
450 'uservisible' => new external_value(PARAM_BOOL, 'Is the module visible for the user?',
451 VALUE_OPTIONAL),
452 'availabilityinfo' => new external_value(PARAM_RAW, 'Availability information.',
453 VALUE_OPTIONAL),
454 'visibleoncoursepage' => new external_value(PARAM_INT, 'is the module visible on course page',
455 VALUE_OPTIONAL),
456 'modicon' => new external_value(PARAM_URL, 'activity icon url'),
457 'modname' => new external_value(PARAM_PLUGIN, 'activity module type'),
458 'modplural' => new external_value(PARAM_TEXT, 'activity module plural name'),
459 'availability' => new external_value(PARAM_RAW, 'module availability settings', VALUE_OPTIONAL),
460 'indent' => new external_value(PARAM_INT, 'number of identation in the site'),
461 'onclick' => new external_value(PARAM_RAW, 'Onclick action.', VALUE_OPTIONAL),
462 'afterlink' => new external_value(PARAM_RAW, 'After link info to be displayed.',
463 VALUE_OPTIONAL),
464 'customdata' => new external_value(PARAM_RAW, 'Custom data (JSON encoded).', VALUE_OPTIONAL),
465 'noviewlink' => new external_value(PARAM_BOOL, 'Whether the module has no view page',
466 VALUE_OPTIONAL),
467 'completion' => new external_value(PARAM_INT, 'Type of completion tracking:
468 0 means none, 1 manual, 2 automatic.', VALUE_OPTIONAL),
469 'completiondata' => $completiondefinition,
470 'downloadcontent' => new external_value(PARAM_INT, 'The download content value', VALUE_OPTIONAL),
471 'dates' => new external_multiple_structure(
472 new external_single_structure(
473 array(
474 'label' => new external_value(PARAM_TEXT, 'date label'),
475 'timestamp' => new external_value(PARAM_INT, 'date timestamp'),
476 'relativeto' => new external_value(PARAM_INT, 'relative date timestamp',
477 VALUE_OPTIONAL),
478 'dataid' => new external_value(PARAM_NOTAGS, 'cm data id', VALUE_OPTIONAL),
481 'Course dates',
482 VALUE_DEFAULT,
485 'contents' => new external_multiple_structure(
486 new external_single_structure(
487 array(
488 // content info
489 'type'=> new external_value(PARAM_TEXT, 'a file or a folder or external link'),
490 'filename'=> new external_value(PARAM_FILE, 'filename'),
491 'filepath'=> new external_value(PARAM_PATH, 'filepath'),
492 'filesize'=> new external_value(PARAM_INT, 'filesize'),
493 'fileurl' => new external_value(PARAM_URL, 'downloadable file url', VALUE_OPTIONAL),
494 'content' => new external_value(PARAM_RAW, 'Raw content, will be used when type is content', VALUE_OPTIONAL),
495 'timecreated' => new external_value(PARAM_INT, 'Time created'),
496 'timemodified' => new external_value(PARAM_INT, 'Time modified'),
497 'sortorder' => new external_value(PARAM_INT, 'Content sort order'),
498 'mimetype' => new external_value(PARAM_RAW, 'File mime type.', VALUE_OPTIONAL),
499 'isexternalfile' => new external_value(PARAM_BOOL, 'Whether is an external file.',
500 VALUE_OPTIONAL),
501 'repositorytype' => new external_value(PARAM_PLUGIN, 'The repository type for external files.',
502 VALUE_OPTIONAL),
504 // copyright related info
505 'userid' => new external_value(PARAM_INT, 'User who added this content to moodle'),
506 'author' => new external_value(PARAM_TEXT, 'Content owner'),
507 'license' => new external_value(PARAM_TEXT, 'Content license'),
508 'tags' => new external_multiple_structure(
509 \core_tag\external\tag_item_exporter::get_read_structure(), 'Tags',
510 VALUE_OPTIONAL
513 ), 'Course contents', VALUE_DEFAULT, array()
515 'contentsinfo' => new external_single_structure(
516 array(
517 'filescount' => new external_value(PARAM_INT, 'Total number of files.'),
518 'filessize' => new external_value(PARAM_INT, 'Total files size.'),
519 'lastmodified' => new external_value(PARAM_INT, 'Last time files were modified.'),
520 'mimetypes' => new external_multiple_structure(
521 new external_value(PARAM_RAW, 'File mime type.'),
522 'Files mime types.'
524 'repositorytype' => new external_value(PARAM_PLUGIN, 'The repository type for
525 the main file.', VALUE_OPTIONAL),
526 ), 'Contents summary information.', VALUE_OPTIONAL
529 ), 'list of module'
537 * Returns description of method parameters
539 * @return external_function_parameters
540 * @since Moodle 2.3
542 public static function get_courses_parameters() {
543 return new external_function_parameters(
544 array('options' => new external_single_structure(
545 array('ids' => new external_multiple_structure(
546 new external_value(PARAM_INT, 'Course id')
547 , 'List of course id. If empty return all courses
548 except front page course.',
549 VALUE_OPTIONAL)
550 ), 'options - operator OR is used', VALUE_DEFAULT, array())
556 * Get courses
558 * @param array $options It contains an array (list of ids)
559 * @return array
560 * @since Moodle 2.2
562 public static function get_courses($options = array()) {
563 global $CFG, $DB;
564 require_once($CFG->dirroot . "/course/lib.php");
566 //validate parameter
567 $params = self::validate_parameters(self::get_courses_parameters(),
568 array('options' => $options));
570 //retrieve courses
571 if (!array_key_exists('ids', $params['options'])
572 or empty($params['options']['ids'])) {
573 $courses = $DB->get_records('course');
574 } else {
575 $courses = $DB->get_records_list('course', 'id', $params['options']['ids']);
578 //create return value
579 $coursesinfo = array();
580 foreach ($courses as $course) {
582 // now security checks
583 $context = context_course::instance($course->id, IGNORE_MISSING);
584 $courseformatoptions = course_get_format($course)->get_format_options();
585 try {
586 self::validate_context($context);
587 } catch (Exception $e) {
588 $exceptionparam = new stdClass();
589 $exceptionparam->message = $e->getMessage();
590 $exceptionparam->courseid = $course->id;
591 throw new moodle_exception('errorcoursecontextnotvalid', 'webservice', '', $exceptionparam);
593 if ($course->id != SITEID) {
594 require_capability('moodle/course:view', $context);
597 $courseinfo = array();
598 $courseinfo['id'] = $course->id;
599 $courseinfo['fullname'] = \core_external\util::format_string($course->fullname, $context);
600 $courseinfo['shortname'] = \core_external\util::format_string($course->shortname, $context);
601 $courseinfo['displayname'] = \core_external\util::format_string(get_course_display_name_for_list($course), $context);
602 $courseinfo['categoryid'] = $course->category;
603 list($courseinfo['summary'], $courseinfo['summaryformat']) =
604 \core_external\util::format_text($course->summary, $course->summaryformat, $context, 'course', 'summary', 0);
605 $courseinfo['format'] = $course->format;
606 $courseinfo['startdate'] = $course->startdate;
607 $courseinfo['enddate'] = $course->enddate;
608 $courseinfo['showactivitydates'] = $course->showactivitydates;
609 $courseinfo['showcompletionconditions'] = $course->showcompletionconditions;
610 if (array_key_exists('numsections', $courseformatoptions)) {
611 // For backward-compartibility
612 $courseinfo['numsections'] = $courseformatoptions['numsections'];
614 $courseinfo['pdfexportfont'] = $course->pdfexportfont;
616 $handler = core_course\customfield\course_handler::create();
617 if ($customfields = $handler->export_instance_data($course->id)) {
618 $courseinfo['customfields'] = [];
619 foreach ($customfields as $data) {
620 $courseinfo['customfields'][] = [
621 'type' => $data->get_type(),
622 'value' => $data->get_value(),
623 'valueraw' => $data->get_data_controller()->get_value(),
624 'name' => $data->get_name(),
625 'shortname' => $data->get_shortname()
630 //some field should be returned only if the user has update permission
631 $courseadmin = has_capability('moodle/course:update', $context);
632 if ($courseadmin) {
633 $courseinfo['categorysortorder'] = $course->sortorder;
634 $courseinfo['idnumber'] = $course->idnumber;
635 $courseinfo['showgrades'] = $course->showgrades;
636 $courseinfo['showreports'] = $course->showreports;
637 $courseinfo['newsitems'] = $course->newsitems;
638 $courseinfo['visible'] = $course->visible;
639 $courseinfo['maxbytes'] = $course->maxbytes;
640 if (array_key_exists('hiddensections', $courseformatoptions)) {
641 // For backward-compartibility
642 $courseinfo['hiddensections'] = $courseformatoptions['hiddensections'];
644 // Return numsections for backward-compatibility with clients who expect it.
645 $courseinfo['numsections'] = course_get_format($course)->get_last_section_number();
646 $courseinfo['groupmode'] = $course->groupmode;
647 $courseinfo['groupmodeforce'] = $course->groupmodeforce;
648 $courseinfo['defaultgroupingid'] = $course->defaultgroupingid;
649 $courseinfo['lang'] = clean_param($course->lang, PARAM_LANG);
650 $courseinfo['timecreated'] = $course->timecreated;
651 $courseinfo['timemodified'] = $course->timemodified;
652 $courseinfo['forcetheme'] = clean_param($course->theme, PARAM_THEME);
653 $courseinfo['enablecompletion'] = $course->enablecompletion;
654 $courseinfo['completionnotify'] = $course->completionnotify;
655 $courseinfo['courseformatoptions'] = array();
656 foreach ($courseformatoptions as $key => $value) {
657 $courseinfo['courseformatoptions'][] = array(
658 'name' => $key,
659 'value' => $value
664 if ($courseadmin or $course->visible
665 or has_capability('moodle/course:viewhiddencourses', $context)) {
666 $coursesinfo[] = $courseinfo;
670 return $coursesinfo;
674 * Returns description of method result value
676 * @return \core_external\external_description
677 * @since Moodle 2.2
679 public static function get_courses_returns() {
680 return new external_multiple_structure(
681 new external_single_structure(
682 array(
683 'id' => new external_value(PARAM_INT, 'course id'),
684 'shortname' => new external_value(PARAM_RAW, 'course short name'),
685 'categoryid' => new external_value(PARAM_INT, 'category id'),
686 'categorysortorder' => new external_value(PARAM_INT,
687 'sort order into the category', VALUE_OPTIONAL),
688 'fullname' => new external_value(PARAM_RAW, 'full name'),
689 'displayname' => new external_value(PARAM_RAW, 'course display name'),
690 'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
691 'summary' => new external_value(PARAM_RAW, 'summary'),
692 'summaryformat' => new external_format_value('summary'),
693 'format' => new external_value(PARAM_PLUGIN,
694 'course format: weeks, topics, social, site,..'),
695 'showgrades' => new external_value(PARAM_INT,
696 '1 if grades are shown, otherwise 0', VALUE_OPTIONAL),
697 'newsitems' => new external_value(PARAM_INT,
698 'number of recent items appearing on the course page', VALUE_OPTIONAL),
699 'startdate' => new external_value(PARAM_INT,
700 'timestamp when the course start'),
701 'enddate' => new external_value(PARAM_INT,
702 'timestamp when the course end'),
703 'numsections' => new external_value(PARAM_INT,
704 '(deprecated, use courseformatoptions) number of weeks/topics',
705 VALUE_OPTIONAL),
706 'maxbytes' => new external_value(PARAM_INT,
707 'largest size of file that can be uploaded into the course',
708 VALUE_OPTIONAL),
709 'showreports' => new external_value(PARAM_INT,
710 'are activity report shown (yes = 1, no =0)', VALUE_OPTIONAL),
711 'visible' => new external_value(PARAM_INT,
712 '1: available to student, 0:not available', VALUE_OPTIONAL),
713 'hiddensections' => new external_value(PARAM_INT,
714 '(deprecated, use courseformatoptions) How the hidden sections in the course are displayed to students',
715 VALUE_OPTIONAL),
716 'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible',
717 VALUE_OPTIONAL),
718 'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no',
719 VALUE_OPTIONAL),
720 'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id',
721 VALUE_OPTIONAL),
722 'timecreated' => new external_value(PARAM_INT,
723 'timestamp when the course have been created', VALUE_OPTIONAL),
724 'timemodified' => new external_value(PARAM_INT,
725 'timestamp when the course have been modified', VALUE_OPTIONAL),
726 'enablecompletion' => new external_value(PARAM_INT,
727 'Enabled, control via completion and activity settings. Disbaled,
728 not shown in activity settings.',
729 VALUE_OPTIONAL),
730 'completionnotify' => new external_value(PARAM_INT,
731 '1: yes 0: no', VALUE_OPTIONAL),
732 'lang' => new external_value(PARAM_SAFEDIR,
733 'forced course language', VALUE_OPTIONAL),
734 'forcetheme' => new external_value(PARAM_PLUGIN,
735 'name of the force theme', VALUE_OPTIONAL),
736 'courseformatoptions' => new external_multiple_structure(
737 new external_single_structure(
738 array('name' => new external_value(PARAM_ALPHANUMEXT, 'course format option name'),
739 'value' => new external_value(PARAM_RAW, 'course format option value')
740 )), 'additional options for particular course format', VALUE_OPTIONAL
742 'showactivitydates' => new external_value(PARAM_BOOL, 'Whether the activity dates are shown or not'),
743 'showcompletionconditions' => new external_value(PARAM_BOOL,
744 'Whether the activity completion conditions are shown or not'),
745 'customfields' => new external_multiple_structure(
746 new external_single_structure(
747 ['name' => new external_value(PARAM_RAW, 'The name of the custom field'),
748 'shortname' => new external_value(PARAM_ALPHANUMEXT, 'The shortname of the custom field'),
749 'type' => new external_value(PARAM_COMPONENT,
750 'The type of the custom field - text, checkbox...'),
751 'valueraw' => new external_value(PARAM_RAW, 'The raw value of the custom field'),
752 'value' => new external_value(PARAM_RAW, 'The value of the custom field')]
753 ), 'Custom fields and associated values', VALUE_OPTIONAL),
754 ), 'course'
760 * Returns description of method parameters
762 * @return external_function_parameters
763 * @since Moodle 2.2
765 public static function create_courses_parameters() {
766 $courseconfig = get_config('moodlecourse'); //needed for many default values
767 return new external_function_parameters(
768 array(
769 'courses' => new external_multiple_structure(
770 new external_single_structure(
771 array(
772 'fullname' => new external_value(PARAM_TEXT, 'full name'),
773 'shortname' => new external_value(PARAM_TEXT, 'course short name'),
774 'categoryid' => new external_value(PARAM_INT, 'category id'),
775 'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
776 'summary' => new external_value(PARAM_RAW, 'summary', VALUE_OPTIONAL),
777 'summaryformat' => new external_format_value('summary', VALUE_DEFAULT),
778 'format' => new external_value(PARAM_PLUGIN,
779 'course format: weeks, topics, social, site,..',
780 VALUE_DEFAULT, $courseconfig->format),
781 'showgrades' => new external_value(PARAM_INT,
782 '1 if grades are shown, otherwise 0', VALUE_DEFAULT,
783 $courseconfig->showgrades),
784 'newsitems' => new external_value(PARAM_INT,
785 'number of recent items appearing on the course page',
786 VALUE_DEFAULT, $courseconfig->newsitems),
787 'startdate' => new external_value(PARAM_INT,
788 'timestamp when the course start', VALUE_OPTIONAL),
789 'enddate' => new external_value(PARAM_INT,
790 'timestamp when the course end', VALUE_OPTIONAL),
791 'numsections' => new external_value(PARAM_INT,
792 '(deprecated, use courseformatoptions) number of weeks/topics',
793 VALUE_OPTIONAL),
794 'maxbytes' => new external_value(PARAM_INT,
795 'largest size of file that can be uploaded into the course',
796 VALUE_DEFAULT, $courseconfig->maxbytes),
797 'showreports' => new external_value(PARAM_INT,
798 'are activity report shown (yes = 1, no =0)', VALUE_DEFAULT,
799 $courseconfig->showreports),
800 'visible' => new external_value(PARAM_INT,
801 '1: available to student, 0:not available', VALUE_OPTIONAL),
802 'hiddensections' => new external_value(PARAM_INT,
803 '(deprecated, use courseformatoptions) How the hidden sections in the course are displayed to students',
804 VALUE_OPTIONAL),
805 'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible',
806 VALUE_DEFAULT, $courseconfig->groupmode),
807 'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no',
808 VALUE_DEFAULT, $courseconfig->groupmodeforce),
809 'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id',
810 VALUE_DEFAULT, 0),
811 'enablecompletion' => new external_value(PARAM_INT,
812 'Enabled, control via completion and activity settings. Disabled,
813 not shown in activity settings.',
814 VALUE_OPTIONAL),
815 'completionnotify' => new external_value(PARAM_INT,
816 '1: yes 0: no', VALUE_OPTIONAL),
817 'lang' => new external_value(PARAM_SAFEDIR,
818 'forced course language', VALUE_OPTIONAL),
819 'forcetheme' => new external_value(PARAM_PLUGIN,
820 'name of the force theme', VALUE_OPTIONAL),
821 'courseformatoptions' => new external_multiple_structure(
822 new external_single_structure(
823 array('name' => new external_value(PARAM_ALPHANUMEXT, 'course format option name'),
824 'value' => new external_value(PARAM_RAW, 'course format option value')
826 'additional options for particular course format', VALUE_OPTIONAL),
827 'customfields' => new external_multiple_structure(
828 new external_single_structure(
829 array(
830 'shortname' => new external_value(PARAM_ALPHANUMEXT, 'The shortname of the custom field'),
831 'value' => new external_value(PARAM_RAW, 'The value of the custom field'),
832 )), 'custom fields for the course', VALUE_OPTIONAL
834 )), 'courses to create'
841 * Create courses
843 * @param array $courses
844 * @return array courses (id and shortname only)
845 * @since Moodle 2.2
847 public static function create_courses($courses) {
848 global $CFG, $DB;
849 require_once($CFG->dirroot . "/course/lib.php");
850 require_once($CFG->libdir . '/completionlib.php');
852 $params = self::validate_parameters(self::create_courses_parameters(),
853 array('courses' => $courses));
855 $availablethemes = core_component::get_plugin_list('theme');
856 $availablelangs = get_string_manager()->get_list_of_translations();
858 $transaction = $DB->start_delegated_transaction();
860 foreach ($params['courses'] as $course) {
862 // Ensure the current user is allowed to run this function
863 $context = context_coursecat::instance($course['categoryid'], IGNORE_MISSING);
864 try {
865 self::validate_context($context);
866 } catch (Exception $e) {
867 $exceptionparam = new stdClass();
868 $exceptionparam->message = $e->getMessage();
869 $exceptionparam->catid = $course['categoryid'];
870 throw new moodle_exception('errorcatcontextnotvalid', 'webservice', '', $exceptionparam);
872 require_capability('moodle/course:create', $context);
874 // Fullname and short name are required to be non-empty.
875 if (trim($course['fullname']) === '') {
876 throw new moodle_exception('errorinvalidparam', 'webservice', '', 'fullname');
877 } else if (trim($course['shortname']) === '') {
878 throw new moodle_exception('errorinvalidparam', 'webservice', '', 'shortname');
881 // Make sure lang is valid
882 if (array_key_exists('lang', $course)) {
883 if (empty($availablelangs[$course['lang']])) {
884 throw new moodle_exception('errorinvalidparam', 'webservice', '', 'lang');
886 if (!has_capability('moodle/course:setforcedlanguage', $context)) {
887 unset($course['lang']);
891 // Make sure theme is valid
892 if (array_key_exists('forcetheme', $course)) {
893 if (!empty($CFG->allowcoursethemes)) {
894 if (empty($availablethemes[$course['forcetheme']])) {
895 throw new moodle_exception('errorinvalidparam', 'webservice', '', 'forcetheme');
896 } else {
897 $course['theme'] = $course['forcetheme'];
902 //force visibility if ws user doesn't have the permission to set it
903 $category = $DB->get_record('course_categories', array('id' => $course['categoryid']));
904 if (!has_capability('moodle/course:visibility', $context)) {
905 $course['visible'] = $category->visible;
908 //set default value for completion
909 $courseconfig = get_config('moodlecourse');
910 if (completion_info::is_enabled_for_site()) {
911 if (!array_key_exists('enablecompletion', $course)) {
912 $course['enablecompletion'] = $courseconfig->enablecompletion;
914 } else {
915 $course['enablecompletion'] = 0;
918 $course['category'] = $course['categoryid'];
920 // Summary format.
921 $course['summaryformat'] = util::validate_format($course['summaryformat']);
923 if (!empty($course['courseformatoptions'])) {
924 foreach ($course['courseformatoptions'] as $option) {
925 $course[$option['name']] = $option['value'];
929 // Custom fields.
930 if (!empty($course['customfields'])) {
931 foreach ($course['customfields'] as $field) {
932 $course['customfield_'.$field['shortname']] = $field['value'];
936 //Note: create_course() core function check shortname, idnumber, category
937 $course['id'] = create_course((object) $course)->id;
939 $resultcourses[] = array('id' => $course['id'], 'shortname' => $course['shortname']);
942 $transaction->allow_commit();
944 return $resultcourses;
948 * Returns description of method result value
950 * @return \core_external\external_description
951 * @since Moodle 2.2
953 public static function create_courses_returns() {
954 return new external_multiple_structure(
955 new external_single_structure(
956 array(
957 'id' => new external_value(PARAM_INT, 'course id'),
958 'shortname' => new external_value(PARAM_RAW, 'short name'),
965 * Update courses
967 * @return external_function_parameters
968 * @since Moodle 2.5
970 public static function update_courses_parameters() {
971 return new external_function_parameters(
972 array(
973 'courses' => new external_multiple_structure(
974 new external_single_structure(
975 array(
976 'id' => new external_value(PARAM_INT, 'ID of the course'),
977 'fullname' => new external_value(PARAM_TEXT, 'full name', VALUE_OPTIONAL),
978 'shortname' => new external_value(PARAM_TEXT, 'course short name', VALUE_OPTIONAL),
979 'categoryid' => new external_value(PARAM_INT, 'category id', VALUE_OPTIONAL),
980 'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
981 'summary' => new external_value(PARAM_RAW, 'summary', VALUE_OPTIONAL),
982 'summaryformat' => new external_format_value('summary', VALUE_OPTIONAL),
983 'format' => new external_value(PARAM_PLUGIN,
984 'course format: weeks, topics, social, site,..', VALUE_OPTIONAL),
985 'showgrades' => new external_value(PARAM_INT,
986 '1 if grades are shown, otherwise 0', VALUE_OPTIONAL),
987 'newsitems' => new external_value(PARAM_INT,
988 'number of recent items appearing on the course page', VALUE_OPTIONAL),
989 'startdate' => new external_value(PARAM_INT,
990 'timestamp when the course start', VALUE_OPTIONAL),
991 'enddate' => new external_value(PARAM_INT,
992 'timestamp when the course end', VALUE_OPTIONAL),
993 'numsections' => new external_value(PARAM_INT,
994 '(deprecated, use courseformatoptions) number of weeks/topics', VALUE_OPTIONAL),
995 'maxbytes' => new external_value(PARAM_INT,
996 'largest size of file that can be uploaded into the course', VALUE_OPTIONAL),
997 'showreports' => new external_value(PARAM_INT,
998 'are activity report shown (yes = 1, no =0)', VALUE_OPTIONAL),
999 'visible' => new external_value(PARAM_INT,
1000 '1: available to student, 0:not available', VALUE_OPTIONAL),
1001 'hiddensections' => new external_value(PARAM_INT,
1002 '(deprecated, use courseformatoptions) How the hidden sections in the course are
1003 displayed to students', VALUE_OPTIONAL),
1004 'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible', VALUE_OPTIONAL),
1005 'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no', VALUE_OPTIONAL),
1006 'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id', VALUE_OPTIONAL),
1007 'enablecompletion' => new external_value(PARAM_INT,
1008 'Enabled, control via completion and activity settings. Disabled,
1009 not shown in activity settings.', VALUE_OPTIONAL),
1010 'completionnotify' => new external_value(PARAM_INT, '1: yes 0: no', VALUE_OPTIONAL),
1011 'lang' => new external_value(PARAM_SAFEDIR, 'forced course language', VALUE_OPTIONAL),
1012 'forcetheme' => new external_value(PARAM_PLUGIN, 'name of the force theme', VALUE_OPTIONAL),
1013 'courseformatoptions' => new external_multiple_structure(
1014 new external_single_structure(
1015 array('name' => new external_value(PARAM_ALPHANUMEXT, 'course format option name'),
1016 'value' => new external_value(PARAM_RAW, 'course format option value')
1017 )), 'additional options for particular course format', VALUE_OPTIONAL),
1018 'customfields' => new external_multiple_structure(
1019 new external_single_structure(
1021 'shortname' => new external_value(PARAM_ALPHANUMEXT, 'The shortname of the custom field'),
1022 'value' => new external_value(PARAM_RAW, 'The value of the custom field')
1024 ), 'Custom fields', VALUE_OPTIONAL),
1026 ), 'courses to update'
1033 * Update courses
1035 * @param array $courses
1036 * @since Moodle 2.5
1038 public static function update_courses($courses) {
1039 global $CFG, $DB;
1040 require_once($CFG->dirroot . "/course/lib.php");
1041 $warnings = array();
1043 $params = self::validate_parameters(self::update_courses_parameters(),
1044 array('courses' => $courses));
1046 $availablethemes = core_component::get_plugin_list('theme');
1047 $availablelangs = get_string_manager()->get_list_of_translations();
1049 foreach ($params['courses'] as $course) {
1050 // Catch any exception while updating course and return as warning to user.
1051 try {
1052 // Ensure the current user is allowed to run this function.
1053 $context = context_course::instance($course['id'], MUST_EXIST);
1054 self::validate_context($context);
1056 $oldcourse = course_get_format($course['id'])->get_course();
1058 require_capability('moodle/course:update', $context);
1060 // Check if user can change category.
1061 if (array_key_exists('categoryid', $course) && ($oldcourse->category != $course['categoryid'])) {
1062 require_capability('moodle/course:changecategory', $context);
1063 $course['category'] = $course['categoryid'];
1066 // Check if the user can change fullname, and the new value is non-empty.
1067 if (array_key_exists('fullname', $course) && ($oldcourse->fullname != $course['fullname'])) {
1068 require_capability('moodle/course:changefullname', $context);
1069 if (trim($course['fullname']) === '') {
1070 throw new moodle_exception('errorinvalidparam', 'webservice', '', 'fullname');
1074 // Check if the user can change shortname, and the new value is non-empty.
1075 if (array_key_exists('shortname', $course) && ($oldcourse->shortname != $course['shortname'])) {
1076 require_capability('moodle/course:changeshortname', $context);
1077 if (trim($course['shortname']) === '') {
1078 throw new moodle_exception('errorinvalidparam', 'webservice', '', 'shortname');
1082 // Check if the user can change the idnumber.
1083 if (array_key_exists('idnumber', $course) && ($oldcourse->idnumber != $course['idnumber'])) {
1084 require_capability('moodle/course:changeidnumber', $context);
1087 // Check if user can change summary.
1088 if (array_key_exists('summary', $course) && ($oldcourse->summary != $course['summary'])) {
1089 require_capability('moodle/course:changesummary', $context);
1092 // Summary format.
1093 if (array_key_exists('summaryformat', $course) && ($oldcourse->summaryformat != $course['summaryformat'])) {
1094 require_capability('moodle/course:changesummary', $context);
1095 $course['summaryformat'] = util::validate_format($course['summaryformat']);
1098 // Check if user can change visibility.
1099 if (array_key_exists('visible', $course) && ($oldcourse->visible != $course['visible'])) {
1100 require_capability('moodle/course:visibility', $context);
1103 // Make sure lang is valid.
1104 if (array_key_exists('lang', $course) && ($oldcourse->lang != $course['lang'])) {
1105 require_capability('moodle/course:setforcedlanguage', $context);
1106 if (empty($availablelangs[$course['lang']])) {
1107 throw new moodle_exception('errorinvalidparam', 'webservice', '', 'lang');
1111 // Make sure theme is valid.
1112 if (array_key_exists('forcetheme', $course)) {
1113 if (!empty($CFG->allowcoursethemes)) {
1114 if (empty($availablethemes[$course['forcetheme']])) {
1115 throw new moodle_exception('errorinvalidparam', 'webservice', '', 'forcetheme');
1116 } else {
1117 $course['theme'] = $course['forcetheme'];
1122 // Make sure completion is enabled before setting it.
1123 if (array_key_exists('enabledcompletion', $course) && !completion_info::is_enabled_for_site()) {
1124 $course['enabledcompletion'] = 0;
1127 // Make sure maxbytes are less then CFG->maxbytes.
1128 if (array_key_exists('maxbytes', $course)) {
1129 // We allow updates back to 0 max bytes, a special value denoting the course uses the site limit.
1130 // Otherwise, either use the size specified, or cap at the max size for the course.
1131 if ($course['maxbytes'] != 0) {
1132 $course['maxbytes'] = get_max_upload_file_size($CFG->maxbytes, $course['maxbytes']);
1136 if (!empty($course['courseformatoptions'])) {
1137 foreach ($course['courseformatoptions'] as $option) {
1138 if (isset($option['name']) && isset($option['value'])) {
1139 $course[$option['name']] = $option['value'];
1144 // Prepare list of custom fields.
1145 if (isset($course['customfields'])) {
1146 foreach ($course['customfields'] as $field) {
1147 $course['customfield_' . $field['shortname']] = $field['value'];
1151 // Update course if user has all required capabilities.
1152 update_course((object) $course);
1153 } catch (Exception $e) {
1154 $warning = array();
1155 $warning['item'] = 'course';
1156 $warning['itemid'] = $course['id'];
1157 if ($e instanceof moodle_exception) {
1158 $warning['warningcode'] = $e->errorcode;
1159 } else {
1160 $warning['warningcode'] = $e->getCode();
1162 $warning['message'] = $e->getMessage();
1163 $warnings[] = $warning;
1167 $result = array();
1168 $result['warnings'] = $warnings;
1169 return $result;
1173 * Returns description of method result value
1175 * @return \core_external\external_description
1176 * @since Moodle 2.5
1178 public static function update_courses_returns() {
1179 return new external_single_structure(
1180 array(
1181 'warnings' => new external_warnings()
1187 * Returns description of method parameters
1189 * @return external_function_parameters
1190 * @since Moodle 2.2
1192 public static function delete_courses_parameters() {
1193 return new external_function_parameters(
1194 array(
1195 'courseids' => new external_multiple_structure(new external_value(PARAM_INT, 'course ID')),
1201 * Delete courses
1203 * @param array $courseids A list of course ids
1204 * @since Moodle 2.2
1206 public static function delete_courses($courseids) {
1207 global $CFG, $DB;
1208 require_once($CFG->dirroot."/course/lib.php");
1210 // Parameter validation.
1211 $params = self::validate_parameters(self::delete_courses_parameters(), array('courseids'=>$courseids));
1213 $warnings = array();
1215 foreach ($params['courseids'] as $courseid) {
1216 $course = $DB->get_record('course', array('id' => $courseid));
1218 if ($course === false) {
1219 $warnings[] = array(
1220 'item' => 'course',
1221 'itemid' => $courseid,
1222 'warningcode' => 'unknowncourseidnumber',
1223 'message' => 'Unknown course ID ' . $courseid
1225 continue;
1228 // Check if the context is valid.
1229 $coursecontext = context_course::instance($course->id);
1230 self::validate_context($coursecontext);
1232 // Check if the current user has permission.
1233 if (!can_delete_course($courseid)) {
1234 $warnings[] = array(
1235 'item' => 'course',
1236 'itemid' => $courseid,
1237 'warningcode' => 'cannotdeletecourse',
1238 'message' => 'You do not have the permission to delete this course' . $courseid
1240 continue;
1243 if (delete_course($course, false) === false) {
1244 $warnings[] = array(
1245 'item' => 'course',
1246 'itemid' => $courseid,
1247 'warningcode' => 'cannotdeletecategorycourse',
1248 'message' => 'Course ' . $courseid . ' failed to be deleted'
1250 continue;
1254 fix_course_sortorder();
1256 return array('warnings' => $warnings);
1260 * Returns description of method result value
1262 * @return \core_external\external_description
1263 * @since Moodle 2.2
1265 public static function delete_courses_returns() {
1266 return new external_single_structure(
1267 array(
1268 'warnings' => new external_warnings()
1274 * Returns description of method parameters
1276 * @return external_function_parameters
1277 * @since Moodle 2.3
1279 public static function duplicate_course_parameters() {
1280 return new external_function_parameters(
1281 array(
1282 'courseid' => new external_value(PARAM_INT, 'course to duplicate id'),
1283 'fullname' => new external_value(PARAM_TEXT, 'duplicated course full name'),
1284 'shortname' => new external_value(PARAM_TEXT, 'duplicated course short name'),
1285 'categoryid' => new external_value(PARAM_INT, 'duplicated course category parent'),
1286 'visible' => new external_value(PARAM_INT, 'duplicated course visible, default to yes', VALUE_DEFAULT, 1),
1287 'options' => new external_multiple_structure(
1288 new external_single_structure(
1289 array(
1290 'name' => new external_value(PARAM_ALPHAEXT, 'The backup option name:
1291 "activities" (int) Include course activites (default to 1 that is equal to yes),
1292 "blocks" (int) Include course blocks (default to 1 that is equal to yes),
1293 "filters" (int) Include course filters (default to 1 that is equal to yes),
1294 "users" (int) Include users (default to 0 that is equal to no),
1295 "enrolments" (int) Include enrolment methods (default to 1 - restore only with users),
1296 "role_assignments" (int) Include role assignments (default to 0 that is equal to no),
1297 "comments" (int) Include user comments (default to 0 that is equal to no),
1298 "userscompletion" (int) Include user course completion information (default to 0 that is equal to no),
1299 "logs" (int) Include course logs (default to 0 that is equal to no),
1300 "grade_histories" (int) Include histories (default to 0 that is equal to no)'
1302 'value' => new external_value(PARAM_RAW, 'the value for the option 1 (yes) or 0 (no)'
1305 ), 'Course duplication options', VALUE_DEFAULT, array()
1312 * Duplicate a course
1314 * @param int $courseid
1315 * @param string $fullname Duplicated course fullname
1316 * @param string $shortname Duplicated course shortname
1317 * @param int $categoryid Duplicated course parent category id
1318 * @param int $visible Duplicated course availability
1319 * @param array $options List of backup options
1320 * @return array New course info
1321 * @since Moodle 2.3
1323 public static function duplicate_course($courseid, $fullname, $shortname, $categoryid, $visible = 1, $options = array()) {
1324 global $CFG, $USER, $DB;
1325 require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
1326 require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
1328 // Parameter validation.
1329 $params = self::validate_parameters(
1330 self::duplicate_course_parameters(),
1331 array(
1332 'courseid' => $courseid,
1333 'fullname' => $fullname,
1334 'shortname' => $shortname,
1335 'categoryid' => $categoryid,
1336 'visible' => $visible,
1337 'options' => $options
1341 // Context validation.
1343 if (! ($course = $DB->get_record('course', array('id'=>$params['courseid'])))) {
1344 throw new moodle_exception('invalidcourseid', 'error');
1347 // Category where duplicated course is going to be created.
1348 $categorycontext = context_coursecat::instance($params['categoryid']);
1349 self::validate_context($categorycontext);
1351 // Course to be duplicated.
1352 $coursecontext = context_course::instance($course->id);
1353 self::validate_context($coursecontext);
1355 $backupdefaults = array(
1356 'activities' => 1,
1357 'blocks' => 1,
1358 'filters' => 1,
1359 'users' => 0,
1360 'enrolments' => backup::ENROL_WITHUSERS,
1361 'role_assignments' => 0,
1362 'comments' => 0,
1363 'userscompletion' => 0,
1364 'logs' => 0,
1365 'grade_histories' => 0
1368 $backupsettings = array();
1369 // Check for backup and restore options.
1370 if (!empty($params['options'])) {
1371 foreach ($params['options'] as $option) {
1373 // Strict check for a correct value (allways 1 or 0, true or false).
1374 $value = clean_param($option['value'], PARAM_INT);
1376 if ($value !== 0 and $value !== 1) {
1377 throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
1380 if (!isset($backupdefaults[$option['name']])) {
1381 throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
1384 $backupsettings[$option['name']] = $value;
1388 // Capability checking.
1390 // The backup controller check for this currently, this may be redundant.
1391 require_capability('moodle/course:create', $categorycontext);
1392 require_capability('moodle/restore:restorecourse', $categorycontext);
1393 require_capability('moodle/backup:backupcourse', $coursecontext);
1395 if (!empty($backupsettings['users'])) {
1396 require_capability('moodle/backup:userinfo', $coursecontext);
1397 require_capability('moodle/restore:userinfo', $categorycontext);
1400 // Check if the shortname is used.
1401 if ($foundcourses = $DB->get_records('course', array('shortname'=>$shortname))) {
1402 foreach ($foundcourses as $foundcourse) {
1403 $foundcoursenames[] = $foundcourse->fullname;
1406 $foundcoursenamestring = implode(',', $foundcoursenames);
1407 throw new moodle_exception('shortnametaken', '', '', $foundcoursenamestring);
1410 // Backup the course.
1412 $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
1413 backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $USER->id);
1415 foreach ($backupsettings as $name => $value) {
1416 if ($setting = $bc->get_plan()->get_setting($name)) {
1417 $bc->get_plan()->get_setting($name)->set_value($value);
1421 $backupid = $bc->get_backupid();
1422 $backupbasepath = $bc->get_plan()->get_basepath();
1424 $bc->execute_plan();
1425 $results = $bc->get_results();
1426 $file = $results['backup_destination'];
1428 $bc->destroy();
1430 // Restore the backup immediately.
1432 // Check if we need to unzip the file because the backup temp dir does not contains backup files.
1433 if (!file_exists($backupbasepath . "/moodle_backup.xml")) {
1434 $file->extract_to_pathname(get_file_packer('application/vnd.moodle.backup'), $backupbasepath);
1437 // Create new course.
1438 $newcourseid = restore_dbops::create_new_course($params['fullname'], $params['shortname'], $params['categoryid']);
1440 $rc = new restore_controller($backupid, $newcourseid,
1441 backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $USER->id, backup::TARGET_NEW_COURSE);
1443 foreach ($backupsettings as $name => $value) {
1444 $setting = $rc->get_plan()->get_setting($name);
1445 if ($setting->get_status() == backup_setting::NOT_LOCKED) {
1446 $setting->set_value($value);
1450 if (!$rc->execute_precheck()) {
1451 $precheckresults = $rc->get_precheck_results();
1452 if (is_array($precheckresults) && !empty($precheckresults['errors'])) {
1453 if (empty($CFG->keeptempdirectoriesonbackup)) {
1454 fulldelete($backupbasepath);
1457 $errorinfo = '';
1459 foreach ($precheckresults['errors'] as $error) {
1460 $errorinfo .= $error;
1463 if (array_key_exists('warnings', $precheckresults)) {
1464 foreach ($precheckresults['warnings'] as $warning) {
1465 $errorinfo .= $warning;
1469 throw new moodle_exception('backupprecheckerrors', 'webservice', '', $errorinfo);
1473 $rc->execute_plan();
1474 $rc->destroy();
1476 $course = $DB->get_record('course', array('id' => $newcourseid), '*', MUST_EXIST);
1477 $course->fullname = $params['fullname'];
1478 $course->shortname = $params['shortname'];
1479 $course->visible = $params['visible'];
1481 // Set shortname and fullname back.
1482 $DB->update_record('course', $course);
1484 if (empty($CFG->keeptempdirectoriesonbackup)) {
1485 fulldelete($backupbasepath);
1488 // Delete the course backup file created by this WebService. Originally located in the course backups area.
1489 $file->delete();
1491 return array('id' => $course->id, 'shortname' => $course->shortname);
1495 * Returns description of method result value
1497 * @return \core_external\external_description
1498 * @since Moodle 2.3
1500 public static function duplicate_course_returns() {
1501 return new external_single_structure(
1502 array(
1503 'id' => new external_value(PARAM_INT, 'course id'),
1504 'shortname' => new external_value(PARAM_RAW, 'short name'),
1510 * Returns description of method parameters for import_course
1512 * @return external_function_parameters
1513 * @since Moodle 2.4
1515 public static function import_course_parameters() {
1516 return new external_function_parameters(
1517 array(
1518 'importfrom' => new external_value(PARAM_INT, 'the id of the course we are importing from'),
1519 'importto' => new external_value(PARAM_INT, 'the id of the course we are importing to'),
1520 'deletecontent' => new external_value(PARAM_INT, 'whether to delete the course content where we are importing to (default to 0 = No)', VALUE_DEFAULT, 0),
1521 'options' => new external_multiple_structure(
1522 new external_single_structure(
1523 array(
1524 'name' => new external_value(PARAM_ALPHA, 'The backup option name:
1525 "activities" (int) Include course activites (default to 1 that is equal to yes),
1526 "blocks" (int) Include course blocks (default to 1 that is equal to yes),
1527 "filters" (int) Include course filters (default to 1 that is equal to yes)'
1529 'value' => new external_value(PARAM_RAW, 'the value for the option 1 (yes) or 0 (no)'
1532 ), 'Course import options', VALUE_DEFAULT, array()
1539 * Imports a course
1541 * @param int $importfrom The id of the course we are importing from
1542 * @param int $importto The id of the course we are importing to
1543 * @param bool $deletecontent Whether to delete the course we are importing to content
1544 * @param array $options List of backup options
1545 * @return null
1546 * @since Moodle 2.4
1548 public static function import_course($importfrom, $importto, $deletecontent = 0, $options = array()) {
1549 global $CFG, $USER, $DB;
1550 require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
1551 require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
1553 // Parameter validation.
1554 $params = self::validate_parameters(
1555 self::import_course_parameters(),
1556 array(
1557 'importfrom' => $importfrom,
1558 'importto' => $importto,
1559 'deletecontent' => $deletecontent,
1560 'options' => $options
1564 if ($params['deletecontent'] !== 0 and $params['deletecontent'] !== 1) {
1565 throw new moodle_exception('invalidextparam', 'webservice', '', $params['deletecontent']);
1568 // Context validation.
1570 if (! ($importfrom = $DB->get_record('course', array('id'=>$params['importfrom'])))) {
1571 throw new moodle_exception('invalidcourseid', 'error');
1574 if (! ($importto = $DB->get_record('course', array('id'=>$params['importto'])))) {
1575 throw new moodle_exception('invalidcourseid', 'error');
1578 $importfromcontext = context_course::instance($importfrom->id);
1579 self::validate_context($importfromcontext);
1581 $importtocontext = context_course::instance($importto->id);
1582 self::validate_context($importtocontext);
1584 $backupdefaults = array(
1585 'activities' => 1,
1586 'blocks' => 1,
1587 'filters' => 1
1590 $backupsettings = array();
1592 // Check for backup and restore options.
1593 if (!empty($params['options'])) {
1594 foreach ($params['options'] as $option) {
1596 // Strict check for a correct value (allways 1 or 0, true or false).
1597 $value = clean_param($option['value'], PARAM_INT);
1599 if ($value !== 0 and $value !== 1) {
1600 throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
1603 if (!isset($backupdefaults[$option['name']])) {
1604 throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
1607 $backupsettings[$option['name']] = $value;
1611 // Capability checking.
1613 require_capability('moodle/backup:backuptargetimport', $importfromcontext);
1614 require_capability('moodle/restore:restoretargetimport', $importtocontext);
1616 $bc = new backup_controller(backup::TYPE_1COURSE, $importfrom->id, backup::FORMAT_MOODLE,
1617 backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id);
1619 foreach ($backupsettings as $name => $value) {
1620 $bc->get_plan()->get_setting($name)->set_value($value);
1623 $backupid = $bc->get_backupid();
1624 $backupbasepath = $bc->get_plan()->get_basepath();
1626 $bc->execute_plan();
1627 $bc->destroy();
1629 // Restore the backup immediately.
1631 // Check if we must delete the contents of the destination course.
1632 if ($params['deletecontent']) {
1633 $restoretarget = backup::TARGET_EXISTING_DELETING;
1634 } else {
1635 $restoretarget = backup::TARGET_EXISTING_ADDING;
1638 $rc = new restore_controller($backupid, $importto->id,
1639 backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id, $restoretarget);
1641 foreach ($backupsettings as $name => $value) {
1642 $rc->get_plan()->get_setting($name)->set_value($value);
1645 if (!$rc->execute_precheck()) {
1646 $precheckresults = $rc->get_precheck_results();
1647 if (is_array($precheckresults) && !empty($precheckresults['errors'])) {
1648 if (empty($CFG->keeptempdirectoriesonbackup)) {
1649 fulldelete($backupbasepath);
1652 $errorinfo = '';
1654 foreach ($precheckresults['errors'] as $error) {
1655 $errorinfo .= $error;
1658 if (array_key_exists('warnings', $precheckresults)) {
1659 foreach ($precheckresults['warnings'] as $warning) {
1660 $errorinfo .= $warning;
1664 throw new moodle_exception('backupprecheckerrors', 'webservice', '', $errorinfo);
1666 } else {
1667 if ($restoretarget == backup::TARGET_EXISTING_DELETING) {
1668 restore_dbops::delete_course_content($importto->id);
1672 $rc->execute_plan();
1673 $rc->destroy();
1675 if (empty($CFG->keeptempdirectoriesonbackup)) {
1676 fulldelete($backupbasepath);
1679 return null;
1683 * Returns description of method result value
1685 * @return \core_external\external_description
1686 * @since Moodle 2.4
1688 public static function import_course_returns() {
1689 return null;
1693 * Returns description of method parameters
1695 * @return external_function_parameters
1696 * @since Moodle 2.3
1698 public static function get_categories_parameters() {
1699 return new external_function_parameters(
1700 array(
1701 'criteria' => new external_multiple_structure(
1702 new external_single_structure(
1703 array(
1704 'key' => new external_value(PARAM_ALPHA,
1705 'The category column to search, expected keys (value format) are:'.
1706 '"id" (int) the category id,'.
1707 '"ids" (string) category ids separated by commas,'.
1708 '"name" (string) the category name,'.
1709 '"parent" (int) the parent category id,'.
1710 '"idnumber" (string) category idnumber'.
1711 ' - user must have \'moodle/category:manage\' to search on idnumber,'.
1712 '"visible" (int) whether the returned categories must be visible or hidden. If the key is not passed,
1713 then the function return all categories that the user can see.'.
1714 ' - user must have \'moodle/category:manage\' or \'moodle/category:viewhiddencategories\' to search on visible,'.
1715 '"theme" (string) only return the categories having this theme'.
1716 ' - user must have \'moodle/category:manage\' to search on theme'),
1717 'value' => new external_value(PARAM_RAW, 'the value to match')
1719 ), 'criteria', VALUE_DEFAULT, array()
1721 'addsubcategories' => new external_value(PARAM_BOOL, 'return the sub categories infos
1722 (1 - default) otherwise only the category info (0)', VALUE_DEFAULT, 1)
1728 * Get categories
1730 * @param array $criteria Criteria to match the results
1731 * @param booln $addsubcategories obtain only the category (false) or its subcategories (true - default)
1732 * @return array list of categories
1733 * @since Moodle 2.3
1735 public static function get_categories($criteria = array(), $addsubcategories = true) {
1736 global $CFG, $DB;
1737 require_once($CFG->dirroot . "/course/lib.php");
1739 // Validate parameters.
1740 $params = self::validate_parameters(self::get_categories_parameters(),
1741 array('criteria' => $criteria, 'addsubcategories' => $addsubcategories));
1743 // Retrieve the categories.
1744 $categories = array();
1745 if (!empty($params['criteria'])) {
1747 $conditions = array();
1748 $wheres = array();
1749 foreach ($params['criteria'] as $crit) {
1750 $key = trim($crit['key']);
1752 // Trying to avoid duplicate keys.
1753 if (!isset($conditions[$key])) {
1755 $context = context_system::instance();
1756 $value = null;
1757 switch ($key) {
1758 case 'id':
1759 $value = clean_param($crit['value'], PARAM_INT);
1760 $conditions[$key] = $value;
1761 $wheres[] = $key . " = :" . $key;
1762 break;
1764 case 'ids':
1765 $value = clean_param($crit['value'], PARAM_SEQUENCE);
1766 $ids = explode(',', $value);
1767 list($sqlids, $paramids) = $DB->get_in_or_equal($ids, SQL_PARAMS_NAMED);
1768 $conditions = array_merge($conditions, $paramids);
1769 $wheres[] = 'id ' . $sqlids;
1770 break;
1772 case 'idnumber':
1773 if (has_capability('moodle/category:manage', $context)) {
1774 $value = clean_param($crit['value'], PARAM_RAW);
1775 $conditions[$key] = $value;
1776 $wheres[] = $key . " = :" . $key;
1777 } else {
1778 // We must throw an exception.
1779 // Otherwise the dev client would think no idnumber exists.
1780 throw new moodle_exception('criteriaerror',
1781 'webservice', '', null,
1782 'You don\'t have the permissions to search on the "idnumber" field.');
1784 break;
1786 case 'name':
1787 $value = clean_param($crit['value'], PARAM_TEXT);
1788 $conditions[$key] = $value;
1789 $wheres[] = $key . " = :" . $key;
1790 break;
1792 case 'parent':
1793 $value = clean_param($crit['value'], PARAM_INT);
1794 $conditions[$key] = $value;
1795 $wheres[] = $key . " = :" . $key;
1796 break;
1798 case 'visible':
1799 if (has_capability('moodle/category:viewhiddencategories', $context)) {
1800 $value = clean_param($crit['value'], PARAM_INT);
1801 $conditions[$key] = $value;
1802 $wheres[] = $key . " = :" . $key;
1803 } else {
1804 throw new moodle_exception('criteriaerror',
1805 'webservice', '', null,
1806 'You don\'t have the permissions to search on the "visible" field.');
1808 break;
1810 case 'theme':
1811 if (has_capability('moodle/category:manage', $context)) {
1812 $value = clean_param($crit['value'], PARAM_THEME);
1813 $conditions[$key] = $value;
1814 $wheres[] = $key . " = :" . $key;
1815 } else {
1816 throw new moodle_exception('criteriaerror',
1817 'webservice', '', null,
1818 'You don\'t have the permissions to search on the "theme" field.');
1820 break;
1822 default:
1823 throw new moodle_exception('criteriaerror',
1824 'webservice', '', null,
1825 'You can not search on this criteria: ' . $key);
1830 if (!empty($wheres)) {
1831 $wheres = implode(" AND ", $wheres);
1833 $categories = $DB->get_records_select('course_categories', $wheres, $conditions);
1835 // Retrieve its sub subcategories (all levels).
1836 if ($categories and !empty($params['addsubcategories'])) {
1837 $newcategories = array();
1839 // Check if we required visible/theme checks.
1840 $additionalselect = '';
1841 $additionalparams = array();
1842 if (isset($conditions['visible'])) {
1843 $additionalselect .= ' AND visible = :visible';
1844 $additionalparams['visible'] = $conditions['visible'];
1846 if (isset($conditions['theme'])) {
1847 $additionalselect .= ' AND theme= :theme';
1848 $additionalparams['theme'] = $conditions['theme'];
1851 foreach ($categories as $category) {
1852 $sqlselect = $DB->sql_like('path', ':path') . $additionalselect;
1853 $sqlparams = array('path' => $category->path.'/%') + $additionalparams; // It will NOT include the specified category.
1854 $subcategories = $DB->get_records_select('course_categories', $sqlselect, $sqlparams);
1855 $newcategories = $newcategories + $subcategories; // Both arrays have integer as keys.
1857 $categories = $categories + $newcategories;
1861 } else {
1862 // Retrieve all categories in the database.
1863 $categories = $DB->get_records('course_categories');
1866 // The not returned categories. key => category id, value => reason of exclusion.
1867 $excludedcats = array();
1869 // The returned categories.
1870 $categoriesinfo = array();
1872 // We need to sort the categories by path.
1873 // The parent cats need to be checked by the algo first.
1874 usort($categories, "core_course_external::compare_categories_by_path");
1876 foreach ($categories as $category) {
1878 // Check if the category is a child of an excluded category, if yes exclude it too (excluded => do not return).
1879 $parents = explode('/', $category->path);
1880 unset($parents[0]); // First key is always empty because path start with / => /1/2/4.
1881 foreach ($parents as $parentid) {
1882 // Note: when the parent exclusion was due to the context,
1883 // the sub category could still be returned.
1884 if (isset($excludedcats[$parentid]) and $excludedcats[$parentid] != 'context') {
1885 $excludedcats[$category->id] = 'parent';
1889 // Check the user can use the category context.
1890 $context = context_coursecat::instance($category->id);
1891 try {
1892 self::validate_context($context);
1893 } catch (Exception $e) {
1894 $excludedcats[$category->id] = 'context';
1896 // If it was the requested category then throw an exception.
1897 if (isset($params['categoryid']) && $category->id == $params['categoryid']) {
1898 $exceptionparam = new stdClass();
1899 $exceptionparam->message = $e->getMessage();
1900 $exceptionparam->catid = $category->id;
1901 throw new moodle_exception('errorcatcontextnotvalid', 'webservice', '', $exceptionparam);
1905 // Return the category information.
1906 if (!isset($excludedcats[$category->id])) {
1908 // Final check to see if the category is visible to the user.
1909 if (core_course_category::can_view_category($category)) {
1911 $categoryinfo = array();
1912 $categoryinfo['id'] = $category->id;
1913 $categoryinfo['name'] = \core_external\util::format_string($category->name, $context);
1914 list($categoryinfo['description'], $categoryinfo['descriptionformat']) =
1915 \core_external\util::format_text($category->description, $category->descriptionformat,
1916 $context, 'coursecat', 'description', null);
1917 $categoryinfo['parent'] = $category->parent;
1918 $categoryinfo['sortorder'] = $category->sortorder;
1919 $categoryinfo['coursecount'] = $category->coursecount;
1920 $categoryinfo['depth'] = $category->depth;
1921 $categoryinfo['path'] = $category->path;
1923 // Some fields only returned for admin.
1924 if (has_capability('moodle/category:manage', $context)) {
1925 $categoryinfo['idnumber'] = $category->idnumber;
1926 $categoryinfo['visible'] = $category->visible;
1927 $categoryinfo['visibleold'] = $category->visibleold;
1928 $categoryinfo['timemodified'] = $category->timemodified;
1929 $categoryinfo['theme'] = clean_param($category->theme, PARAM_THEME);
1932 $categoriesinfo[] = $categoryinfo;
1933 } else {
1934 $excludedcats[$category->id] = 'visibility';
1939 // Sorting the resulting array so it looks a bit better for the client developer.
1940 usort($categoriesinfo, "core_course_external::compare_categories_by_sortorder");
1942 return $categoriesinfo;
1946 * Sort categories array by path
1947 * private function: only used by get_categories
1949 * @param array $category1
1950 * @param array $category2
1951 * @return int result of strcmp
1952 * @since Moodle 2.3
1954 private static function compare_categories_by_path($category1, $category2) {
1955 return strcmp($category1->path, $category2->path);
1959 * Sort categories array by sortorder
1960 * private function: only used by get_categories
1962 * @param array $category1
1963 * @param array $category2
1964 * @return int result of strcmp
1965 * @since Moodle 2.3
1967 private static function compare_categories_by_sortorder($category1, $category2) {
1968 return strcmp($category1['sortorder'], $category2['sortorder']);
1972 * Returns description of method result value
1974 * @return \core_external\external_description
1975 * @since Moodle 2.3
1977 public static function get_categories_returns() {
1978 return new external_multiple_structure(
1979 new external_single_structure(
1980 array(
1981 'id' => new external_value(PARAM_INT, 'category id'),
1982 'name' => new external_value(PARAM_RAW, 'category name'),
1983 'idnumber' => new external_value(PARAM_RAW, 'category id number', VALUE_OPTIONAL),
1984 'description' => new external_value(PARAM_RAW, 'category description'),
1985 'descriptionformat' => new external_format_value('description'),
1986 'parent' => new external_value(PARAM_INT, 'parent category id'),
1987 'sortorder' => new external_value(PARAM_INT, 'category sorting order'),
1988 'coursecount' => new external_value(PARAM_INT, 'number of courses in this category'),
1989 'visible' => new external_value(PARAM_INT, '1: available, 0:not available', VALUE_OPTIONAL),
1990 'visibleold' => new external_value(PARAM_INT, '1: available, 0:not available', VALUE_OPTIONAL),
1991 'timemodified' => new external_value(PARAM_INT, 'timestamp', VALUE_OPTIONAL),
1992 'depth' => new external_value(PARAM_INT, 'category depth'),
1993 'path' => new external_value(PARAM_TEXT, 'category path'),
1994 'theme' => new external_value(PARAM_THEME, 'category theme', VALUE_OPTIONAL),
1995 ), 'List of categories'
2001 * Returns description of method parameters
2003 * @return external_function_parameters
2004 * @since Moodle 2.3
2006 public static function create_categories_parameters() {
2007 return new external_function_parameters(
2008 array(
2009 'categories' => new external_multiple_structure(
2010 new external_single_structure(
2011 array(
2012 'name' => new external_value(PARAM_TEXT, 'new category name'),
2013 'parent' => new external_value(PARAM_INT,
2014 'the parent category id inside which the new category will be created
2015 - set to 0 for a root category',
2016 VALUE_DEFAULT, 0),
2017 'idnumber' => new external_value(PARAM_RAW,
2018 'the new category idnumber', VALUE_OPTIONAL),
2019 'description' => new external_value(PARAM_RAW,
2020 'the new category description', VALUE_OPTIONAL),
2021 'descriptionformat' => new external_format_value('description', VALUE_DEFAULT),
2022 'theme' => new external_value(PARAM_THEME,
2023 'the new category theme. This option must be enabled on moodle',
2024 VALUE_OPTIONAL),
2033 * Create categories
2035 * @param array $categories - see create_categories_parameters() for the array structure
2036 * @return array - see create_categories_returns() for the array structure
2037 * @since Moodle 2.3
2039 public static function create_categories($categories) {
2040 global $DB;
2042 $params = self::validate_parameters(self::create_categories_parameters(),
2043 array('categories' => $categories));
2045 $transaction = $DB->start_delegated_transaction();
2047 $createdcategories = array();
2048 foreach ($params['categories'] as $category) {
2049 if ($category['parent']) {
2050 if (!$DB->record_exists('course_categories', array('id' => $category['parent']))) {
2051 throw new moodle_exception('unknowcategory');
2053 $context = context_coursecat::instance($category['parent']);
2054 } else {
2055 $context = context_system::instance();
2057 self::validate_context($context);
2058 require_capability('moodle/category:manage', $context);
2060 // this will validate format and throw an exception if there are errors
2061 util::validate_format($category['descriptionformat']);
2063 $newcategory = core_course_category::create($category);
2064 $context = context_coursecat::instance($newcategory->id);
2066 $createdcategories[] = array(
2067 'id' => $newcategory->id,
2068 'name' => \core_external\util::format_string($newcategory->name, $context),
2072 $transaction->allow_commit();
2074 return $createdcategories;
2078 * Returns description of method parameters
2080 * @return external_function_parameters
2081 * @since Moodle 2.3
2083 public static function create_categories_returns() {
2084 return new external_multiple_structure(
2085 new external_single_structure(
2086 array(
2087 'id' => new external_value(PARAM_INT, 'new category id'),
2088 'name' => new external_value(PARAM_RAW, 'new category name'),
2095 * Returns description of method parameters
2097 * @return external_function_parameters
2098 * @since Moodle 2.3
2100 public static function update_categories_parameters() {
2101 return new external_function_parameters(
2102 array(
2103 'categories' => new external_multiple_structure(
2104 new external_single_structure(
2105 array(
2106 'id' => new external_value(PARAM_INT, 'course id'),
2107 'name' => new external_value(PARAM_TEXT, 'category name', VALUE_OPTIONAL),
2108 'idnumber' => new external_value(PARAM_RAW, 'category id number', VALUE_OPTIONAL),
2109 'parent' => new external_value(PARAM_INT, 'parent category id', VALUE_OPTIONAL),
2110 'description' => new external_value(PARAM_RAW, 'category description', VALUE_OPTIONAL),
2111 'descriptionformat' => new external_format_value('description', VALUE_DEFAULT),
2112 'theme' => new external_value(PARAM_THEME,
2113 'the category theme. This option must be enabled on moodle', VALUE_OPTIONAL),
2122 * Update categories
2124 * @param array $categories The list of categories to update
2125 * @return null
2126 * @since Moodle 2.3
2128 public static function update_categories($categories) {
2129 global $DB;
2131 // Validate parameters.
2132 $params = self::validate_parameters(self::update_categories_parameters(), array('categories' => $categories));
2134 $transaction = $DB->start_delegated_transaction();
2136 foreach ($params['categories'] as $cat) {
2137 $category = core_course_category::get($cat['id']);
2139 $categorycontext = context_coursecat::instance($cat['id']);
2140 self::validate_context($categorycontext);
2141 require_capability('moodle/category:manage', $categorycontext);
2143 // this will throw an exception if descriptionformat is not valid
2144 util::validate_format($cat['descriptionformat']);
2146 $category->update($cat);
2149 $transaction->allow_commit();
2153 * Returns description of method result value
2155 * @return \core_external\external_description
2156 * @since Moodle 2.3
2158 public static function update_categories_returns() {
2159 return null;
2163 * Returns description of method parameters
2165 * @return external_function_parameters
2166 * @since Moodle 2.3
2168 public static function delete_categories_parameters() {
2169 return new external_function_parameters(
2170 array(
2171 'categories' => new external_multiple_structure(
2172 new external_single_structure(
2173 array(
2174 'id' => new external_value(PARAM_INT, 'category id to delete'),
2175 'newparent' => new external_value(PARAM_INT,
2176 'the parent category to move the contents to, if specified', VALUE_OPTIONAL),
2177 'recursive' => new external_value(PARAM_BOOL, '1: recursively delete all contents inside this
2178 category, 0 (default): move contents to newparent or current parent category (except if parent is root)', VALUE_DEFAULT, 0)
2187 * Delete categories
2189 * @param array $categories A list of category ids
2190 * @return array
2191 * @since Moodle 2.3
2193 public static function delete_categories($categories) {
2194 global $CFG, $DB;
2195 require_once($CFG->dirroot . "/course/lib.php");
2197 // Validate parameters.
2198 $params = self::validate_parameters(self::delete_categories_parameters(), array('categories' => $categories));
2200 $transaction = $DB->start_delegated_transaction();
2202 foreach ($params['categories'] as $category) {
2203 $deletecat = core_course_category::get($category['id'], MUST_EXIST);
2204 $context = context_coursecat::instance($deletecat->id);
2205 require_capability('moodle/category:manage', $context);
2206 self::validate_context($context);
2207 self::validate_context(get_category_or_system_context($deletecat->parent));
2209 if ($category['recursive']) {
2210 // If recursive was specified, then we recursively delete the category's contents.
2211 if ($deletecat->can_delete_full()) {
2212 $deletecat->delete_full(false);
2213 } else {
2214 throw new moodle_exception('youcannotdeletecategory', '', '', $deletecat->get_formatted_name());
2216 } else {
2217 // In this situation, we don't delete the category's contents, we either move it to newparent or parent.
2218 // If the parent is the root, moving is not supported (because a course must always be inside a category).
2219 // We must move to an existing category.
2220 if (!empty($category['newparent'])) {
2221 $newparentcat = core_course_category::get($category['newparent']);
2222 } else {
2223 $newparentcat = core_course_category::get($deletecat->parent);
2226 // This operation is not allowed. We must move contents to an existing category.
2227 if (!$newparentcat->id) {
2228 throw new moodle_exception('movecatcontentstoroot');
2231 self::validate_context(context_coursecat::instance($newparentcat->id));
2232 if ($deletecat->can_move_content_to($newparentcat->id)) {
2233 $deletecat->delete_move($newparentcat->id, false);
2234 } else {
2235 throw new moodle_exception('youcannotdeletecategory', '', '', $deletecat->get_formatted_name());
2240 $transaction->allow_commit();
2244 * Returns description of method parameters
2246 * @return external_function_parameters
2247 * @since Moodle 2.3
2249 public static function delete_categories_returns() {
2250 return null;
2254 * Describes the parameters for delete_modules.
2256 * @return external_function_parameters
2257 * @since Moodle 2.5
2259 public static function delete_modules_parameters() {
2260 return new external_function_parameters (
2261 array(
2262 'cmids' => new external_multiple_structure(new external_value(PARAM_INT, 'course module ID',
2263 VALUE_REQUIRED, '', NULL_NOT_ALLOWED), 'Array of course module IDs'),
2269 * Deletes a list of provided module instances.
2271 * @param array $cmids the course module ids
2272 * @since Moodle 2.5
2274 public static function delete_modules($cmids) {
2275 global $CFG, $DB;
2277 // Require course file containing the course delete module function.
2278 require_once($CFG->dirroot . "/course/lib.php");
2280 // Clean the parameters.
2281 $params = self::validate_parameters(self::delete_modules_parameters(), array('cmids' => $cmids));
2283 // Keep track of the course ids we have performed a capability check on to avoid repeating.
2284 $arrcourseschecked = array();
2286 foreach ($params['cmids'] as $cmid) {
2287 // Get the course module.
2288 $cm = $DB->get_record('course_modules', array('id' => $cmid), '*', MUST_EXIST);
2290 // Check if we have not yet confirmed they have permission in this course.
2291 if (!in_array($cm->course, $arrcourseschecked)) {
2292 // Ensure the current user has required permission in this course.
2293 $context = context_course::instance($cm->course);
2294 self::validate_context($context);
2295 // Add to the array.
2296 $arrcourseschecked[] = $cm->course;
2299 // Ensure they can delete this module.
2300 $modcontext = context_module::instance($cm->id);
2301 require_capability('moodle/course:manageactivities', $modcontext);
2303 // Delete the module.
2304 course_delete_module($cm->id);
2309 * Describes the delete_modules return value.
2311 * @return external_single_structure
2312 * @since Moodle 2.5
2314 public static function delete_modules_returns() {
2315 return null;
2319 * Returns description of method parameters
2321 * @return external_function_parameters
2322 * @since Moodle 2.9
2324 public static function view_course_parameters() {
2325 return new external_function_parameters(
2326 array(
2327 'courseid' => new external_value(PARAM_INT, 'id of the course'),
2328 'sectionnumber' => new external_value(PARAM_INT, 'section number', VALUE_DEFAULT, 0)
2334 * Trigger the course viewed event.
2336 * @param int $courseid id of course
2337 * @param int $sectionnumber sectionnumber (0, 1, 2...)
2338 * @return array of warnings and status result
2339 * @since Moodle 2.9
2340 * @throws moodle_exception
2342 public static function view_course($courseid, $sectionnumber = 0) {
2343 global $CFG;
2344 require_once($CFG->dirroot . "/course/lib.php");
2346 $params = self::validate_parameters(self::view_course_parameters(),
2347 array(
2348 'courseid' => $courseid,
2349 'sectionnumber' => $sectionnumber
2352 $warnings = array();
2354 $course = get_course($params['courseid']);
2355 $context = context_course::instance($course->id);
2356 self::validate_context($context);
2358 if (!empty($params['sectionnumber'])) {
2360 // Get section details and check it exists.
2361 $modinfo = get_fast_modinfo($course);
2362 $coursesection = $modinfo->get_section_info($params['sectionnumber'], MUST_EXIST);
2364 // Check user is allowed to see it.
2365 if (!$coursesection->uservisible) {
2366 require_capability('moodle/course:viewhiddensections', $context);
2370 course_view($context, $params['sectionnumber']);
2372 $result = array();
2373 $result['status'] = true;
2374 $result['warnings'] = $warnings;
2375 return $result;
2379 * Returns description of method result value
2381 * @return \core_external\external_description
2382 * @since Moodle 2.9
2384 public static function view_course_returns() {
2385 return new external_single_structure(
2386 array(
2387 'status' => new external_value(PARAM_BOOL, 'status: true if success'),
2388 'warnings' => new external_warnings()
2394 * Returns description of method parameters
2396 * @return external_function_parameters
2397 * @since Moodle 3.0
2399 public static function search_courses_parameters() {
2400 return new external_function_parameters(
2401 array(
2402 'criterianame' => new external_value(PARAM_ALPHA, 'criteria name
2403 (search, modulelist (only admins), blocklist (only admins), tagid)'),
2404 'criteriavalue' => new external_value(PARAM_RAW, 'criteria value'),
2405 'page' => new external_value(PARAM_INT, 'page number (0 based)', VALUE_DEFAULT, 0),
2406 'perpage' => new external_value(PARAM_INT, 'items per page', VALUE_DEFAULT, 0),
2407 'requiredcapabilities' => new external_multiple_structure(
2408 new external_value(PARAM_CAPABILITY, 'Capability string used to filter courses by permission'),
2409 'Optional list of required capabilities (used to filter the list)', VALUE_DEFAULT, array()
2411 'limittoenrolled' => new external_value(PARAM_BOOL, 'limit to enrolled courses', VALUE_DEFAULT, 0),
2412 'onlywithcompletion' => new external_value(PARAM_BOOL, 'limit to courses where completion is enabled',
2413 VALUE_DEFAULT, 0),
2419 * Return the course information that is public (visible by every one)
2421 * @param core_course_list_element $course course in list object
2422 * @param stdClass $coursecontext course context object
2423 * @return array the course information
2424 * @since Moodle 3.2
2426 protected static function get_course_public_information(core_course_list_element $course, $coursecontext) {
2427 global $OUTPUT;
2429 static $categoriescache = array();
2431 // Category information.
2432 if (!array_key_exists($course->category, $categoriescache)) {
2433 $categoriescache[$course->category] = core_course_category::get($course->category, IGNORE_MISSING);
2435 $category = $categoriescache[$course->category];
2437 // Retrieve course overview used files.
2438 $files = array();
2439 foreach ($course->get_course_overviewfiles() as $file) {
2440 $fileurl = moodle_url::make_webservice_pluginfile_url($file->get_contextid(), $file->get_component(),
2441 $file->get_filearea(), null, $file->get_filepath(),
2442 $file->get_filename())->out(false);
2443 $files[] = array(
2444 'filename' => $file->get_filename(),
2445 'fileurl' => $fileurl,
2446 'filesize' => $file->get_filesize(),
2447 'filepath' => $file->get_filepath(),
2448 'mimetype' => $file->get_mimetype(),
2449 'timemodified' => $file->get_timemodified(),
2453 // Retrieve the course contacts,
2454 // we need here the users fullname since if we are not enrolled can be difficult to obtain them via other Web Services.
2455 $coursecontacts = array();
2456 foreach ($course->get_course_contacts() as $contact) {
2457 $coursecontacts[] = array(
2458 'id' => $contact['user']->id,
2459 'fullname' => $contact['username'],
2460 'roles' => array_map(function($role){
2461 return array('id' => $role->id, 'name' => $role->displayname);
2462 }, $contact['roles']),
2463 'role' => array('id' => $contact['role']->id, 'name' => $contact['role']->displayname),
2464 'rolename' => $contact['rolename']
2468 // Allowed enrolment methods (maybe we can self-enrol).
2469 $enroltypes = array();
2470 $instances = enrol_get_instances($course->id, true);
2471 foreach ($instances as $instance) {
2472 $enroltypes[] = $instance->enrol;
2475 // Format summary.
2476 list($summary, $summaryformat) =
2477 \core_external\util::format_text($course->summary, $course->summaryformat, $coursecontext, 'course', 'summary', null);
2479 $categoryname = '';
2480 if (!empty($category)) {
2481 $categoryname = \core_external\util::format_string($category->name, $category->get_context());
2484 $displayname = get_course_display_name_for_list($course);
2485 $coursereturns = array();
2486 $coursereturns['id'] = $course->id;
2487 $coursereturns['fullname'] = \core_external\util::format_string($course->fullname, $coursecontext);
2488 $coursereturns['displayname'] = \core_external\util::format_string($displayname, $coursecontext);
2489 $coursereturns['shortname'] = \core_external\util::format_string($course->shortname, $coursecontext);
2490 $coursereturns['categoryid'] = $course->category;
2491 $coursereturns['categoryname'] = $categoryname;
2492 $coursereturns['summary'] = $summary;
2493 $coursereturns['summaryformat'] = $summaryformat;
2494 $coursereturns['summaryfiles'] = util::get_area_files($coursecontext->id, 'course', 'summary', false, false);
2495 $coursereturns['overviewfiles'] = $files;
2496 $coursereturns['contacts'] = $coursecontacts;
2497 $coursereturns['enrollmentmethods'] = $enroltypes;
2498 $coursereturns['sortorder'] = $course->sortorder;
2499 $coursereturns['showactivitydates'] = $course->showactivitydates;
2500 $coursereturns['showcompletionconditions'] = $course->showcompletionconditions;
2502 $handler = core_course\customfield\course_handler::create();
2503 if ($customfields = $handler->export_instance_data($course->id)) {
2504 $coursereturns['customfields'] = [];
2505 foreach ($customfields as $data) {
2506 $coursereturns['customfields'][] = [
2507 'type' => $data->get_type(),
2508 'value' => $data->get_value(),
2509 'valueraw' => $data->get_data_controller()->get_value(),
2510 'name' => $data->get_name(),
2511 'shortname' => $data->get_shortname()
2516 $courseimage = \core_course\external\course_summary_exporter::get_course_image($course);
2517 if (!$courseimage) {
2518 $courseimage = $OUTPUT->get_generated_url_for_course($coursecontext);
2520 $coursereturns['courseimage'] = $courseimage;
2522 return $coursereturns;
2526 * Search courses following the specified criteria.
2528 * @param string $criterianame Criteria name (search, modulelist (only admins), blocklist (only admins), tagid)
2529 * @param string $criteriavalue Criteria value
2530 * @param int $page Page number (for pagination)
2531 * @param int $perpage Items per page
2532 * @param array $requiredcapabilities Optional list of required capabilities (used to filter the list).
2533 * @param int $limittoenrolled Limit to only enrolled courses
2534 * @param int onlywithcompletion Limit to only courses where completion is enabled
2535 * @return array of course objects and warnings
2536 * @since Moodle 3.0
2537 * @throws moodle_exception
2539 public static function search_courses($criterianame,
2540 $criteriavalue,
2541 $page=0,
2542 $perpage=0,
2543 $requiredcapabilities=array(),
2544 $limittoenrolled=0,
2545 $onlywithcompletion=0) {
2546 global $CFG;
2548 $warnings = array();
2550 $parameters = array(
2551 'criterianame' => $criterianame,
2552 'criteriavalue' => $criteriavalue,
2553 'page' => $page,
2554 'perpage' => $perpage,
2555 'requiredcapabilities' => $requiredcapabilities,
2556 'limittoenrolled' => $limittoenrolled,
2557 'onlywithcompletion' => $onlywithcompletion
2559 $params = self::validate_parameters(self::search_courses_parameters(), $parameters);
2560 self::validate_context(context_system::instance());
2562 $allowedcriterianames = array('search', 'modulelist', 'blocklist', 'tagid');
2563 if (!in_array($params['criterianame'], $allowedcriterianames)) {
2564 throw new invalid_parameter_exception('Invalid value for criterianame parameter (value: '.$params['criterianame'].'),' .
2565 'allowed values are: '.implode(',', $allowedcriterianames));
2568 if ($params['criterianame'] == 'modulelist' or $params['criterianame'] == 'blocklist') {
2569 require_capability('moodle/site:config', context_system::instance());
2572 $paramtype = array(
2573 'search' => PARAM_RAW,
2574 'modulelist' => PARAM_PLUGIN,
2575 'blocklist' => PARAM_INT,
2576 'tagid' => PARAM_INT
2578 $params['criteriavalue'] = clean_param($params['criteriavalue'], $paramtype[$params['criterianame']]);
2580 // Prepare the search API options.
2581 $searchcriteria = array();
2582 $searchcriteria[$params['criterianame']] = $params['criteriavalue'];
2583 if ($params['onlywithcompletion']) {
2584 $searchcriteria['onlywithcompletion'] = true;
2587 $options = array();
2588 if ($params['perpage'] != 0) {
2589 $offset = $params['page'] * $params['perpage'];
2590 $options = array('offset' => $offset, 'limit' => $params['perpage']);
2593 // Search the courses.
2594 $courses = core_course_category::search_courses($searchcriteria, $options, $params['requiredcapabilities']);
2595 $totalcount = core_course_category::search_courses_count($searchcriteria, $options, $params['requiredcapabilities']);
2597 if (!empty($limittoenrolled)) {
2598 // Get the courses where the current user has access.
2599 $enrolled = enrol_get_my_courses(array('id', 'cacherev'));
2602 $finalcourses = array();
2603 $categoriescache = array();
2605 foreach ($courses as $course) {
2606 if (!empty($limittoenrolled)) {
2607 // Filter out not enrolled courses.
2608 if (!isset($enrolled[$course->id])) {
2609 $totalcount--;
2610 continue;
2614 $coursecontext = context_course::instance($course->id);
2616 $finalcourses[] = self::get_course_public_information($course, $coursecontext);
2619 return array(
2620 'total' => $totalcount,
2621 'courses' => $finalcourses,
2622 'warnings' => $warnings
2627 * Returns a course structure definition
2629 * @param boolean $onlypublicdata set to true, to retrieve only fields viewable by anyone when the course is visible
2630 * @return array the course structure
2631 * @since Moodle 3.2
2633 protected static function get_course_structure($onlypublicdata = true) {
2634 $coursestructure = array(
2635 'id' => new external_value(PARAM_INT, 'course id'),
2636 'fullname' => new external_value(PARAM_RAW, 'course full name'),
2637 'displayname' => new external_value(PARAM_RAW, 'course display name'),
2638 'shortname' => new external_value(PARAM_RAW, 'course short name'),
2639 'courseimage' => new external_value(PARAM_URL, 'Course image', VALUE_OPTIONAL),
2640 'categoryid' => new external_value(PARAM_INT, 'category id'),
2641 'categoryname' => new external_value(PARAM_RAW, 'category name'),
2642 'sortorder' => new external_value(PARAM_INT, 'Sort order in the category', VALUE_OPTIONAL),
2643 'summary' => new external_value(PARAM_RAW, 'summary'),
2644 'summaryformat' => new external_format_value('summary'),
2645 'summaryfiles' => new external_files('summary files in the summary field', VALUE_OPTIONAL),
2646 'overviewfiles' => new external_files('additional overview files attached to this course'),
2647 'showactivitydates' => new external_value(PARAM_BOOL, 'Whether the activity dates are shown or not'),
2648 'showcompletionconditions' => new external_value(PARAM_BOOL,
2649 'Whether the activity completion conditions are shown or not'),
2650 'contacts' => new external_multiple_structure(
2651 new external_single_structure(
2652 array(
2653 'id' => new external_value(PARAM_INT, 'contact user id'),
2654 'fullname' => new external_value(PARAM_NOTAGS, 'contact user fullname'),
2657 'contact users'
2659 'enrollmentmethods' => new external_multiple_structure(
2660 new external_value(PARAM_PLUGIN, 'enrollment method'),
2661 'enrollment methods list'
2663 'customfields' => new external_multiple_structure(
2664 new external_single_structure(
2665 array(
2666 'name' => new external_value(PARAM_RAW, 'The name of the custom field'),
2667 'shortname' => new external_value(PARAM_RAW,
2668 'The shortname of the custom field - to be able to build the field class in the code'),
2669 'type' => new external_value(PARAM_ALPHANUMEXT,
2670 'The type of the custom field - text field, checkbox...'),
2671 'valueraw' => new external_value(PARAM_RAW, 'The raw value of the custom field'),
2672 'value' => new external_value(PARAM_RAW, 'The value of the custom field'),
2674 ), 'Custom fields', VALUE_OPTIONAL),
2677 if (!$onlypublicdata) {
2678 $extra = array(
2679 'idnumber' => new external_value(PARAM_RAW, 'Id number', VALUE_OPTIONAL),
2680 'format' => new external_value(PARAM_PLUGIN, 'Course format: weeks, topics, social, site,..', VALUE_OPTIONAL),
2681 'showgrades' => new external_value(PARAM_INT, '1 if grades are shown, otherwise 0', VALUE_OPTIONAL),
2682 'newsitems' => new external_value(PARAM_INT, 'Number of recent items appearing on the course page', VALUE_OPTIONAL),
2683 'startdate' => new external_value(PARAM_INT, 'Timestamp when the course start', VALUE_OPTIONAL),
2684 'enddate' => new external_value(PARAM_INT, 'Timestamp when the course end', VALUE_OPTIONAL),
2685 'maxbytes' => new external_value(PARAM_INT, 'Largest size of file that can be uploaded into', VALUE_OPTIONAL),
2686 'showreports' => new external_value(PARAM_INT, 'Are activity report shown (yes = 1, no =0)', VALUE_OPTIONAL),
2687 'visible' => new external_value(PARAM_INT, '1: available to student, 0:not available', VALUE_OPTIONAL),
2688 'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible', VALUE_OPTIONAL),
2689 'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no', VALUE_OPTIONAL),
2690 'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id', VALUE_OPTIONAL),
2691 'enablecompletion' => new external_value(PARAM_INT, 'Completion enabled? 1: yes 0: no', VALUE_OPTIONAL),
2692 'completionnotify' => new external_value(PARAM_INT, '1: yes 0: no', VALUE_OPTIONAL),
2693 'lang' => new external_value(PARAM_SAFEDIR, 'Forced course language', VALUE_OPTIONAL),
2694 'theme' => new external_value(PARAM_PLUGIN, 'Fame of the forced theme', VALUE_OPTIONAL),
2695 'marker' => new external_value(PARAM_INT, 'Current course marker', VALUE_OPTIONAL),
2696 'legacyfiles' => new external_value(PARAM_INT, 'If legacy files are enabled', VALUE_OPTIONAL),
2697 'calendartype' => new external_value(PARAM_PLUGIN, 'Calendar type', VALUE_OPTIONAL),
2698 'timecreated' => new external_value(PARAM_INT, 'Time when the course was created', VALUE_OPTIONAL),
2699 'timemodified' => new external_value(PARAM_INT, 'Last time the course was updated', VALUE_OPTIONAL),
2700 'requested' => new external_value(PARAM_INT, 'If is a requested course', VALUE_OPTIONAL),
2701 'cacherev' => new external_value(PARAM_INT, 'Cache revision number', VALUE_OPTIONAL),
2702 'filters' => new external_multiple_structure(
2703 new external_single_structure(
2704 array(
2705 'filter' => new external_value(PARAM_PLUGIN, 'Filter plugin name'),
2706 'localstate' => new external_value(PARAM_INT, 'Filter state: 1 for on, -1 for off, 0 if inherit'),
2707 'inheritedstate' => new external_value(PARAM_INT, '1 or 0 to use when localstate is set to inherit'),
2710 'Course filters', VALUE_OPTIONAL
2712 'courseformatoptions' => new external_multiple_structure(
2713 new external_single_structure(
2714 array(
2715 'name' => new external_value(PARAM_RAW, 'Course format option name.'),
2716 'value' => new external_value(PARAM_RAW, 'Course format option value.'),
2719 'Additional options for particular course format.', VALUE_OPTIONAL
2722 $coursestructure = array_merge($coursestructure, $extra);
2724 return new external_single_structure($coursestructure);
2728 * Returns description of method result value
2730 * @return \core_external\external_description
2731 * @since Moodle 3.0
2733 public static function search_courses_returns() {
2734 return new external_single_structure(
2735 array(
2736 'total' => new external_value(PARAM_INT, 'total course count'),
2737 'courses' => new external_multiple_structure(self::get_course_structure(), 'course'),
2738 'warnings' => new external_warnings()
2744 * Returns description of method parameters
2746 * @return external_function_parameters
2747 * @since Moodle 3.0
2749 public static function get_course_module_parameters() {
2750 return new external_function_parameters(
2751 array(
2752 'cmid' => new external_value(PARAM_INT, 'The course module id')
2758 * Return information about a course module.
2760 * @param int $cmid the course module id
2761 * @return array of warnings and the course module
2762 * @since Moodle 3.0
2763 * @throws moodle_exception
2765 public static function get_course_module($cmid) {
2766 global $CFG, $DB;
2768 $params = self::validate_parameters(self::get_course_module_parameters(), array('cmid' => $cmid));
2769 $warnings = array();
2771 $cm = get_coursemodule_from_id(null, $params['cmid'], 0, true, MUST_EXIST);
2772 $context = context_module::instance($cm->id);
2773 self::validate_context($context);
2775 // If the user has permissions to manage the activity, return all the information.
2776 if (has_capability('moodle/course:manageactivities', $context)) {
2777 require_once($CFG->dirroot . '/course/modlib.php');
2778 require_once($CFG->libdir . '/gradelib.php');
2780 $info = $cm;
2781 // Get the extra information: grade, advanced grading and outcomes data.
2782 $course = get_course($cm->course);
2783 list($newcm, $newcontext, $module, $extrainfo, $cw) = get_moduleinfo_data($cm, $course);
2784 // Grades.
2785 $gradeinfo = array('grade', 'gradepass', 'gradecat');
2786 foreach ($gradeinfo as $gfield) {
2787 if (isset($extrainfo->{$gfield})) {
2788 $info->{$gfield} = $extrainfo->{$gfield};
2791 if (isset($extrainfo->grade) and $extrainfo->grade < 0) {
2792 $info->scale = $DB->get_field('scale', 'scale', array('id' => abs($extrainfo->grade)));
2794 // Advanced grading.
2795 if (isset($extrainfo->_advancedgradingdata)) {
2796 $info->advancedgrading = array();
2797 foreach ($extrainfo as $key => $val) {
2798 if (strpos($key, 'advancedgradingmethod_') === 0) {
2799 $info->advancedgrading[] = array(
2800 'area' => str_replace('advancedgradingmethod_', '', $key),
2801 'method' => $val
2806 // Outcomes.
2807 foreach ($extrainfo as $key => $val) {
2808 if (strpos($key, 'outcome_') === 0) {
2809 if (!isset($info->outcomes)) {
2810 $info->outcomes = array();
2812 $id = str_replace('outcome_', '', $key);
2813 $outcome = grade_outcome::fetch(array('id' => $id));
2814 $scaleitems = $outcome->load_scale();
2815 $info->outcomes[] = array(
2816 'id' => $id,
2817 'name' => \core_external\util::format_string($outcome->get_name(), $context),
2818 'scale' => $scaleitems->scale
2822 } else {
2823 // Return information is safe to show to any user.
2824 $info = new stdClass();
2825 $info->id = $cm->id;
2826 $info->course = $cm->course;
2827 $info->module = $cm->module;
2828 $info->modname = $cm->modname;
2829 $info->instance = $cm->instance;
2830 $info->section = $cm->section;
2831 $info->sectionnum = $cm->sectionnum;
2832 $info->groupmode = $cm->groupmode;
2833 $info->groupingid = $cm->groupingid;
2834 $info->completion = $cm->completion;
2835 $info->downloadcontent = $cm->downloadcontent;
2837 // Format name.
2838 $info->name = \core_external\util::format_string($cm->name, $context);
2839 $result = array();
2840 $result['cm'] = $info;
2841 $result['warnings'] = $warnings;
2842 return $result;
2846 * Returns description of method result value
2848 * @return \core_external\external_description
2849 * @since Moodle 3.0
2851 public static function get_course_module_returns() {
2852 return new external_single_structure(
2853 array(
2854 'cm' => new external_single_structure(
2855 array(
2856 'id' => new external_value(PARAM_INT, 'The course module id'),
2857 'course' => new external_value(PARAM_INT, 'The course id'),
2858 'module' => new external_value(PARAM_INT, 'The module type id'),
2859 'name' => new external_value(PARAM_RAW, 'The activity name'),
2860 'modname' => new external_value(PARAM_COMPONENT, 'The module component name (forum, assign, etc..)'),
2861 'instance' => new external_value(PARAM_INT, 'The activity instance id'),
2862 'section' => new external_value(PARAM_INT, 'The module section id'),
2863 'sectionnum' => new external_value(PARAM_INT, 'The module section number'),
2864 'groupmode' => new external_value(PARAM_INT, 'Group mode'),
2865 'groupingid' => new external_value(PARAM_INT, 'Grouping id'),
2866 'completion' => new external_value(PARAM_INT, 'If completion is enabled'),
2867 'idnumber' => new external_value(PARAM_RAW, 'Module id number', VALUE_OPTIONAL),
2868 'added' => new external_value(PARAM_INT, 'Time added', VALUE_OPTIONAL),
2869 'score' => new external_value(PARAM_INT, 'Score', VALUE_OPTIONAL),
2870 'indent' => new external_value(PARAM_INT, 'Indentation', VALUE_OPTIONAL),
2871 'visible' => new external_value(PARAM_INT, 'If visible', VALUE_OPTIONAL),
2872 'visibleoncoursepage' => new external_value(PARAM_INT, 'If visible on course page', VALUE_OPTIONAL),
2873 'visibleold' => new external_value(PARAM_INT, 'Visible old', VALUE_OPTIONAL),
2874 'completiongradeitemnumber' => new external_value(PARAM_INT, 'Completion grade item', VALUE_OPTIONAL),
2875 'completionpassgrade' => new external_value(PARAM_INT, 'Completion pass grade setting', VALUE_OPTIONAL),
2876 'completionview' => new external_value(PARAM_INT, 'Completion view setting', VALUE_OPTIONAL),
2877 'completionexpected' => new external_value(PARAM_INT, 'Completion time expected', VALUE_OPTIONAL),
2878 'showdescription' => new external_value(PARAM_INT, 'If the description is showed', VALUE_OPTIONAL),
2879 'downloadcontent' => new external_value(PARAM_INT, 'The download content value', VALUE_OPTIONAL),
2880 'availability' => new external_value(PARAM_RAW, 'Availability settings', VALUE_OPTIONAL),
2881 'grade' => new external_value(PARAM_FLOAT, 'Grade (max value or scale id)', VALUE_OPTIONAL),
2882 'scale' => new external_value(PARAM_TEXT, 'Scale items (if used)', VALUE_OPTIONAL),
2883 'gradepass' => new external_value(PARAM_RAW, 'Grade to pass (float)', VALUE_OPTIONAL),
2884 'gradecat' => new external_value(PARAM_INT, 'Grade category', VALUE_OPTIONAL),
2885 'advancedgrading' => new external_multiple_structure(
2886 new external_single_structure(
2887 array(
2888 'area' => new external_value(PARAM_AREA, 'Gradable area name'),
2889 'method' => new external_value(PARAM_COMPONENT, 'Grading method'),
2892 'Advanced grading settings', VALUE_OPTIONAL
2894 'outcomes' => new external_multiple_structure(
2895 new external_single_structure(
2896 array(
2897 'id' => new external_value(PARAM_ALPHANUMEXT, 'Outcome id'),
2898 'name' => new external_value(PARAM_RAW, 'Outcome full name'),
2899 'scale' => new external_value(PARAM_TEXT, 'Scale items')
2902 'Outcomes information', VALUE_OPTIONAL
2906 'warnings' => new external_warnings()
2912 * Returns description of method parameters
2914 * @return external_function_parameters
2915 * @since Moodle 3.0
2917 public static function get_course_module_by_instance_parameters() {
2918 return new external_function_parameters(
2919 array(
2920 'module' => new external_value(PARAM_COMPONENT, 'The module name'),
2921 'instance' => new external_value(PARAM_INT, 'The module instance id')
2927 * Return information about a course module.
2929 * @param string $module the module name
2930 * @param int $instance the activity instance id
2931 * @return array of warnings and the course module
2932 * @since Moodle 3.0
2933 * @throws moodle_exception
2935 public static function get_course_module_by_instance($module, $instance) {
2937 $params = self::validate_parameters(self::get_course_module_by_instance_parameters(),
2938 array(
2939 'module' => $module,
2940 'instance' => $instance,
2943 $warnings = array();
2944 $cm = get_coursemodule_from_instance($params['module'], $params['instance'], 0, false, MUST_EXIST);
2946 return self::get_course_module($cm->id);
2950 * Returns description of method result value
2952 * @return \core_external\external_description
2953 * @since Moodle 3.0
2955 public static function get_course_module_by_instance_returns() {
2956 return self::get_course_module_returns();
2960 * Returns description of method parameters
2962 * @return external_function_parameters
2963 * @since Moodle 3.2
2965 public static function get_user_navigation_options_parameters() {
2966 return new external_function_parameters(
2967 array(
2968 'courseids' => new external_multiple_structure(new external_value(PARAM_INT, 'Course id.')),
2974 * Return a list of navigation options in a set of courses that are avaialable or not for the current user.
2976 * @param array $courseids a list of course ids
2977 * @return array of warnings and the options availability
2978 * @since Moodle 3.2
2979 * @throws moodle_exception
2981 public static function get_user_navigation_options($courseids) {
2982 global $CFG;
2983 require_once($CFG->dirroot . '/course/lib.php');
2985 // Parameter validation.
2986 $params = self::validate_parameters(self::get_user_navigation_options_parameters(), array('courseids' => $courseids));
2987 $courseoptions = array();
2989 list($courses, $warnings) = util::validate_courses($params['courseids'], array(), true);
2991 if (!empty($courses)) {
2992 foreach ($courses as $course) {
2993 // Fix the context for the frontpage.
2994 if ($course->id == SITEID) {
2995 $course->context = context_system::instance();
2997 $navoptions = course_get_user_navigation_options($course->context, $course);
2998 $options = array();
2999 foreach ($navoptions as $name => $available) {
3000 $options[] = array(
3001 'name' => $name,
3002 'available' => $available,
3006 $courseoptions[] = array(
3007 'id' => $course->id,
3008 'options' => $options
3013 $result = array(
3014 'courses' => $courseoptions,
3015 'warnings' => $warnings
3017 return $result;
3021 * Returns description of method result value
3023 * @return \core_external\external_description
3024 * @since Moodle 3.2
3026 public static function get_user_navigation_options_returns() {
3027 return new external_single_structure(
3028 array(
3029 'courses' => new external_multiple_structure(
3030 new external_single_structure(
3031 array(
3032 'id' => new external_value(PARAM_INT, 'Course id'),
3033 'options' => new external_multiple_structure(
3034 new external_single_structure(
3035 array(
3036 'name' => new external_value(PARAM_ALPHANUMEXT, 'Option name'),
3037 'available' => new external_value(PARAM_BOOL, 'Whether the option is available or not'),
3042 ), 'List of courses'
3044 'warnings' => new external_warnings()
3050 * Returns description of method parameters
3052 * @return external_function_parameters
3053 * @since Moodle 3.2
3055 public static function get_user_administration_options_parameters() {
3056 return new external_function_parameters(
3057 array(
3058 'courseids' => new external_multiple_structure(new external_value(PARAM_INT, 'Course id.')),
3064 * Return a list of administration options in a set of courses that are available or not for the current user.
3066 * @param array $courseids a list of course ids
3067 * @return array of warnings and the options availability
3068 * @since Moodle 3.2
3069 * @throws moodle_exception
3071 public static function get_user_administration_options($courseids) {
3072 global $CFG;
3073 require_once($CFG->dirroot . '/course/lib.php');
3075 // Parameter validation.
3076 $params = self::validate_parameters(self::get_user_administration_options_parameters(), array('courseids' => $courseids));
3077 $courseoptions = array();
3079 list($courses, $warnings) = util::validate_courses($params['courseids'], array(), true);
3081 if (!empty($courses)) {
3082 foreach ($courses as $course) {
3083 $adminoptions = course_get_user_administration_options($course, $course->context);
3084 $options = array();
3085 foreach ($adminoptions as $name => $available) {
3086 $options[] = array(
3087 'name' => $name,
3088 'available' => $available,
3092 $courseoptions[] = array(
3093 'id' => $course->id,
3094 'options' => $options
3099 $result = array(
3100 'courses' => $courseoptions,
3101 'warnings' => $warnings
3103 return $result;
3107 * Returns description of method result value
3109 * @return \core_external\external_description
3110 * @since Moodle 3.2
3112 public static function get_user_administration_options_returns() {
3113 return self::get_user_navigation_options_returns();
3117 * Returns description of method parameters
3119 * @return external_function_parameters
3120 * @since Moodle 3.2
3122 public static function get_courses_by_field_parameters() {
3123 return new external_function_parameters(
3124 array(
3125 'field' => new external_value(PARAM_ALPHA, 'The field to search can be left empty for all courses or:
3126 id: course id
3127 ids: comma separated course ids
3128 shortname: course short name
3129 idnumber: course id number
3130 category: category id the course belongs to
3131 ', VALUE_DEFAULT, ''),
3132 'value' => new external_value(PARAM_RAW, 'The value to match', VALUE_DEFAULT, '')
3139 * Get courses matching a specific field (id/s, shortname, idnumber, category)
3141 * @param string $field field name to search, or empty for all courses
3142 * @param string $value value to search
3143 * @return array list of courses and warnings
3144 * @throws invalid_parameter_exception
3145 * @since Moodle 3.2
3147 public static function get_courses_by_field($field = '', $value = '') {
3148 global $DB, $CFG;
3149 require_once($CFG->dirroot . '/course/lib.php');
3150 require_once($CFG->libdir . '/filterlib.php');
3152 $params = self::validate_parameters(self::get_courses_by_field_parameters(),
3153 array(
3154 'field' => $field,
3155 'value' => $value,
3158 $warnings = array();
3160 if (empty($params['field'])) {
3161 $courses = $DB->get_records('course', null, 'id ASC');
3162 } else {
3163 switch ($params['field']) {
3164 case 'id':
3165 case 'category':
3166 $value = clean_param($params['value'], PARAM_INT);
3167 break;
3168 case 'ids':
3169 $value = clean_param($params['value'], PARAM_SEQUENCE);
3170 break;
3171 case 'shortname':
3172 $value = clean_param($params['value'], PARAM_TEXT);
3173 break;
3174 case 'idnumber':
3175 $value = clean_param($params['value'], PARAM_RAW);
3176 break;
3177 default:
3178 throw new invalid_parameter_exception('Invalid field name');
3181 if ($params['field'] === 'ids') {
3182 // Preload categories to avoid loading one at a time.
3183 $courseids = explode(',', $value);
3184 list ($listsql, $listparams) = $DB->get_in_or_equal($courseids);
3185 $categoryids = $DB->get_fieldset_sql("
3186 SELECT DISTINCT cc.id
3187 FROM {course} c
3188 JOIN {course_categories} cc ON cc.id = c.category
3189 WHERE c.id $listsql", $listparams);
3190 core_course_category::get_many($categoryids);
3192 // Load and validate all courses. This is called because it loads the courses
3193 // more efficiently.
3194 list ($courses, $warnings) = util::validate_courses($courseids, [],
3195 false, true);
3196 } else {
3197 $courses = $DB->get_records('course', array($params['field'] => $value), 'id ASC');
3201 $coursesdata = array();
3202 foreach ($courses as $course) {
3203 $context = context_course::instance($course->id);
3204 $canupdatecourse = has_capability('moodle/course:update', $context);
3205 $canviewhiddencourses = has_capability('moodle/course:viewhiddencourses', $context);
3207 // Check if the course is visible in the site for the user.
3208 if (!$course->visible and !$canviewhiddencourses and !$canupdatecourse) {
3209 continue;
3211 // Get the public course information, even if we are not enrolled.
3212 $courseinlist = new core_course_list_element($course);
3214 // Now, check if we have access to the course, unless it was already checked.
3215 try {
3216 if (empty($course->contextvalidated)) {
3217 self::validate_context($context);
3219 } catch (Exception $e) {
3220 // User can not access the course, check if they can see the public information about the course and return it.
3221 if (core_course_category::can_view_course_info($course)) {
3222 $coursesdata[$course->id] = self::get_course_public_information($courseinlist, $context);
3224 continue;
3226 $coursesdata[$course->id] = self::get_course_public_information($courseinlist, $context);
3227 // Return information for any user that can access the course.
3228 $coursefields = array('format', 'showgrades', 'newsitems', 'startdate', 'enddate', 'maxbytes', 'showreports', 'visible',
3229 'groupmode', 'groupmodeforce', 'defaultgroupingid', 'enablecompletion', 'completionnotify', 'lang', 'theme',
3230 'marker');
3232 // Course filters.
3233 $coursesdata[$course->id]['filters'] = filter_get_available_in_context($context);
3235 // Information for managers only.
3236 if ($canupdatecourse) {
3237 $managerfields = array('idnumber', 'legacyfiles', 'calendartype', 'timecreated', 'timemodified', 'requested',
3238 'cacherev');
3239 $coursefields = array_merge($coursefields, $managerfields);
3242 // Populate fields.
3243 foreach ($coursefields as $field) {
3244 $coursesdata[$course->id][$field] = $course->{$field};
3247 // Clean lang and auth fields for external functions (it may content uninstalled themes or language packs).
3248 if (isset($coursesdata[$course->id]['theme'])) {
3249 $coursesdata[$course->id]['theme'] = clean_param($coursesdata[$course->id]['theme'], PARAM_THEME);
3251 if (isset($coursesdata[$course->id]['lang'])) {
3252 $coursesdata[$course->id]['lang'] = clean_param($coursesdata[$course->id]['lang'], PARAM_LANG);
3255 $courseformatoptions = course_get_format($course)->get_config_for_external();
3256 foreach ($courseformatoptions as $key => $value) {
3257 $coursesdata[$course->id]['courseformatoptions'][] = array(
3258 'name' => $key,
3259 'value' => $value
3264 return array(
3265 'courses' => $coursesdata,
3266 'warnings' => $warnings
3271 * Returns description of method result value
3273 * @return \core_external\external_description
3274 * @since Moodle 3.2
3276 public static function get_courses_by_field_returns() {
3277 // Course structure, including not only public viewable fields.
3278 return new external_single_structure(
3279 array(
3280 'courses' => new external_multiple_structure(self::get_course_structure(false), 'Course'),
3281 'warnings' => new external_warnings()
3287 * Returns description of method parameters
3289 * @return external_function_parameters
3290 * @since Moodle 3.2
3292 public static function check_updates_parameters() {
3293 return new external_function_parameters(
3294 array(
3295 'courseid' => new external_value(PARAM_INT, 'Course id to check'),
3296 'tocheck' => new external_multiple_structure(
3297 new external_single_structure(
3298 array(
3299 'contextlevel' => new external_value(PARAM_ALPHA, 'The context level for the file location.
3300 Only module supported right now.'),
3301 'id' => new external_value(PARAM_INT, 'Context instance id'),
3302 'since' => new external_value(PARAM_INT, 'Check updates since this time stamp'),
3305 'Instances to check'
3307 'filter' => new external_multiple_structure(
3308 new external_value(PARAM_ALPHANUM, 'Area name: configuration, fileareas, completion, ratings, comments,
3309 gradeitems, outcomes'),
3310 'Check only for updates in these areas', VALUE_DEFAULT, array()
3317 * Check if there is updates affecting the user for the given course and contexts.
3318 * Right now only modules are supported.
3319 * This WS calls mod_check_updates_since for each module to check if there is any update the user should we aware of.
3321 * @param int $courseid the list of modules to check
3322 * @param array $tocheck the list of modules to check
3323 * @param array $filter check only for updates in these areas
3324 * @return array list of updates and warnings
3325 * @throws moodle_exception
3326 * @since Moodle 3.2
3328 public static function check_updates($courseid, $tocheck, $filter = array()) {
3329 global $CFG, $DB;
3330 require_once($CFG->dirroot . "/course/lib.php");
3332 $params = self::validate_parameters(
3333 self::check_updates_parameters(),
3334 array(
3335 'courseid' => $courseid,
3336 'tocheck' => $tocheck,
3337 'filter' => $filter,
3341 $course = get_course($params['courseid']);
3342 $context = context_course::instance($course->id);
3343 self::validate_context($context);
3345 list($instances, $warnings) = course_check_updates($course, $params['tocheck'], $filter);
3347 $instancesformatted = array();
3348 foreach ($instances as $instance) {
3349 $updates = array();
3350 foreach ($instance['updates'] as $name => $data) {
3351 if (empty($data->updated)) {
3352 continue;
3354 $updatedata = array(
3355 'name' => $name,
3357 if (!empty($data->timeupdated)) {
3358 $updatedata['timeupdated'] = $data->timeupdated;
3360 if (!empty($data->itemids)) {
3361 $updatedata['itemids'] = $data->itemids;
3363 $updates[] = $updatedata;
3365 if (!empty($updates)) {
3366 $instancesformatted[] = array(
3367 'contextlevel' => $instance['contextlevel'],
3368 'id' => $instance['id'],
3369 'updates' => $updates
3374 return array(
3375 'instances' => $instancesformatted,
3376 'warnings' => $warnings
3381 * Returns description of method result value
3383 * @return \core_external\external_description
3384 * @since Moodle 3.2
3386 public static function check_updates_returns() {
3387 return new external_single_structure(
3388 array(
3389 'instances' => new external_multiple_structure(
3390 new external_single_structure(
3391 array(
3392 'contextlevel' => new external_value(PARAM_ALPHA, 'The context level'),
3393 'id' => new external_value(PARAM_INT, 'Instance id'),
3394 'updates' => new external_multiple_structure(
3395 new external_single_structure(
3396 array(
3397 'name' => new external_value(PARAM_ALPHANUMEXT, 'Name of the area updated.'),
3398 'timeupdated' => new external_value(PARAM_INT, 'Last time was updated', VALUE_OPTIONAL),
3399 'itemids' => new external_multiple_structure(
3400 new external_value(PARAM_INT, 'Instance id'),
3401 'The ids of the items updated',
3402 VALUE_OPTIONAL
3410 'warnings' => new external_warnings()
3416 * Returns description of method parameters
3418 * @return external_function_parameters
3419 * @since Moodle 3.3
3421 public static function get_updates_since_parameters() {
3422 return new external_function_parameters(
3423 array(
3424 'courseid' => new external_value(PARAM_INT, 'Course id to check'),
3425 'since' => new external_value(PARAM_INT, 'Check updates since this time stamp'),
3426 'filter' => new external_multiple_structure(
3427 new external_value(PARAM_ALPHANUM, 'Area name: configuration, fileareas, completion, ratings, comments,
3428 gradeitems, outcomes'),
3429 'Check only for updates in these areas', VALUE_DEFAULT, array()
3436 * Check if there are updates affecting the user for the given course since the given time stamp.
3438 * This function is a wrapper of self::check_updates for retrieving all the updates since a given time for all the activities.
3440 * @param int $courseid the list of modules to check
3441 * @param int $since check updates since this time stamp
3442 * @param array $filter check only for updates in these areas
3443 * @return array list of updates and warnings
3444 * @throws moodle_exception
3445 * @since Moodle 3.3
3447 public static function get_updates_since($courseid, $since, $filter = array()) {
3448 global $CFG, $DB;
3450 $params = self::validate_parameters(
3451 self::get_updates_since_parameters(),
3452 array(
3453 'courseid' => $courseid,
3454 'since' => $since,
3455 'filter' => $filter,
3459 $course = get_course($params['courseid']);
3460 $modinfo = get_fast_modinfo($course);
3461 $tocheck = array();
3463 // Retrieve all the visible course modules for the current user.
3464 $cms = $modinfo->get_cms();
3465 foreach ($cms as $cm) {
3466 if (!$cm->uservisible) {
3467 continue;
3469 $tocheck[] = array(
3470 'id' => $cm->id,
3471 'contextlevel' => 'module',
3472 'since' => $params['since'],
3476 return self::check_updates($course->id, $tocheck, $params['filter']);
3480 * Returns description of method result value
3482 * @return \core_external\external_description
3483 * @since Moodle 3.3
3485 public static function get_updates_since_returns() {
3486 return self::check_updates_returns();
3490 * Parameters for function edit_module()
3492 * @since Moodle 3.3
3493 * @return external_function_parameters
3495 public static function edit_module_parameters() {
3496 return new external_function_parameters(
3497 array(
3498 'action' => new external_value(PARAM_ALPHA,
3499 'action: hide, show, stealth, duplicate, delete, moveleft, moveright, group...', VALUE_REQUIRED),
3500 'id' => new external_value(PARAM_INT, 'course module id', VALUE_REQUIRED),
3501 'sectionreturn' => new external_value(PARAM_INT, 'section to return to', VALUE_DEFAULT, null),
3506 * Performs one of the edit module actions and return new html for AJAX
3508 * Returns html to replace the current module html with, for example:
3509 * - empty string for "delete" action,
3510 * - two modules html for "duplicate" action
3511 * - updated module html for everything else
3513 * Throws exception if operation is not permitted/possible
3515 * @since Moodle 3.3
3516 * @param string $action
3517 * @param int $id
3518 * @param null|int $sectionreturn
3519 * @return string
3521 public static function edit_module($action, $id, $sectionreturn = null) {
3522 global $PAGE, $DB;
3523 // Validate and normalize parameters.
3524 $params = self::validate_parameters(self::edit_module_parameters(),
3525 array('action' => $action, 'id' => $id, 'sectionreturn' => $sectionreturn));
3526 $action = $params['action'];
3527 $id = $params['id'];
3528 $sectionreturn = $params['sectionreturn'];
3530 // Set of permissions an editing user may have.
3531 $contextarray = [
3532 'moodle/course:update',
3533 'moodle/course:manageactivities',
3534 'moodle/course:activityvisibility',
3535 'moodle/course:sectionvisibility',
3536 'moodle/course:movesections',
3537 'moodle/course:setcurrentsection',
3539 $PAGE->set_other_editing_capability($contextarray);
3541 list($course, $cm) = get_course_and_cm_from_cmid($id);
3542 $modcontext = context_module::instance($cm->id);
3543 $coursecontext = context_course::instance($course->id);
3544 self::validate_context($modcontext);
3545 $format = course_get_format($course);
3546 if ($sectionreturn) {
3547 $format->set_section_number($sectionreturn);
3549 $renderer = $format->get_renderer($PAGE);
3551 switch($action) {
3552 case 'hide':
3553 case 'show':
3554 case 'stealth':
3555 require_capability('moodle/course:activityvisibility', $modcontext);
3556 $visible = ($action === 'hide') ? 0 : 1;
3557 $visibleoncoursepage = ($action === 'stealth') ? 0 : 1;
3558 set_coursemodule_visible($id, $visible, $visibleoncoursepage);
3559 \core\event\course_module_updated::create_from_cm($cm, $modcontext)->trigger();
3560 break;
3561 case 'duplicate':
3562 require_capability('moodle/course:manageactivities', $coursecontext);
3563 require_capability('moodle/backup:backuptargetimport', $coursecontext);
3564 require_capability('moodle/restore:restoretargetimport', $coursecontext);
3565 if (!course_allowed_module($course, $cm->modname)) {
3566 throw new moodle_exception('No permission to create that activity');
3568 if ($newcm = duplicate_module($course, $cm)) {
3570 $modinfo = $format->get_modinfo();
3571 $section = $modinfo->get_section_info($newcm->sectionnum);
3572 $cm = $modinfo->get_cm($id);
3574 // Get both original and new element html.
3575 $result = $renderer->course_section_updated_cm_item($format, $section, $cm);
3576 $result .= $renderer->course_section_updated_cm_item($format, $section, $newcm);
3577 return $result;
3579 break;
3580 case 'groupsseparate':
3581 case 'groupsvisible':
3582 case 'groupsnone':
3583 require_capability('moodle/course:manageactivities', $modcontext);
3584 if ($action === 'groupsseparate') {
3585 $newgroupmode = SEPARATEGROUPS;
3586 } else if ($action === 'groupsvisible') {
3587 $newgroupmode = VISIBLEGROUPS;
3588 } else {
3589 $newgroupmode = NOGROUPS;
3591 if (set_coursemodule_groupmode($cm->id, $newgroupmode)) {
3592 \core\event\course_module_updated::create_from_cm($cm, $modcontext)->trigger();
3594 break;
3595 case 'moveleft':
3596 case 'moveright':
3597 require_capability('moodle/course:manageactivities', $modcontext);
3598 $indent = $cm->indent + (($action === 'moveright') ? 1 : -1);
3599 if ($cm->indent >= 0) {
3600 $DB->update_record('course_modules', array('id' => $cm->id, 'indent' => $indent));
3601 rebuild_course_cache($cm->course);
3603 break;
3604 case 'delete':
3605 require_capability('moodle/course:manageactivities', $modcontext);
3606 course_delete_module($cm->id, true);
3607 return '';
3608 default:
3609 throw new coding_exception('Unrecognised action');
3612 $modinfo = $format->get_modinfo();
3613 $section = $modinfo->get_section_info($cm->sectionnum);
3614 $cm = $modinfo->get_cm($id);
3615 return $renderer->course_section_updated_cm_item($format, $section, $cm);
3619 * Return structure for edit_module()
3621 * @since Moodle 3.3
3622 * @return \core_external\external_description
3624 public static function edit_module_returns() {
3625 return new external_value(PARAM_RAW, 'html to replace the current module with');
3629 * Parameters for function get_module()
3631 * @since Moodle 3.3
3632 * @return external_function_parameters
3634 public static function get_module_parameters() {
3635 return new external_function_parameters(
3636 array(
3637 'id' => new external_value(PARAM_INT, 'course module id', VALUE_REQUIRED),
3638 'sectionreturn' => new external_value(PARAM_INT, 'section to return to', VALUE_DEFAULT, null),
3643 * Returns html for displaying one activity module on course page
3645 * @since Moodle 3.3
3646 * @param int $id
3647 * @param null|int $sectionreturn
3648 * @return string
3650 public static function get_module($id, $sectionreturn = null) {
3651 global $PAGE;
3652 // Validate and normalize parameters.
3653 $params = self::validate_parameters(self::get_module_parameters(),
3654 array('id' => $id, 'sectionreturn' => $sectionreturn));
3655 $id = $params['id'];
3656 $sectionreturn = $params['sectionreturn'];
3658 // Set of permissions an editing user may have.
3659 $contextarray = [
3660 'moodle/course:update',
3661 'moodle/course:manageactivities',
3662 'moodle/course:activityvisibility',
3663 'moodle/course:sectionvisibility',
3664 'moodle/course:movesections',
3665 'moodle/course:setcurrentsection',
3667 $PAGE->set_other_editing_capability($contextarray);
3669 // Validate access to the course (note, this is html for the course view page, we don't validate access to the module).
3670 list($course, $cm) = get_course_and_cm_from_cmid($id);
3671 self::validate_context(context_course::instance($course->id));
3673 $format = course_get_format($course);
3674 if ($sectionreturn) {
3675 $format->set_section_number($sectionreturn);
3677 $renderer = $format->get_renderer($PAGE);
3679 $modinfo = $format->get_modinfo();
3680 $section = $modinfo->get_section_info($cm->sectionnum);
3681 return $renderer->course_section_updated_cm_item($format, $section, $cm);
3685 * Return structure for get_module()
3687 * @since Moodle 3.3
3688 * @return \core_external\external_description
3690 public static function get_module_returns() {
3691 return new external_value(PARAM_RAW, 'html to replace the current module with');
3695 * Parameters for function edit_section()
3697 * @since Moodle 3.3
3698 * @return external_function_parameters
3700 public static function edit_section_parameters() {
3701 return new external_function_parameters(
3702 array(
3703 'action' => new external_value(PARAM_ALPHA, 'action: hide, show, stealth, setmarker, removemarker', VALUE_REQUIRED),
3704 'id' => new external_value(PARAM_INT, 'course section id', VALUE_REQUIRED),
3705 'sectionreturn' => new external_value(PARAM_INT, 'section to return to', VALUE_DEFAULT, null),
3710 * Performs one of the edit section actions
3712 * @since Moodle 3.3
3713 * @param string $action
3714 * @param int $id section id
3715 * @param int $sectionreturn section to return to
3716 * @return string
3718 public static function edit_section($action, $id, $sectionreturn) {
3719 global $DB;
3720 // Validate and normalize parameters.
3721 $params = self::validate_parameters(self::edit_section_parameters(),
3722 array('action' => $action, 'id' => $id, 'sectionreturn' => $sectionreturn));
3723 $action = $params['action'];
3724 $id = $params['id'];
3725 $sr = $params['sectionreturn'];
3727 $section = $DB->get_record('course_sections', array('id' => $id), '*', MUST_EXIST);
3728 $coursecontext = context_course::instance($section->course);
3729 self::validate_context($coursecontext);
3731 $rv = course_get_format($section->course)->section_action($section, $action, $sectionreturn);
3732 if ($rv) {
3733 return json_encode($rv);
3734 } else {
3735 return null;
3740 * Return structure for edit_section()
3742 * @since Moodle 3.3
3743 * @return \core_external\external_description
3745 public static function edit_section_returns() {
3746 return new external_value(PARAM_RAW, 'Additional data for javascript (JSON-encoded string)');
3750 * Returns description of method parameters
3752 * @return external_function_parameters
3754 public static function get_enrolled_courses_by_timeline_classification_parameters() {
3755 return new external_function_parameters(
3756 array(
3757 'classification' => new external_value(PARAM_ALPHA, 'future, inprogress, or past'),
3758 'limit' => new external_value(PARAM_INT, 'Result set limit', VALUE_DEFAULT, 0),
3759 'offset' => new external_value(PARAM_INT, 'Result set offset', VALUE_DEFAULT, 0),
3760 'sort' => new external_value(PARAM_TEXT, 'Sort string', VALUE_DEFAULT, null),
3761 'customfieldname' => new external_value(PARAM_ALPHANUMEXT, 'Used when classification = customfield',
3762 VALUE_DEFAULT, null),
3763 'customfieldvalue' => new external_value(PARAM_RAW, 'Used when classification = customfield',
3764 VALUE_DEFAULT, null),
3765 'searchvalue' => new external_value(PARAM_RAW, 'The value a user wishes to search against',
3766 VALUE_DEFAULT, null),
3772 * Get courses matching the given timeline classification.
3774 * NOTE: The offset applies to the unfiltered full set of courses before the classification
3775 * filtering is done.
3776 * E.g.
3777 * If the user is enrolled in 5 courses:
3778 * c1, c2, c3, c4, and c5
3779 * And c4 and c5 are 'future' courses
3781 * If a request comes in for future courses with an offset of 1 it will mean that
3782 * c1 is skipped (because the offset applies *before* the classification filtering)
3783 * and c4 and c5 will be return.
3785 * @param string $classification past, inprogress, or future
3786 * @param int $limit Result set limit
3787 * @param int $offset Offset the full course set before timeline classification is applied
3788 * @param string $sort SQL sort string for results
3789 * @param string $customfieldname
3790 * @param string $customfieldvalue
3791 * @param string $searchvalue
3792 * @return array list of courses and warnings
3793 * @throws invalid_parameter_exception
3795 public static function get_enrolled_courses_by_timeline_classification(
3796 string $classification,
3797 int $limit = 0,
3798 int $offset = 0,
3799 string $sort = null,
3800 string $customfieldname = null,
3801 string $customfieldvalue = null,
3802 string $searchvalue = null
3804 global $CFG, $PAGE, $USER;
3805 require_once($CFG->dirroot . '/course/lib.php');
3807 $params = self::validate_parameters(self::get_enrolled_courses_by_timeline_classification_parameters(),
3808 array(
3809 'classification' => $classification,
3810 'limit' => $limit,
3811 'offset' => $offset,
3812 'sort' => $sort,
3813 'customfieldvalue' => $customfieldvalue,
3814 'searchvalue' => $searchvalue,
3818 $classification = $params['classification'];
3819 $limit = $params['limit'];
3820 $offset = $params['offset'];
3821 $sort = $params['sort'];
3822 $customfieldvalue = $params['customfieldvalue'];
3823 $searchvalue = clean_param($params['searchvalue'], PARAM_TEXT);
3825 switch($classification) {
3826 case COURSE_TIMELINE_ALLINCLUDINGHIDDEN:
3827 break;
3828 case COURSE_TIMELINE_ALL:
3829 break;
3830 case COURSE_TIMELINE_PAST:
3831 break;
3832 case COURSE_TIMELINE_INPROGRESS:
3833 break;
3834 case COURSE_TIMELINE_FUTURE:
3835 break;
3836 case COURSE_FAVOURITES:
3837 break;
3838 case COURSE_TIMELINE_HIDDEN:
3839 break;
3840 case COURSE_TIMELINE_SEARCH:
3841 break;
3842 case COURSE_CUSTOMFIELD:
3843 break;
3844 default:
3845 throw new invalid_parameter_exception('Invalid classification');
3848 self::validate_context(context_user::instance($USER->id));
3850 $requiredproperties = course_summary_exporter::define_properties();
3851 $fields = join(',', array_keys($requiredproperties));
3852 $hiddencourses = get_hidden_courses_on_timeline();
3853 $courses = [];
3855 // If the timeline requires really all courses, get really all courses.
3856 if ($classification == COURSE_TIMELINE_ALLINCLUDINGHIDDEN) {
3857 $courses = course_get_enrolled_courses_for_logged_in_user(0, $offset, $sort, $fields, COURSE_DB_QUERY_LIMIT);
3859 // Otherwise if the timeline requires the hidden courses then restrict the result to only $hiddencourses.
3860 } else if ($classification == COURSE_TIMELINE_HIDDEN) {
3861 $courses = course_get_enrolled_courses_for_logged_in_user(0, $offset, $sort, $fields,
3862 COURSE_DB_QUERY_LIMIT, $hiddencourses);
3864 // Otherwise get the requested courses and exclude the hidden courses.
3865 } else if ($classification == COURSE_TIMELINE_SEARCH) {
3866 // Prepare the search API options.
3867 $searchcriteria['search'] = $searchvalue;
3868 $options = ['idonly' => true];
3869 $courses = course_get_enrolled_courses_for_logged_in_user_from_search(
3871 $offset,
3872 $sort,
3873 $fields,
3874 COURSE_DB_QUERY_LIMIT,
3875 $searchcriteria,
3876 $options
3878 } else {
3879 $courses = course_get_enrolled_courses_for_logged_in_user(0, $offset, $sort, $fields,
3880 COURSE_DB_QUERY_LIMIT, [], $hiddencourses);
3883 $favouritecourseids = [];
3884 $ufservice = \core_favourites\service_factory::get_service_for_user_context(\context_user::instance($USER->id));
3885 $favourites = $ufservice->find_favourites_by_type('core_course', 'courses');
3887 if ($favourites) {
3888 $favouritecourseids = array_map(
3889 function($favourite) {
3890 return $favourite->itemid;
3891 }, $favourites);
3894 if ($classification == COURSE_FAVOURITES) {
3895 list($filteredcourses, $processedcount) = course_filter_courses_by_favourites(
3896 $courses,
3897 $favouritecourseids,
3898 $limit
3900 } else if ($classification == COURSE_CUSTOMFIELD) {
3901 list($filteredcourses, $processedcount) = course_filter_courses_by_customfield(
3902 $courses,
3903 $customfieldname,
3904 $customfieldvalue,
3905 $limit
3907 } else {
3908 list($filteredcourses, $processedcount) = course_filter_courses_by_timeline_classification(
3909 $courses,
3910 $classification,
3911 $limit
3915 $renderer = $PAGE->get_renderer('core');
3916 $formattedcourses = array_map(function($course) use ($renderer, $favouritecourseids) {
3917 if ($course == null) {
3918 return;
3920 context_helper::preload_from_record($course);
3921 $context = context_course::instance($course->id);
3922 $isfavourite = false;
3923 if (in_array($course->id, $favouritecourseids)) {
3924 $isfavourite = true;
3926 $exporter = new course_summary_exporter($course, ['context' => $context, 'isfavourite' => $isfavourite]);
3927 return $exporter->export($renderer);
3928 }, $filteredcourses);
3930 $formattedcourses = array_filter($formattedcourses, function($course) {
3931 if ($course != null) {
3932 return $course;
3936 return [
3937 'courses' => $formattedcourses,
3938 'nextoffset' => $offset + $processedcount
3943 * Returns description of method result value
3945 * @return \core_external\external_description
3947 public static function get_enrolled_courses_by_timeline_classification_returns() {
3948 return new external_single_structure(
3949 array(
3950 'courses' => new external_multiple_structure(course_summary_exporter::get_read_structure(), 'Course'),
3951 'nextoffset' => new external_value(PARAM_INT, 'Offset for the next request')
3957 * Returns description of method parameters
3959 * @return external_function_parameters
3961 public static function set_favourite_courses_parameters() {
3962 return new external_function_parameters(
3963 array(
3964 'courses' => new external_multiple_structure(
3965 new external_single_structure(
3966 array(
3967 'id' => new external_value(PARAM_INT, 'course ID'),
3968 'favourite' => new external_value(PARAM_BOOL, 'favourite status')
3977 * Set the course favourite status for an array of courses.
3979 * @param array $courses List with course id's and favourite status.
3980 * @return array Array with an array of favourite courses.
3982 public static function set_favourite_courses(
3983 array $courses
3985 global $USER;
3987 $params = self::validate_parameters(self::set_favourite_courses_parameters(),
3988 array(
3989 'courses' => $courses
3993 $warnings = [];
3995 $ufservice = \core_favourites\service_factory::get_service_for_user_context(\context_user::instance($USER->id));
3997 foreach ($params['courses'] as $course) {
3999 $warning = [];
4001 $favouriteexists = $ufservice->favourite_exists('core_course', 'courses', $course['id'],
4002 \context_course::instance($course['id']));
4004 if ($course['favourite']) {
4005 if (!$favouriteexists) {
4006 try {
4007 $ufservice->create_favourite('core_course', 'courses', $course['id'],
4008 \context_course::instance($course['id']));
4009 } catch (Exception $e) {
4010 $warning['courseid'] = $course['id'];
4011 if ($e instanceof moodle_exception) {
4012 $warning['warningcode'] = $e->errorcode;
4013 } else {
4014 $warning['warningcode'] = $e->getCode();
4016 $warning['message'] = $e->getMessage();
4017 $warnings[] = $warning;
4018 $warnings[] = $warning;
4020 } else {
4021 $warning['courseid'] = $course['id'];
4022 $warning['warningcode'] = 'coursealreadyfavourited';
4023 $warning['message'] = 'Course already favourited';
4024 $warnings[] = $warning;
4026 } else {
4027 if ($favouriteexists) {
4028 try {
4029 $ufservice->delete_favourite('core_course', 'courses', $course['id'],
4030 \context_course::instance($course['id']));
4031 } catch (Exception $e) {
4032 $warning['courseid'] = $course['id'];
4033 if ($e instanceof moodle_exception) {
4034 $warning['warningcode'] = $e->errorcode;
4035 } else {
4036 $warning['warningcode'] = $e->getCode();
4038 $warning['message'] = $e->getMessage();
4039 $warnings[] = $warning;
4040 $warnings[] = $warning;
4042 } else {
4043 $warning['courseid'] = $course['id'];
4044 $warning['warningcode'] = 'cannotdeletefavourite';
4045 $warning['message'] = 'Could not delete favourite status for course';
4046 $warnings[] = $warning;
4051 return [
4052 'warnings' => $warnings
4057 * Returns description of method result value
4059 * @return \core_external\external_description
4061 public static function set_favourite_courses_returns() {
4062 return new external_single_structure(
4063 array(
4064 'warnings' => new external_warnings()
4070 * Returns description of method parameters
4072 * @return external_function_parameters
4073 * @since Moodle 3.6
4075 public static function get_recent_courses_parameters() {
4076 return new external_function_parameters(
4077 array(
4078 'userid' => new external_value(PARAM_INT, 'id of the user, default to current user', VALUE_DEFAULT, 0),
4079 'limit' => new external_value(PARAM_INT, 'result set limit', VALUE_DEFAULT, 0),
4080 'offset' => new external_value(PARAM_INT, 'Result set offset', VALUE_DEFAULT, 0),
4081 'sort' => new external_value(PARAM_TEXT, 'Sort string', VALUE_DEFAULT, null)
4087 * Get last accessed courses adding additional course information like images.
4089 * @param int $userid User id from which the courses will be obtained
4090 * @param int $limit Restrict result set to this amount
4091 * @param int $offset Skip this number of records from the start of the result set
4092 * @param string|null $sort SQL string for sorting
4093 * @return array List of courses
4094 * @throws invalid_parameter_exception
4096 public static function get_recent_courses(int $userid = 0, int $limit = 0, int $offset = 0, string $sort = null) {
4097 global $USER, $PAGE;
4099 if (empty($userid)) {
4100 $userid = $USER->id;
4103 $params = self::validate_parameters(self::get_recent_courses_parameters(),
4104 array(
4105 'userid' => $userid,
4106 'limit' => $limit,
4107 'offset' => $offset,
4108 'sort' => $sort
4112 $userid = $params['userid'];
4113 $limit = $params['limit'];
4114 $offset = $params['offset'];
4115 $sort = $params['sort'];
4117 $usercontext = context_user::instance($userid);
4119 self::validate_context($usercontext);
4121 if ($userid != $USER->id and !has_capability('moodle/user:viewdetails', $usercontext)) {
4122 return array();
4125 $courses = course_get_recent_courses($userid, $limit, $offset, $sort);
4127 $renderer = $PAGE->get_renderer('core');
4129 $recentcourses = array_map(function($course) use ($renderer) {
4130 context_helper::preload_from_record($course);
4131 $context = context_course::instance($course->id);
4132 $isfavourite = !empty($course->component);
4133 $exporter = new course_summary_exporter($course, ['context' => $context, 'isfavourite' => $isfavourite]);
4134 return $exporter->export($renderer);
4135 }, $courses);
4137 return $recentcourses;
4141 * Returns description of method result value
4143 * @return \core_external\external_description
4144 * @since Moodle 3.6
4146 public static function get_recent_courses_returns() {
4147 return new external_multiple_structure(course_summary_exporter::get_read_structure(), 'Courses');
4151 * Returns description of method parameters
4153 * @return external_function_parameters
4155 public static function get_enrolled_users_by_cmid_parameters() {
4156 return new external_function_parameters([
4157 'cmid' => new external_value(PARAM_INT, 'id of the course module', VALUE_REQUIRED),
4158 'groupid' => new external_value(PARAM_INT, 'id of the group', VALUE_DEFAULT, 0),
4159 'onlyactive' => new external_value(PARAM_BOOL, 'whether to return only active users or all.',
4160 VALUE_DEFAULT, false),
4165 * Get all users in a course for a given cmid.
4167 * @param int $cmid Course Module id from which the users will be obtained
4168 * @param int $groupid Group id from which the users will be obtained
4169 * @param bool $onlyactive Whether to return only the active enrolled users or all enrolled users in the course.
4170 * @return array List of users
4171 * @throws invalid_parameter_exception
4173 public static function get_enrolled_users_by_cmid(int $cmid, int $groupid = 0, bool $onlyactive = false) {
4174 global $PAGE;
4175 $warnings = [];
4177 self::validate_parameters(self::get_enrolled_users_by_cmid_parameters(), [
4178 'cmid' => $cmid,
4179 'groupid' => $groupid,
4180 'onlyactive' => $onlyactive,
4183 list($course, $cm) = get_course_and_cm_from_cmid($cmid);
4184 $coursecontext = context_course::instance($course->id);
4185 self::validate_context($coursecontext);
4187 $enrolledusers = get_enrolled_users($coursecontext, '', $groupid, 'u.*', null, 0, 0, $onlyactive);
4189 $users = array_map(function ($user) use ($PAGE) {
4190 $user->fullname = fullname($user);
4191 $userpicture = new user_picture($user);
4192 $userpicture->size = 1;
4193 $user->profileimage = $userpicture->get_url($PAGE)->out(false);
4194 return $user;
4195 }, $enrolledusers);
4196 sort($users);
4198 return [
4199 'users' => $users,
4200 'warnings' => $warnings,
4205 * Returns description of method result value
4207 * @return \core_external\external_description
4209 public static function get_enrolled_users_by_cmid_returns() {
4210 return new external_single_structure([
4211 'users' => new external_multiple_structure(self::user_description()),
4212 'warnings' => new external_warnings(),
4217 * Create user return value description.
4219 * @return \core_external\external_description
4221 public static function user_description() {
4222 $userfields = array(
4223 'id' => new external_value(core_user::get_property_type('id'), 'ID of the user'),
4224 'profileimage' => new external_value(PARAM_URL, 'The location of the users larger image', VALUE_OPTIONAL),
4225 'fullname' => new external_value(PARAM_TEXT, 'The full name of the user', VALUE_OPTIONAL),
4226 'firstname' => new external_value(
4227 core_user::get_property_type('firstname'),
4228 'The first name(s) of the user',
4229 VALUE_OPTIONAL),
4230 'lastname' => new external_value(
4231 core_user::get_property_type('lastname'),
4232 'The family name of the user',
4233 VALUE_OPTIONAL),
4235 return new external_single_structure($userfields);
4239 * Returns description of method parameters.
4241 * @return external_function_parameters
4243 public static function add_content_item_to_user_favourites_parameters() {
4244 return new external_function_parameters([
4245 'componentname' => new external_value(PARAM_TEXT,
4246 'frankenstyle name of the component to which the content item belongs', VALUE_REQUIRED),
4247 'contentitemid' => new external_value(PARAM_INT, 'id of the content item', VALUE_REQUIRED, '', NULL_NOT_ALLOWED)
4252 * Add a content item to a user's favourites.
4254 * @param string $componentname the name of the component from which this content item originates.
4255 * @param int $contentitemid the id of the content item.
4256 * @return stdClass the exporter content item.
4258 public static function add_content_item_to_user_favourites(string $componentname, int $contentitemid) {
4259 global $USER;
4262 'componentname' => $componentname,
4263 'contentitemid' => $contentitemid,
4264 ] = self::validate_parameters(self::add_content_item_to_user_favourites_parameters(),
4266 'componentname' => $componentname,
4267 'contentitemid' => $contentitemid,
4271 self::validate_context(context_user::instance($USER->id));
4273 $contentitemservice = \core_course\local\factory\content_item_service_factory::get_content_item_service();
4275 return $contentitemservice->add_to_user_favourites($USER, $componentname, $contentitemid);
4279 * Returns description of method result value.
4281 * @return \core_external\external_description
4283 public static function add_content_item_to_user_favourites_returns() {
4284 return \core_course\local\exporters\course_content_item_exporter::get_read_structure();
4288 * Returns description of method parameters.
4290 * @return external_function_parameters
4292 public static function remove_content_item_from_user_favourites_parameters() {
4293 return new external_function_parameters([
4294 'componentname' => new external_value(PARAM_TEXT,
4295 'frankenstyle name of the component to which the content item belongs', VALUE_REQUIRED),
4296 'contentitemid' => new external_value(PARAM_INT, 'id of the content item', VALUE_REQUIRED, '', NULL_NOT_ALLOWED),
4301 * Remove a content item from a user's favourites.
4303 * @param string $componentname the name of the component from which this content item originates.
4304 * @param int $contentitemid the id of the content item.
4305 * @return stdClass the exported content item.
4307 public static function remove_content_item_from_user_favourites(string $componentname, int $contentitemid) {
4308 global $USER;
4311 'componentname' => $componentname,
4312 'contentitemid' => $contentitemid,
4313 ] = self::validate_parameters(self::remove_content_item_from_user_favourites_parameters(),
4315 'componentname' => $componentname,
4316 'contentitemid' => $contentitemid,
4320 self::validate_context(context_user::instance($USER->id));
4322 $contentitemservice = \core_course\local\factory\content_item_service_factory::get_content_item_service();
4324 return $contentitemservice->remove_from_user_favourites($USER, $componentname, $contentitemid);
4328 * Returns description of method result value.
4330 * @return \core_external\external_description
4332 public static function remove_content_item_from_user_favourites_returns() {
4333 return \core_course\local\exporters\course_content_item_exporter::get_read_structure();
4337 * Returns description of method result value
4339 * @return \core_external\external_description
4341 public static function get_course_content_items_returns() {
4342 return new external_single_structure([
4343 'content_items' => new external_multiple_structure(
4344 \core_course\local\exporters\course_content_item_exporter::get_read_structure()
4350 * Returns description of method parameters
4352 * @return external_function_parameters
4354 public static function get_course_content_items_parameters() {
4355 return new external_function_parameters([
4356 'courseid' => new external_value(PARAM_INT, 'ID of the course', VALUE_REQUIRED),
4361 * Given a course ID fetch all accessible modules for that course
4363 * @param int $courseid The course we want to fetch the modules for
4364 * @return array Contains array of modules and their metadata
4366 public static function get_course_content_items(int $courseid) {
4367 global $USER;
4370 'courseid' => $courseid,
4371 ] = self::validate_parameters(self::get_course_content_items_parameters(), [
4372 'courseid' => $courseid,
4375 $coursecontext = context_course::instance($courseid);
4376 self::validate_context($coursecontext);
4377 $course = get_course($courseid);
4379 $contentitemservice = \core_course\local\factory\content_item_service_factory::get_content_item_service();
4381 $contentitems = $contentitemservice->get_content_items_for_user_in_course($USER, $course);
4382 return ['content_items' => $contentitems];
4386 * Returns description of method parameters.
4388 * @return external_function_parameters
4390 public static function toggle_activity_recommendation_parameters() {
4391 return new external_function_parameters([
4392 'area' => new external_value(PARAM_TEXT, 'The favourite area (itemtype)', VALUE_REQUIRED),
4393 'id' => new external_value(PARAM_INT, 'id of the activity or whatever', VALUE_REQUIRED),
4398 * Update the recommendation for an activity item.
4400 * @param string $area identifier for this activity.
4401 * @param int $id Associated id. This is needed in conjunction with the area to find the recommendation.
4402 * @return array some warnings or something.
4404 public static function toggle_activity_recommendation(string $area, int $id): array {
4405 ['area' => $area, 'id' => $id] = self::validate_parameters(self::toggle_activity_recommendation_parameters(),
4406 ['area' => $area, 'id' => $id]);
4408 $context = context_system::instance();
4409 self::validate_context($context);
4411 require_capability('moodle/course:recommendactivity', $context);
4413 $manager = \core_course\local\factory\content_item_service_factory::get_content_item_service();
4415 $status = $manager->toggle_recommendation($area, $id);
4416 return ['id' => $id, 'area' => $area, 'status' => $status];
4420 * Returns warnings.
4422 * @return \core_external\external_description
4424 public static function toggle_activity_recommendation_returns() {
4425 return new external_single_structure(
4427 'id' => new external_value(PARAM_INT, 'id of the activity or whatever'),
4428 'area' => new external_value(PARAM_TEXT, 'The favourite area (itemtype)'),
4429 'status' => new external_value(PARAM_BOOL, 'If created or deleted'),
4435 * Returns description of method parameters
4437 * @return external_function_parameters
4439 public static function get_activity_chooser_footer_parameters() {
4440 return new external_function_parameters([
4441 'courseid' => new external_value(PARAM_INT, 'ID of the course', VALUE_REQUIRED),
4442 'sectionid' => new external_value(PARAM_INT, 'ID of the section', VALUE_REQUIRED),
4447 * Given a course ID we need to build up a footre for the chooser.
4449 * @param int $courseid The course we want to fetch the modules for
4450 * @param int $sectionid The section we want to fetch the modules for
4451 * @return array
4453 public static function get_activity_chooser_footer(int $courseid, int $sectionid) {
4455 'courseid' => $courseid,
4456 'sectionid' => $sectionid,
4457 ] = self::validate_parameters(self::get_activity_chooser_footer_parameters(), [
4458 'courseid' => $courseid,
4459 'sectionid' => $sectionid,
4462 $coursecontext = context_course::instance($courseid);
4463 self::validate_context($coursecontext);
4465 $activeplugin = get_config('core', 'activitychooseractivefooter');
4467 if ($activeplugin !== COURSE_CHOOSER_FOOTER_NONE) {
4468 $footerdata = component_callback($activeplugin, 'custom_chooser_footer', [$courseid, $sectionid]);
4469 return [
4470 'footer' => true,
4471 'customfooterjs' => $footerdata->get_footer_js_file(),
4472 'customfootertemplate' => $footerdata->get_footer_template(),
4473 'customcarouseltemplate' => $footerdata->get_carousel_template(),
4475 } else {
4476 return [
4477 'footer' => false,
4483 * Returns description of method result value
4485 * @return \core_external\external_description
4487 public static function get_activity_chooser_footer_returns() {
4488 return new external_single_structure(
4490 'footer' => new external_value(PARAM_BOOL, 'Is a footer being return by this request?', VALUE_REQUIRED),
4491 'customfooterjs' => new external_value(PARAM_RAW, 'The path to the plugin JS file', VALUE_OPTIONAL),
4492 'customfootertemplate' => new external_value(PARAM_RAW, 'The prerendered footer', VALUE_OPTIONAL),
4493 'customcarouseltemplate' => new external_value(PARAM_RAW, 'Either "" or the prerendered carousel page',
4494 VALUE_OPTIONAL),