MDL-52901 mod_assign: Check due dates in external save_submission
[moodle.git] / course / externallib.php
blobd119c5f9b87670ed63fd82c0096d5e686c897285
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 require_once("$CFG->libdir/externallib.php");
31 /**
32 * Course external functions
34 * @package core_course
35 * @category external
36 * @copyright 2011 Jerome Mouneyrac
37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
38 * @since Moodle 2.2
40 class core_course_external extends external_api {
42 /**
43 * Returns description of method parameters
45 * @return external_function_parameters
46 * @since Moodle 2.2
48 public static function get_course_contents_parameters() {
49 return new external_function_parameters(
50 array('courseid' => new external_value(PARAM_INT, 'course id'),
51 'options' => new external_multiple_structure (
52 new external_single_structure(
53 array('name' => new external_value(PARAM_ALPHANUM, 'option name'),
54 'value' => new external_value(PARAM_RAW, 'the value of the option, this param is personaly validated in the external function.')
56 ), 'Options, not used yet, might be used in later version', VALUE_DEFAULT, array())
61 /**
62 * Get course contents
64 * @param int $courseid course id
65 * @param array $options These options are not used yet, might be used in later version
66 * @return array
67 * @since Moodle 2.2
69 public static function get_course_contents($courseid, $options = array()) {
70 global $CFG, $DB;
71 require_once($CFG->dirroot . "/course/lib.php");
73 //validate parameter
74 $params = self::validate_parameters(self::get_course_contents_parameters(),
75 array('courseid' => $courseid, 'options' => $options));
77 //retrieve the course
78 $course = $DB->get_record('course', array('id' => $params['courseid']), '*', MUST_EXIST);
80 if ($course->id != SITEID) {
81 // Check course format exist.
82 if (!file_exists($CFG->dirroot . '/course/format/' . $course->format . '/lib.php')) {
83 throw new moodle_exception('cannotgetcoursecontents', 'webservice', '', null,
84 get_string('courseformatnotfound', 'error', $course->format));
85 } else {
86 require_once($CFG->dirroot . '/course/format/' . $course->format . '/lib.php');
90 // now security checks
91 $context = context_course::instance($course->id, IGNORE_MISSING);
92 try {
93 self::validate_context($context);
94 } catch (Exception $e) {
95 $exceptionparam = new stdClass();
96 $exceptionparam->message = $e->getMessage();
97 $exceptionparam->courseid = $course->id;
98 throw new moodle_exception('errorcoursecontextnotvalid', 'webservice', '', $exceptionparam);
101 $canupdatecourse = has_capability('moodle/course:update', $context);
103 //create return value
104 $coursecontents = array();
106 if ($canupdatecourse or $course->visible
107 or has_capability('moodle/course:viewhiddencourses', $context)) {
109 //retrieve sections
110 $modinfo = get_fast_modinfo($course);
111 $sections = $modinfo->get_section_info_all();
113 //for each sections (first displayed to last displayed)
114 $modinfosections = $modinfo->get_sections();
115 foreach ($sections as $key => $section) {
117 if (!$section->uservisible) {
118 continue;
121 // reset $sectioncontents
122 $sectionvalues = array();
123 $sectionvalues['id'] = $section->id;
124 $sectionvalues['name'] = get_section_name($course, $section);
125 $sectionvalues['visible'] = $section->visible;
126 list($sectionvalues['summary'], $sectionvalues['summaryformat']) =
127 external_format_text($section->summary, $section->summaryformat,
128 $context->id, 'course', 'section', $section->id);
129 $sectioncontents = array();
131 //for each module of the section
132 if (!empty($modinfosections[$section->section])) {
133 foreach ($modinfosections[$section->section] as $cmid) {
134 $cm = $modinfo->cms[$cmid];
136 // stop here if the module is not visible to the user
137 if (!$cm->uservisible) {
138 continue;
141 $module = array();
143 //common info (for people being able to see the module or availability dates)
144 $module['id'] = $cm->id;
145 $module['name'] = format_string($cm->name, true);
146 $module['instance'] = $cm->instance;
147 $module['modname'] = $cm->modname;
148 $module['modplural'] = $cm->modplural;
149 $module['modicon'] = $cm->get_icon_url()->out(false);
150 $module['indent'] = $cm->indent;
152 $modcontext = context_module::instance($cm->id);
154 if (!empty($cm->showdescription) or $cm->modname == 'label') {
155 // We want to use the external format. However from reading get_formatted_content(), $cm->content format is always FORMAT_HTML.
156 list($module['description'], $descriptionformat) = external_format_text($cm->content,
157 FORMAT_HTML, $modcontext->id, $cm->modname, 'intro', $cm->id);
160 //url of the module
161 $url = $cm->url;
162 if ($url) { //labels don't have url
163 $module['url'] = $url->out(false);
166 $canviewhidden = has_capability('moodle/course:viewhiddenactivities',
167 context_module::instance($cm->id));
168 //user that can view hidden module should know about the visibility
169 $module['visible'] = $cm->visible;
171 // Availability date (also send to user who can see hidden module).
172 if ($CFG->enableavailability && ($canviewhidden || $canupdatecourse)) {
173 $module['availability'] = $cm->availability;
176 $baseurl = 'webservice/pluginfile.php';
178 //call $modulename_export_contents
179 //(each module callback take care about checking the capabilities)
180 require_once($CFG->dirroot . '/mod/' . $cm->modname . '/lib.php');
181 $getcontentfunction = $cm->modname.'_export_contents';
182 if (function_exists($getcontentfunction)) {
183 if ($contents = $getcontentfunction($cm, $baseurl)) {
184 $module['contents'] = $contents;
188 //assign result to $sectioncontents
189 $sectioncontents[] = $module;
193 $sectionvalues['modules'] = $sectioncontents;
195 // assign result to $coursecontents
196 $coursecontents[] = $sectionvalues;
199 return $coursecontents;
203 * Returns description of method result value
205 * @return external_description
206 * @since Moodle 2.2
208 public static function get_course_contents_returns() {
209 return new external_multiple_structure(
210 new external_single_structure(
211 array(
212 'id' => new external_value(PARAM_INT, 'Section ID'),
213 'name' => new external_value(PARAM_TEXT, 'Section name'),
214 'visible' => new external_value(PARAM_INT, 'is the section visible', VALUE_OPTIONAL),
215 'summary' => new external_value(PARAM_RAW, 'Section description'),
216 'summaryformat' => new external_format_value('summary'),
217 'modules' => new external_multiple_structure(
218 new external_single_structure(
219 array(
220 'id' => new external_value(PARAM_INT, 'activity id'),
221 'url' => new external_value(PARAM_URL, 'activity url', VALUE_OPTIONAL),
222 'name' => new external_value(PARAM_RAW, 'activity module name'),
223 'instance' => new external_value(PARAM_INT, 'instance id', VALUE_OPTIONAL),
224 'description' => new external_value(PARAM_RAW, 'activity description', VALUE_OPTIONAL),
225 'visible' => new external_value(PARAM_INT, 'is the module visible', VALUE_OPTIONAL),
226 'modicon' => new external_value(PARAM_URL, 'activity icon url'),
227 'modname' => new external_value(PARAM_PLUGIN, 'activity module type'),
228 'modplural' => new external_value(PARAM_TEXT, 'activity module plural name'),
229 'availability' => new external_value(PARAM_RAW, 'module availability settings', VALUE_OPTIONAL),
230 'indent' => new external_value(PARAM_INT, 'number of identation in the site'),
231 'contents' => new external_multiple_structure(
232 new external_single_structure(
233 array(
234 // content info
235 'type'=> new external_value(PARAM_TEXT, 'a file or a folder or external link'),
236 'filename'=> new external_value(PARAM_FILE, 'filename'),
237 'filepath'=> new external_value(PARAM_PATH, 'filepath'),
238 'filesize'=> new external_value(PARAM_INT, 'filesize'),
239 'fileurl' => new external_value(PARAM_URL, 'downloadable file url', VALUE_OPTIONAL),
240 'content' => new external_value(PARAM_RAW, 'Raw content, will be used when type is content', VALUE_OPTIONAL),
241 'timecreated' => new external_value(PARAM_INT, 'Time created'),
242 'timemodified' => new external_value(PARAM_INT, 'Time modified'),
243 'sortorder' => new external_value(PARAM_INT, 'Content sort order'),
245 // copyright related info
246 'userid' => new external_value(PARAM_INT, 'User who added this content to moodle'),
247 'author' => new external_value(PARAM_TEXT, 'Content owner'),
248 'license' => new external_value(PARAM_TEXT, 'Content license'),
250 ), VALUE_DEFAULT, array()
253 ), 'list of module'
261 * Returns description of method parameters
263 * @return external_function_parameters
264 * @since Moodle 2.3
266 public static function get_courses_parameters() {
267 return new external_function_parameters(
268 array('options' => new external_single_structure(
269 array('ids' => new external_multiple_structure(
270 new external_value(PARAM_INT, 'Course id')
271 , 'List of course id. If empty return all courses
272 except front page course.',
273 VALUE_OPTIONAL)
274 ), 'options - operator OR is used', VALUE_DEFAULT, array())
280 * Get courses
282 * @param array $options It contains an array (list of ids)
283 * @return array
284 * @since Moodle 2.2
286 public static function get_courses($options = array()) {
287 global $CFG, $DB;
288 require_once($CFG->dirroot . "/course/lib.php");
290 //validate parameter
291 $params = self::validate_parameters(self::get_courses_parameters(),
292 array('options' => $options));
294 //retrieve courses
295 if (!array_key_exists('ids', $params['options'])
296 or empty($params['options']['ids'])) {
297 $courses = $DB->get_records('course');
298 } else {
299 $courses = $DB->get_records_list('course', 'id', $params['options']['ids']);
302 //create return value
303 $coursesinfo = array();
304 foreach ($courses as $course) {
306 // now security checks
307 $context = context_course::instance($course->id, IGNORE_MISSING);
308 $courseformatoptions = course_get_format($course)->get_format_options();
309 try {
310 self::validate_context($context);
311 } catch (Exception $e) {
312 $exceptionparam = new stdClass();
313 $exceptionparam->message = $e->getMessage();
314 $exceptionparam->courseid = $course->id;
315 throw new moodle_exception('errorcoursecontextnotvalid', 'webservice', '', $exceptionparam);
317 require_capability('moodle/course:view', $context);
319 $courseinfo = array();
320 $courseinfo['id'] = $course->id;
321 $courseinfo['fullname'] = $course->fullname;
322 $courseinfo['shortname'] = $course->shortname;
323 $courseinfo['categoryid'] = $course->category;
324 list($courseinfo['summary'], $courseinfo['summaryformat']) =
325 external_format_text($course->summary, $course->summaryformat, $context->id, 'course', 'summary', 0);
326 $courseinfo['format'] = $course->format;
327 $courseinfo['startdate'] = $course->startdate;
328 if (array_key_exists('numsections', $courseformatoptions)) {
329 // For backward-compartibility
330 $courseinfo['numsections'] = $courseformatoptions['numsections'];
333 //some field should be returned only if the user has update permission
334 $courseadmin = has_capability('moodle/course:update', $context);
335 if ($courseadmin) {
336 $courseinfo['categorysortorder'] = $course->sortorder;
337 $courseinfo['idnumber'] = $course->idnumber;
338 $courseinfo['showgrades'] = $course->showgrades;
339 $courseinfo['showreports'] = $course->showreports;
340 $courseinfo['newsitems'] = $course->newsitems;
341 $courseinfo['visible'] = $course->visible;
342 $courseinfo['maxbytes'] = $course->maxbytes;
343 if (array_key_exists('hiddensections', $courseformatoptions)) {
344 // For backward-compartibility
345 $courseinfo['hiddensections'] = $courseformatoptions['hiddensections'];
347 $courseinfo['groupmode'] = $course->groupmode;
348 $courseinfo['groupmodeforce'] = $course->groupmodeforce;
349 $courseinfo['defaultgroupingid'] = $course->defaultgroupingid;
350 $courseinfo['lang'] = $course->lang;
351 $courseinfo['timecreated'] = $course->timecreated;
352 $courseinfo['timemodified'] = $course->timemodified;
353 $courseinfo['forcetheme'] = $course->theme;
354 $courseinfo['enablecompletion'] = $course->enablecompletion;
355 $courseinfo['completionnotify'] = $course->completionnotify;
356 $courseinfo['courseformatoptions'] = array();
357 foreach ($courseformatoptions as $key => $value) {
358 $courseinfo['courseformatoptions'][] = array(
359 'name' => $key,
360 'value' => $value
365 if ($courseadmin or $course->visible
366 or has_capability('moodle/course:viewhiddencourses', $context)) {
367 $coursesinfo[] = $courseinfo;
371 return $coursesinfo;
375 * Returns description of method result value
377 * @return external_description
378 * @since Moodle 2.2
380 public static function get_courses_returns() {
381 return new external_multiple_structure(
382 new external_single_structure(
383 array(
384 'id' => new external_value(PARAM_INT, 'course id'),
385 'shortname' => new external_value(PARAM_TEXT, 'course short name'),
386 'categoryid' => new external_value(PARAM_INT, 'category id'),
387 'categorysortorder' => new external_value(PARAM_INT,
388 'sort order into the category', VALUE_OPTIONAL),
389 'fullname' => new external_value(PARAM_TEXT, 'full name'),
390 'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
391 'summary' => new external_value(PARAM_RAW, 'summary'),
392 'summaryformat' => new external_format_value('summary'),
393 'format' => new external_value(PARAM_PLUGIN,
394 'course format: weeks, topics, social, site,..'),
395 'showgrades' => new external_value(PARAM_INT,
396 '1 if grades are shown, otherwise 0', VALUE_OPTIONAL),
397 'newsitems' => new external_value(PARAM_INT,
398 'number of recent items appearing on the course page', VALUE_OPTIONAL),
399 'startdate' => new external_value(PARAM_INT,
400 'timestamp when the course start'),
401 'numsections' => new external_value(PARAM_INT,
402 '(deprecated, use courseformatoptions) number of weeks/topics',
403 VALUE_OPTIONAL),
404 'maxbytes' => new external_value(PARAM_INT,
405 'largest size of file that can be uploaded into the course',
406 VALUE_OPTIONAL),
407 'showreports' => new external_value(PARAM_INT,
408 'are activity report shown (yes = 1, no =0)', VALUE_OPTIONAL),
409 'visible' => new external_value(PARAM_INT,
410 '1: available to student, 0:not available', VALUE_OPTIONAL),
411 'hiddensections' => new external_value(PARAM_INT,
412 '(deprecated, use courseformatoptions) How the hidden sections in the course are displayed to students',
413 VALUE_OPTIONAL),
414 'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible',
415 VALUE_OPTIONAL),
416 'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no',
417 VALUE_OPTIONAL),
418 'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id',
419 VALUE_OPTIONAL),
420 'timecreated' => new external_value(PARAM_INT,
421 'timestamp when the course have been created', VALUE_OPTIONAL),
422 'timemodified' => new external_value(PARAM_INT,
423 'timestamp when the course have been modified', VALUE_OPTIONAL),
424 'enablecompletion' => new external_value(PARAM_INT,
425 'Enabled, control via completion and activity settings. Disbaled,
426 not shown in activity settings.',
427 VALUE_OPTIONAL),
428 'completionnotify' => new external_value(PARAM_INT,
429 '1: yes 0: no', VALUE_OPTIONAL),
430 'lang' => new external_value(PARAM_SAFEDIR,
431 'forced course language', VALUE_OPTIONAL),
432 'forcetheme' => new external_value(PARAM_PLUGIN,
433 'name of the force theme', VALUE_OPTIONAL),
434 'courseformatoptions' => new external_multiple_structure(
435 new external_single_structure(
436 array('name' => new external_value(PARAM_ALPHANUMEXT, 'course format option name'),
437 'value' => new external_value(PARAM_RAW, 'course format option value')
439 'additional options for particular course format', VALUE_OPTIONAL
441 ), 'course'
447 * Returns description of method parameters
449 * @return external_function_parameters
450 * @since Moodle 2.2
452 public static function create_courses_parameters() {
453 $courseconfig = get_config('moodlecourse'); //needed for many default values
454 return new external_function_parameters(
455 array(
456 'courses' => new external_multiple_structure(
457 new external_single_structure(
458 array(
459 'fullname' => new external_value(PARAM_TEXT, 'full name'),
460 'shortname' => new external_value(PARAM_TEXT, 'course short name'),
461 'categoryid' => new external_value(PARAM_INT, 'category id'),
462 'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
463 'summary' => new external_value(PARAM_RAW, 'summary', VALUE_OPTIONAL),
464 'summaryformat' => new external_format_value('summary', VALUE_DEFAULT),
465 'format' => new external_value(PARAM_PLUGIN,
466 'course format: weeks, topics, social, site,..',
467 VALUE_DEFAULT, $courseconfig->format),
468 'showgrades' => new external_value(PARAM_INT,
469 '1 if grades are shown, otherwise 0', VALUE_DEFAULT,
470 $courseconfig->showgrades),
471 'newsitems' => new external_value(PARAM_INT,
472 'number of recent items appearing on the course page',
473 VALUE_DEFAULT, $courseconfig->newsitems),
474 'startdate' => new external_value(PARAM_INT,
475 'timestamp when the course start', VALUE_OPTIONAL),
476 'numsections' => new external_value(PARAM_INT,
477 '(deprecated, use courseformatoptions) number of weeks/topics',
478 VALUE_OPTIONAL),
479 'maxbytes' => new external_value(PARAM_INT,
480 'largest size of file that can be uploaded into the course',
481 VALUE_DEFAULT, $courseconfig->maxbytes),
482 'showreports' => new external_value(PARAM_INT,
483 'are activity report shown (yes = 1, no =0)', VALUE_DEFAULT,
484 $courseconfig->showreports),
485 'visible' => new external_value(PARAM_INT,
486 '1: available to student, 0:not available', VALUE_OPTIONAL),
487 'hiddensections' => new external_value(PARAM_INT,
488 '(deprecated, use courseformatoptions) How the hidden sections in the course are displayed to students',
489 VALUE_OPTIONAL),
490 'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible',
491 VALUE_DEFAULT, $courseconfig->groupmode),
492 'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no',
493 VALUE_DEFAULT, $courseconfig->groupmodeforce),
494 'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id',
495 VALUE_DEFAULT, 0),
496 'enablecompletion' => new external_value(PARAM_INT,
497 'Enabled, control via completion and activity settings. Disabled,
498 not shown in activity settings.',
499 VALUE_OPTIONAL),
500 'completionnotify' => new external_value(PARAM_INT,
501 '1: yes 0: no', VALUE_OPTIONAL),
502 'lang' => new external_value(PARAM_SAFEDIR,
503 'forced course language', VALUE_OPTIONAL),
504 'forcetheme' => new external_value(PARAM_PLUGIN,
505 'name of the force theme', VALUE_OPTIONAL),
506 'courseformatoptions' => new external_multiple_structure(
507 new external_single_structure(
508 array('name' => new external_value(PARAM_ALPHANUMEXT, 'course format option name'),
509 'value' => new external_value(PARAM_RAW, 'course format option value')
511 'additional options for particular course format', VALUE_OPTIONAL),
513 ), 'courses to create'
520 * Create courses
522 * @param array $courses
523 * @return array courses (id and shortname only)
524 * @since Moodle 2.2
526 public static function create_courses($courses) {
527 global $CFG, $DB;
528 require_once($CFG->dirroot . "/course/lib.php");
529 require_once($CFG->libdir . '/completionlib.php');
531 $params = self::validate_parameters(self::create_courses_parameters(),
532 array('courses' => $courses));
534 $availablethemes = core_component::get_plugin_list('theme');
535 $availablelangs = get_string_manager()->get_list_of_translations();
537 $transaction = $DB->start_delegated_transaction();
539 foreach ($params['courses'] as $course) {
541 // Ensure the current user is allowed to run this function
542 $context = context_coursecat::instance($course['categoryid'], IGNORE_MISSING);
543 try {
544 self::validate_context($context);
545 } catch (Exception $e) {
546 $exceptionparam = new stdClass();
547 $exceptionparam->message = $e->getMessage();
548 $exceptionparam->catid = $course['categoryid'];
549 throw new moodle_exception('errorcatcontextnotvalid', 'webservice', '', $exceptionparam);
551 require_capability('moodle/course:create', $context);
553 // Make sure lang is valid
554 if (array_key_exists('lang', $course) and empty($availablelangs[$course['lang']])) {
555 throw new moodle_exception('errorinvalidparam', 'webservice', '', 'lang');
558 // Make sure theme is valid
559 if (array_key_exists('forcetheme', $course)) {
560 if (!empty($CFG->allowcoursethemes)) {
561 if (empty($availablethemes[$course['forcetheme']])) {
562 throw new moodle_exception('errorinvalidparam', 'webservice', '', 'forcetheme');
563 } else {
564 $course['theme'] = $course['forcetheme'];
569 //force visibility if ws user doesn't have the permission to set it
570 $category = $DB->get_record('course_categories', array('id' => $course['categoryid']));
571 if (!has_capability('moodle/course:visibility', $context)) {
572 $course['visible'] = $category->visible;
575 //set default value for completion
576 $courseconfig = get_config('moodlecourse');
577 if (completion_info::is_enabled_for_site()) {
578 if (!array_key_exists('enablecompletion', $course)) {
579 $course['enablecompletion'] = $courseconfig->enablecompletion;
581 } else {
582 $course['enablecompletion'] = 0;
585 $course['category'] = $course['categoryid'];
587 // Summary format.
588 $course['summaryformat'] = external_validate_format($course['summaryformat']);
590 if (!empty($course['courseformatoptions'])) {
591 foreach ($course['courseformatoptions'] as $option) {
592 $course[$option['name']] = $option['value'];
596 //Note: create_course() core function check shortname, idnumber, category
597 $course['id'] = create_course((object) $course)->id;
599 $resultcourses[] = array('id' => $course['id'], 'shortname' => $course['shortname']);
602 $transaction->allow_commit();
604 return $resultcourses;
608 * Returns description of method result value
610 * @return external_description
611 * @since Moodle 2.2
613 public static function create_courses_returns() {
614 return new external_multiple_structure(
615 new external_single_structure(
616 array(
617 'id' => new external_value(PARAM_INT, 'course id'),
618 'shortname' => new external_value(PARAM_TEXT, 'short name'),
625 * Update courses
627 * @return external_function_parameters
628 * @since Moodle 2.5
630 public static function update_courses_parameters() {
631 return new external_function_parameters(
632 array(
633 'courses' => new external_multiple_structure(
634 new external_single_structure(
635 array(
636 'id' => new external_value(PARAM_INT, 'ID of the course'),
637 'fullname' => new external_value(PARAM_TEXT, 'full name', VALUE_OPTIONAL),
638 'shortname' => new external_value(PARAM_TEXT, 'course short name', VALUE_OPTIONAL),
639 'categoryid' => new external_value(PARAM_INT, 'category id', VALUE_OPTIONAL),
640 'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
641 'summary' => new external_value(PARAM_RAW, 'summary', VALUE_OPTIONAL),
642 'summaryformat' => new external_format_value('summary', VALUE_OPTIONAL),
643 'format' => new external_value(PARAM_PLUGIN,
644 'course format: weeks, topics, social, site,..', VALUE_OPTIONAL),
645 'showgrades' => new external_value(PARAM_INT,
646 '1 if grades are shown, otherwise 0', VALUE_OPTIONAL),
647 'newsitems' => new external_value(PARAM_INT,
648 'number of recent items appearing on the course page', VALUE_OPTIONAL),
649 'startdate' => new external_value(PARAM_INT,
650 'timestamp when the course start', VALUE_OPTIONAL),
651 'numsections' => new external_value(PARAM_INT,
652 '(deprecated, use courseformatoptions) number of weeks/topics', VALUE_OPTIONAL),
653 'maxbytes' => new external_value(PARAM_INT,
654 'largest size of file that can be uploaded into the course', VALUE_OPTIONAL),
655 'showreports' => new external_value(PARAM_INT,
656 'are activity report shown (yes = 1, no =0)', VALUE_OPTIONAL),
657 'visible' => new external_value(PARAM_INT,
658 '1: available to student, 0:not available', VALUE_OPTIONAL),
659 'hiddensections' => new external_value(PARAM_INT,
660 '(deprecated, use courseformatoptions) How the hidden sections in the course are
661 displayed to students', VALUE_OPTIONAL),
662 'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible', VALUE_OPTIONAL),
663 'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no', VALUE_OPTIONAL),
664 'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id', VALUE_OPTIONAL),
665 'enablecompletion' => new external_value(PARAM_INT,
666 'Enabled, control via completion and activity settings. Disabled,
667 not shown in activity settings.', VALUE_OPTIONAL),
668 'completionnotify' => new external_value(PARAM_INT, '1: yes 0: no', VALUE_OPTIONAL),
669 'lang' => new external_value(PARAM_SAFEDIR, 'forced course language', VALUE_OPTIONAL),
670 'forcetheme' => new external_value(PARAM_PLUGIN, 'name of the force theme', VALUE_OPTIONAL),
671 'courseformatoptions' => new external_multiple_structure(
672 new external_single_structure(
673 array('name' => new external_value(PARAM_ALPHANUMEXT, 'course format option name'),
674 'value' => new external_value(PARAM_RAW, 'course format option value')
676 'additional options for particular course format', VALUE_OPTIONAL),
678 ), 'courses to update'
685 * Update courses
687 * @param array $courses
688 * @since Moodle 2.5
690 public static function update_courses($courses) {
691 global $CFG, $DB;
692 require_once($CFG->dirroot . "/course/lib.php");
693 $warnings = array();
695 $params = self::validate_parameters(self::update_courses_parameters(),
696 array('courses' => $courses));
698 $availablethemes = core_component::get_plugin_list('theme');
699 $availablelangs = get_string_manager()->get_list_of_translations();
701 foreach ($params['courses'] as $course) {
702 // Catch any exception while updating course and return as warning to user.
703 try {
704 // Ensure the current user is allowed to run this function.
705 $context = context_course::instance($course['id'], MUST_EXIST);
706 self::validate_context($context);
708 $oldcourse = course_get_format($course['id'])->get_course();
710 require_capability('moodle/course:update', $context);
712 // Check if user can change category.
713 if (array_key_exists('categoryid', $course) && ($oldcourse->category != $course['categoryid'])) {
714 require_capability('moodle/course:changecategory', $context);
715 $course['category'] = $course['categoryid'];
718 // Check if the user can change fullname.
719 if (array_key_exists('fullname', $course) && ($oldcourse->fullname != $course['fullname'])) {
720 require_capability('moodle/course:changefullname', $context);
723 // Check if the user can change shortname.
724 if (array_key_exists('shortname', $course) && ($oldcourse->shortname != $course['shortname'])) {
725 require_capability('moodle/course:changeshortname', $context);
728 // Check if the user can change the idnumber.
729 if (array_key_exists('idnumber', $course) && ($oldcourse->idnumber != $course['idnumber'])) {
730 require_capability('moodle/course:changeidnumber', $context);
733 // Check if user can change summary.
734 if (array_key_exists('summary', $course) && ($oldcourse->summary != $course['summary'])) {
735 require_capability('moodle/course:changesummary', $context);
738 // Summary format.
739 if (array_key_exists('summaryformat', $course) && ($oldcourse->summaryformat != $course['summaryformat'])) {
740 require_capability('moodle/course:changesummary', $context);
741 $course['summaryformat'] = external_validate_format($course['summaryformat']);
744 // Check if user can change visibility.
745 if (array_key_exists('visible', $course) && ($oldcourse->visible != $course['visible'])) {
746 require_capability('moodle/course:visibility', $context);
749 // Make sure lang is valid.
750 if (array_key_exists('lang', $course) && empty($availablelangs[$course['lang']])) {
751 throw new moodle_exception('errorinvalidparam', 'webservice', '', 'lang');
754 // Make sure theme is valid.
755 if (array_key_exists('forcetheme', $course)) {
756 if (!empty($CFG->allowcoursethemes)) {
757 if (empty($availablethemes[$course['forcetheme']])) {
758 throw new moodle_exception('errorinvalidparam', 'webservice', '', 'forcetheme');
759 } else {
760 $course['theme'] = $course['forcetheme'];
765 // Make sure completion is enabled before setting it.
766 if (array_key_exists('enabledcompletion', $course) && !completion_info::is_enabled_for_site()) {
767 $course['enabledcompletion'] = 0;
770 // Make sure maxbytes are less then CFG->maxbytes.
771 if (array_key_exists('maxbytes', $course)) {
772 $course['maxbytes'] = get_max_upload_file_size($CFG->maxbytes, $course['maxbytes']);
775 if (!empty($course['courseformatoptions'])) {
776 foreach ($course['courseformatoptions'] as $option) {
777 if (isset($option['name']) && isset($option['value'])) {
778 $course[$option['name']] = $option['value'];
783 // Update course if user has all required capabilities.
784 update_course((object) $course);
785 } catch (Exception $e) {
786 $warning = array();
787 $warning['item'] = 'course';
788 $warning['itemid'] = $course['id'];
789 if ($e instanceof moodle_exception) {
790 $warning['warningcode'] = $e->errorcode;
791 } else {
792 $warning['warningcode'] = $e->getCode();
794 $warning['message'] = $e->getMessage();
795 $warnings[] = $warning;
799 $result = array();
800 $result['warnings'] = $warnings;
801 return $result;
805 * Returns description of method result value
807 * @return external_description
808 * @since Moodle 2.5
810 public static function update_courses_returns() {
811 return new external_single_structure(
812 array(
813 'warnings' => new external_warnings()
819 * Returns description of method parameters
821 * @return external_function_parameters
822 * @since Moodle 2.2
824 public static function delete_courses_parameters() {
825 return new external_function_parameters(
826 array(
827 'courseids' => new external_multiple_structure(new external_value(PARAM_INT, 'course ID')),
833 * Delete courses
835 * @param array $courseids A list of course ids
836 * @since Moodle 2.2
838 public static function delete_courses($courseids) {
839 global $CFG, $DB;
840 require_once($CFG->dirroot."/course/lib.php");
842 // Parameter validation.
843 $params = self::validate_parameters(self::delete_courses_parameters(), array('courseids'=>$courseids));
845 $transaction = $DB->start_delegated_transaction();
847 foreach ($params['courseids'] as $courseid) {
848 $course = $DB->get_record('course', array('id'=>$courseid), '*', MUST_EXIST);
850 // Check if the context is valid.
851 $coursecontext = context_course::instance($course->id);
852 self::validate_context($coursecontext);
854 // Check if the current user has enought permissions.
855 if (!can_delete_course($courseid)) {
856 fix_course_sortorder();
857 throw new moodle_exception('cannotdeletecategorycourse', 'error',
858 '', format_string($course->fullname)." (id: $courseid)");
861 delete_course($course, false);
864 $transaction->allow_commit();
865 fix_course_sortorder();
867 return null;
871 * Returns description of method result value
873 * @return external_description
874 * @since Moodle 2.2
876 public static function delete_courses_returns() {
877 return null;
881 * Returns description of method parameters
883 * @return external_function_parameters
884 * @since Moodle 2.3
886 public static function duplicate_course_parameters() {
887 return new external_function_parameters(
888 array(
889 'courseid' => new external_value(PARAM_INT, 'course to duplicate id'),
890 'fullname' => new external_value(PARAM_TEXT, 'duplicated course full name'),
891 'shortname' => new external_value(PARAM_TEXT, 'duplicated course short name'),
892 'categoryid' => new external_value(PARAM_INT, 'duplicated course category parent'),
893 'visible' => new external_value(PARAM_INT, 'duplicated course visible, default to yes', VALUE_DEFAULT, 1),
894 'options' => new external_multiple_structure(
895 new external_single_structure(
896 array(
897 'name' => new external_value(PARAM_ALPHAEXT, 'The backup option name:
898 "activities" (int) Include course activites (default to 1 that is equal to yes),
899 "blocks" (int) Include course blocks (default to 1 that is equal to yes),
900 "filters" (int) Include course filters (default to 1 that is equal to yes),
901 "users" (int) Include users (default to 0 that is equal to no),
902 "role_assignments" (int) Include role assignments (default to 0 that is equal to no),
903 "comments" (int) Include user comments (default to 0 that is equal to no),
904 "userscompletion" (int) Include user course completion information (default to 0 that is equal to no),
905 "logs" (int) Include course logs (default to 0 that is equal to no),
906 "grade_histories" (int) Include histories (default to 0 that is equal to no)'
908 'value' => new external_value(PARAM_RAW, 'the value for the option 1 (yes) or 0 (no)'
911 ), VALUE_DEFAULT, array()
918 * Duplicate a course
920 * @param int $courseid
921 * @param string $fullname Duplicated course fullname
922 * @param string $shortname Duplicated course shortname
923 * @param int $categoryid Duplicated course parent category id
924 * @param int $visible Duplicated course availability
925 * @param array $options List of backup options
926 * @return array New course info
927 * @since Moodle 2.3
929 public static function duplicate_course($courseid, $fullname, $shortname, $categoryid, $visible = 1, $options = array()) {
930 global $CFG, $USER, $DB;
931 require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
932 require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
934 // Parameter validation.
935 $params = self::validate_parameters(
936 self::duplicate_course_parameters(),
937 array(
938 'courseid' => $courseid,
939 'fullname' => $fullname,
940 'shortname' => $shortname,
941 'categoryid' => $categoryid,
942 'visible' => $visible,
943 'options' => $options
947 // Context validation.
949 if (! ($course = $DB->get_record('course', array('id'=>$params['courseid'])))) {
950 throw new moodle_exception('invalidcourseid', 'error');
953 // Category where duplicated course is going to be created.
954 $categorycontext = context_coursecat::instance($params['categoryid']);
955 self::validate_context($categorycontext);
957 // Course to be duplicated.
958 $coursecontext = context_course::instance($course->id);
959 self::validate_context($coursecontext);
961 $backupdefaults = array(
962 'activities' => 1,
963 'blocks' => 1,
964 'filters' => 1,
965 'users' => 0,
966 'role_assignments' => 0,
967 'comments' => 0,
968 'userscompletion' => 0,
969 'logs' => 0,
970 'grade_histories' => 0
973 $backupsettings = array();
974 // Check for backup and restore options.
975 if (!empty($params['options'])) {
976 foreach ($params['options'] as $option) {
978 // Strict check for a correct value (allways 1 or 0, true or false).
979 $value = clean_param($option['value'], PARAM_INT);
981 if ($value !== 0 and $value !== 1) {
982 throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
985 if (!isset($backupdefaults[$option['name']])) {
986 throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
989 $backupsettings[$option['name']] = $value;
993 // Capability checking.
995 // The backup controller check for this currently, this may be redundant.
996 require_capability('moodle/course:create', $categorycontext);
997 require_capability('moodle/restore:restorecourse', $categorycontext);
998 require_capability('moodle/backup:backupcourse', $coursecontext);
1000 if (!empty($backupsettings['users'])) {
1001 require_capability('moodle/backup:userinfo', $coursecontext);
1002 require_capability('moodle/restore:userinfo', $categorycontext);
1005 // Check if the shortname is used.
1006 if ($foundcourses = $DB->get_records('course', array('shortname'=>$shortname))) {
1007 foreach ($foundcourses as $foundcourse) {
1008 $foundcoursenames[] = $foundcourse->fullname;
1011 $foundcoursenamestring = implode(',', $foundcoursenames);
1012 throw new moodle_exception('shortnametaken', '', '', $foundcoursenamestring);
1015 // Backup the course.
1017 $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
1018 backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $USER->id);
1020 foreach ($backupsettings as $name => $value) {
1021 $bc->get_plan()->get_setting($name)->set_value($value);
1024 $backupid = $bc->get_backupid();
1025 $backupbasepath = $bc->get_plan()->get_basepath();
1027 $bc->execute_plan();
1028 $results = $bc->get_results();
1029 $file = $results['backup_destination'];
1031 $bc->destroy();
1033 // Restore the backup immediately.
1035 // Check if we need to unzip the file because the backup temp dir does not contains backup files.
1036 if (!file_exists($backupbasepath . "/moodle_backup.xml")) {
1037 $file->extract_to_pathname(get_file_packer('application/vnd.moodle.backup'), $backupbasepath);
1040 // Create new course.
1041 $newcourseid = restore_dbops::create_new_course($params['fullname'], $params['shortname'], $params['categoryid']);
1043 $rc = new restore_controller($backupid, $newcourseid,
1044 backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $USER->id, backup::TARGET_NEW_COURSE);
1046 foreach ($backupsettings as $name => $value) {
1047 $setting = $rc->get_plan()->get_setting($name);
1048 if ($setting->get_status() == backup_setting::NOT_LOCKED) {
1049 $setting->set_value($value);
1053 if (!$rc->execute_precheck()) {
1054 $precheckresults = $rc->get_precheck_results();
1055 if (is_array($precheckresults) && !empty($precheckresults['errors'])) {
1056 if (empty($CFG->keeptempdirectoriesonbackup)) {
1057 fulldelete($backupbasepath);
1060 $errorinfo = '';
1062 foreach ($precheckresults['errors'] as $error) {
1063 $errorinfo .= $error;
1066 if (array_key_exists('warnings', $precheckresults)) {
1067 foreach ($precheckresults['warnings'] as $warning) {
1068 $errorinfo .= $warning;
1072 throw new moodle_exception('backupprecheckerrors', 'webservice', '', $errorinfo);
1076 $rc->execute_plan();
1077 $rc->destroy();
1079 $course = $DB->get_record('course', array('id' => $newcourseid), '*', MUST_EXIST);
1080 $course->fullname = $params['fullname'];
1081 $course->shortname = $params['shortname'];
1082 $course->visible = $params['visible'];
1084 // Set shortname and fullname back.
1085 $DB->update_record('course', $course);
1087 if (empty($CFG->keeptempdirectoriesonbackup)) {
1088 fulldelete($backupbasepath);
1091 // Delete the course backup file created by this WebService. Originally located in the course backups area.
1092 $file->delete();
1094 return array('id' => $course->id, 'shortname' => $course->shortname);
1098 * Returns description of method result value
1100 * @return external_description
1101 * @since Moodle 2.3
1103 public static function duplicate_course_returns() {
1104 return new external_single_structure(
1105 array(
1106 'id' => new external_value(PARAM_INT, 'course id'),
1107 'shortname' => new external_value(PARAM_TEXT, 'short name'),
1113 * Returns description of method parameters for import_course
1115 * @return external_function_parameters
1116 * @since Moodle 2.4
1118 public static function import_course_parameters() {
1119 return new external_function_parameters(
1120 array(
1121 'importfrom' => new external_value(PARAM_INT, 'the id of the course we are importing from'),
1122 'importto' => new external_value(PARAM_INT, 'the id of the course we are importing to'),
1123 'deletecontent' => new external_value(PARAM_INT, 'whether to delete the course content where we are importing to (default to 0 = No)', VALUE_DEFAULT, 0),
1124 'options' => new external_multiple_structure(
1125 new external_single_structure(
1126 array(
1127 'name' => new external_value(PARAM_ALPHA, 'The backup option name:
1128 "activities" (int) Include course activites (default to 1 that is equal to yes),
1129 "blocks" (int) Include course blocks (default to 1 that is equal to yes),
1130 "filters" (int) Include course filters (default to 1 that is equal to yes)'
1132 'value' => new external_value(PARAM_RAW, 'the value for the option 1 (yes) or 0 (no)'
1135 ), VALUE_DEFAULT, array()
1142 * Imports a course
1144 * @param int $importfrom The id of the course we are importing from
1145 * @param int $importto The id of the course we are importing to
1146 * @param bool $deletecontent Whether to delete the course we are importing to content
1147 * @param array $options List of backup options
1148 * @return null
1149 * @since Moodle 2.4
1151 public static function import_course($importfrom, $importto, $deletecontent = 0, $options = array()) {
1152 global $CFG, $USER, $DB;
1153 require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
1154 require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
1156 // Parameter validation.
1157 $params = self::validate_parameters(
1158 self::import_course_parameters(),
1159 array(
1160 'importfrom' => $importfrom,
1161 'importto' => $importto,
1162 'deletecontent' => $deletecontent,
1163 'options' => $options
1167 if ($params['deletecontent'] !== 0 and $params['deletecontent'] !== 1) {
1168 throw new moodle_exception('invalidextparam', 'webservice', '', $params['deletecontent']);
1171 // Context validation.
1173 if (! ($importfrom = $DB->get_record('course', array('id'=>$params['importfrom'])))) {
1174 throw new moodle_exception('invalidcourseid', 'error');
1177 if (! ($importto = $DB->get_record('course', array('id'=>$params['importto'])))) {
1178 throw new moodle_exception('invalidcourseid', 'error');
1181 $importfromcontext = context_course::instance($importfrom->id);
1182 self::validate_context($importfromcontext);
1184 $importtocontext = context_course::instance($importto->id);
1185 self::validate_context($importtocontext);
1187 $backupdefaults = array(
1188 'activities' => 1,
1189 'blocks' => 1,
1190 'filters' => 1
1193 $backupsettings = array();
1195 // Check for backup and restore options.
1196 if (!empty($params['options'])) {
1197 foreach ($params['options'] as $option) {
1199 // Strict check for a correct value (allways 1 or 0, true or false).
1200 $value = clean_param($option['value'], PARAM_INT);
1202 if ($value !== 0 and $value !== 1) {
1203 throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
1206 if (!isset($backupdefaults[$option['name']])) {
1207 throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
1210 $backupsettings[$option['name']] = $value;
1214 // Capability checking.
1216 require_capability('moodle/backup:backuptargetimport', $importfromcontext);
1217 require_capability('moodle/restore:restoretargetimport', $importtocontext);
1219 $bc = new backup_controller(backup::TYPE_1COURSE, $importfrom->id, backup::FORMAT_MOODLE,
1220 backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id);
1222 foreach ($backupsettings as $name => $value) {
1223 $bc->get_plan()->get_setting($name)->set_value($value);
1226 $backupid = $bc->get_backupid();
1227 $backupbasepath = $bc->get_plan()->get_basepath();
1229 $bc->execute_plan();
1230 $bc->destroy();
1232 // Restore the backup immediately.
1234 // Check if we must delete the contents of the destination course.
1235 if ($params['deletecontent']) {
1236 $restoretarget = backup::TARGET_EXISTING_DELETING;
1237 } else {
1238 $restoretarget = backup::TARGET_EXISTING_ADDING;
1241 $rc = new restore_controller($backupid, $importto->id,
1242 backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id, $restoretarget);
1244 foreach ($backupsettings as $name => $value) {
1245 $rc->get_plan()->get_setting($name)->set_value($value);
1248 if (!$rc->execute_precheck()) {
1249 $precheckresults = $rc->get_precheck_results();
1250 if (is_array($precheckresults) && !empty($precheckresults['errors'])) {
1251 if (empty($CFG->keeptempdirectoriesonbackup)) {
1252 fulldelete($backupbasepath);
1255 $errorinfo = '';
1257 foreach ($precheckresults['errors'] as $error) {
1258 $errorinfo .= $error;
1261 if (array_key_exists('warnings', $precheckresults)) {
1262 foreach ($precheckresults['warnings'] as $warning) {
1263 $errorinfo .= $warning;
1267 throw new moodle_exception('backupprecheckerrors', 'webservice', '', $errorinfo);
1269 } else {
1270 if ($restoretarget == backup::TARGET_EXISTING_DELETING) {
1271 restore_dbops::delete_course_content($importto->id);
1275 $rc->execute_plan();
1276 $rc->destroy();
1278 if (empty($CFG->keeptempdirectoriesonbackup)) {
1279 fulldelete($backupbasepath);
1282 return null;
1286 * Returns description of method result value
1288 * @return external_description
1289 * @since Moodle 2.4
1291 public static function import_course_returns() {
1292 return null;
1296 * Returns description of method parameters
1298 * @return external_function_parameters
1299 * @since Moodle 2.3
1301 public static function get_categories_parameters() {
1302 return new external_function_parameters(
1303 array(
1304 'criteria' => new external_multiple_structure(
1305 new external_single_structure(
1306 array(
1307 'key' => new external_value(PARAM_ALPHA,
1308 'The category column to search, expected keys (value format) are:'.
1309 '"id" (int) the category id,'.
1310 '"name" (string) the category name,'.
1311 '"parent" (int) the parent category id,'.
1312 '"idnumber" (string) category idnumber'.
1313 ' - user must have \'moodle/category:manage\' to search on idnumber,'.
1314 '"visible" (int) whether the returned categories must be visible or hidden. If the key is not passed,
1315 then the function return all categories that the user can see.'.
1316 ' - user must have \'moodle/category:manage\' or \'moodle/category:viewhiddencategories\' to search on visible,'.
1317 '"theme" (string) only return the categories having this theme'.
1318 ' - user must have \'moodle/category:manage\' to search on theme'),
1319 'value' => new external_value(PARAM_RAW, 'the value to match')
1321 ), 'criteria', VALUE_DEFAULT, array()
1323 'addsubcategories' => new external_value(PARAM_BOOL, 'return the sub categories infos
1324 (1 - default) otherwise only the category info (0)', VALUE_DEFAULT, 1)
1330 * Get categories
1332 * @param array $criteria Criteria to match the results
1333 * @param booln $addsubcategories obtain only the category (false) or its subcategories (true - default)
1334 * @return array list of categories
1335 * @since Moodle 2.3
1337 public static function get_categories($criteria = array(), $addsubcategories = true) {
1338 global $CFG, $DB;
1339 require_once($CFG->dirroot . "/course/lib.php");
1341 // Validate parameters.
1342 $params = self::validate_parameters(self::get_categories_parameters(),
1343 array('criteria' => $criteria, 'addsubcategories' => $addsubcategories));
1345 // Retrieve the categories.
1346 $categories = array();
1347 if (!empty($params['criteria'])) {
1349 $conditions = array();
1350 $wheres = array();
1351 foreach ($params['criteria'] as $crit) {
1352 $key = trim($crit['key']);
1354 // Trying to avoid duplicate keys.
1355 if (!isset($conditions[$key])) {
1357 $context = context_system::instance();
1358 $value = null;
1359 switch ($key) {
1360 case 'id':
1361 $value = clean_param($crit['value'], PARAM_INT);
1362 break;
1364 case 'idnumber':
1365 if (has_capability('moodle/category:manage', $context)) {
1366 $value = clean_param($crit['value'], PARAM_RAW);
1367 } else {
1368 // We must throw an exception.
1369 // Otherwise the dev client would think no idnumber exists.
1370 throw new moodle_exception('criteriaerror',
1371 'webservice', '', null,
1372 'You don\'t have the permissions to search on the "idnumber" field.');
1374 break;
1376 case 'name':
1377 $value = clean_param($crit['value'], PARAM_TEXT);
1378 break;
1380 case 'parent':
1381 $value = clean_param($crit['value'], PARAM_INT);
1382 break;
1384 case 'visible':
1385 if (has_capability('moodle/category:manage', $context)
1386 or has_capability('moodle/category:viewhiddencategories',
1387 context_system::instance())) {
1388 $value = clean_param($crit['value'], PARAM_INT);
1389 } else {
1390 throw new moodle_exception('criteriaerror',
1391 'webservice', '', null,
1392 'You don\'t have the permissions to search on the "visible" field.');
1394 break;
1396 case 'theme':
1397 if (has_capability('moodle/category:manage', $context)) {
1398 $value = clean_param($crit['value'], PARAM_THEME);
1399 } else {
1400 throw new moodle_exception('criteriaerror',
1401 'webservice', '', null,
1402 'You don\'t have the permissions to search on the "theme" field.');
1404 break;
1406 default:
1407 throw new moodle_exception('criteriaerror',
1408 'webservice', '', null,
1409 'You can not search on this criteria: ' . $key);
1412 if (isset($value)) {
1413 $conditions[$key] = $crit['value'];
1414 $wheres[] = $key . " = :" . $key;
1419 if (!empty($wheres)) {
1420 $wheres = implode(" AND ", $wheres);
1422 $categories = $DB->get_records_select('course_categories', $wheres, $conditions);
1424 // Retrieve its sub subcategories (all levels).
1425 if ($categories and !empty($params['addsubcategories'])) {
1426 $newcategories = array();
1428 // Check if we required visible/theme checks.
1429 $additionalselect = '';
1430 $additionalparams = array();
1431 if (isset($conditions['visible'])) {
1432 $additionalselect .= ' AND visible = :visible';
1433 $additionalparams['visible'] = $conditions['visible'];
1435 if (isset($conditions['theme'])) {
1436 $additionalselect .= ' AND theme= :theme';
1437 $additionalparams['theme'] = $conditions['theme'];
1440 foreach ($categories as $category) {
1441 $sqlselect = $DB->sql_like('path', ':path') . $additionalselect;
1442 $sqlparams = array('path' => $category->path.'/%') + $additionalparams; // It will NOT include the specified category.
1443 $subcategories = $DB->get_records_select('course_categories', $sqlselect, $sqlparams);
1444 $newcategories = $newcategories + $subcategories; // Both arrays have integer as keys.
1446 $categories = $categories + $newcategories;
1450 } else {
1451 // Retrieve all categories in the database.
1452 $categories = $DB->get_records('course_categories');
1455 // The not returned categories. key => category id, value => reason of exclusion.
1456 $excludedcats = array();
1458 // The returned categories.
1459 $categoriesinfo = array();
1461 // We need to sort the categories by path.
1462 // The parent cats need to be checked by the algo first.
1463 usort($categories, "core_course_external::compare_categories_by_path");
1465 foreach ($categories as $category) {
1467 // Check if the category is a child of an excluded category, if yes exclude it too (excluded => do not return).
1468 $parents = explode('/', $category->path);
1469 unset($parents[0]); // First key is always empty because path start with / => /1/2/4.
1470 foreach ($parents as $parentid) {
1471 // Note: when the parent exclusion was due to the context,
1472 // the sub category could still be returned.
1473 if (isset($excludedcats[$parentid]) and $excludedcats[$parentid] != 'context') {
1474 $excludedcats[$category->id] = 'parent';
1478 // Check category depth is <= maxdepth (do not check for user who can manage categories).
1479 if ((!empty($CFG->maxcategorydepth) && count($parents) > $CFG->maxcategorydepth)
1480 and !has_capability('moodle/category:manage', $context)) {
1481 $excludedcats[$category->id] = 'depth';
1484 // Check the user can use the category context.
1485 $context = context_coursecat::instance($category->id);
1486 try {
1487 self::validate_context($context);
1488 } catch (Exception $e) {
1489 $excludedcats[$category->id] = 'context';
1491 // If it was the requested category then throw an exception.
1492 if (isset($params['categoryid']) && $category->id == $params['categoryid']) {
1493 $exceptionparam = new stdClass();
1494 $exceptionparam->message = $e->getMessage();
1495 $exceptionparam->catid = $category->id;
1496 throw new moodle_exception('errorcatcontextnotvalid', 'webservice', '', $exceptionparam);
1500 // Return the category information.
1501 if (!isset($excludedcats[$category->id])) {
1503 // Final check to see if the category is visible to the user.
1504 if ($category->visible
1505 or has_capability('moodle/category:viewhiddencategories', context_system::instance())
1506 or has_capability('moodle/category:manage', $context)) {
1508 $categoryinfo = array();
1509 $categoryinfo['id'] = $category->id;
1510 $categoryinfo['name'] = $category->name;
1511 list($categoryinfo['description'], $categoryinfo['descriptionformat']) =
1512 external_format_text($category->description, $category->descriptionformat,
1513 $context->id, 'coursecat', 'description', null);
1514 $categoryinfo['parent'] = $category->parent;
1515 $categoryinfo['sortorder'] = $category->sortorder;
1516 $categoryinfo['coursecount'] = $category->coursecount;
1517 $categoryinfo['depth'] = $category->depth;
1518 $categoryinfo['path'] = $category->path;
1520 // Some fields only returned for admin.
1521 if (has_capability('moodle/category:manage', $context)) {
1522 $categoryinfo['idnumber'] = $category->idnumber;
1523 $categoryinfo['visible'] = $category->visible;
1524 $categoryinfo['visibleold'] = $category->visibleold;
1525 $categoryinfo['timemodified'] = $category->timemodified;
1526 $categoryinfo['theme'] = $category->theme;
1529 $categoriesinfo[] = $categoryinfo;
1530 } else {
1531 $excludedcats[$category->id] = 'visibility';
1536 // Sorting the resulting array so it looks a bit better for the client developer.
1537 usort($categoriesinfo, "core_course_external::compare_categories_by_sortorder");
1539 return $categoriesinfo;
1543 * Sort categories array by path
1544 * private function: only used by get_categories
1546 * @param array $category1
1547 * @param array $category2
1548 * @return int result of strcmp
1549 * @since Moodle 2.3
1551 private static function compare_categories_by_path($category1, $category2) {
1552 return strcmp($category1->path, $category2->path);
1556 * Sort categories array by sortorder
1557 * private function: only used by get_categories
1559 * @param array $category1
1560 * @param array $category2
1561 * @return int result of strcmp
1562 * @since Moodle 2.3
1564 private static function compare_categories_by_sortorder($category1, $category2) {
1565 return strcmp($category1['sortorder'], $category2['sortorder']);
1569 * Returns description of method result value
1571 * @return external_description
1572 * @since Moodle 2.3
1574 public static function get_categories_returns() {
1575 return new external_multiple_structure(
1576 new external_single_structure(
1577 array(
1578 'id' => new external_value(PARAM_INT, 'category id'),
1579 'name' => new external_value(PARAM_TEXT, 'category name'),
1580 'idnumber' => new external_value(PARAM_RAW, 'category id number', VALUE_OPTIONAL),
1581 'description' => new external_value(PARAM_RAW, 'category description'),
1582 'descriptionformat' => new external_format_value('description'),
1583 'parent' => new external_value(PARAM_INT, 'parent category id'),
1584 'sortorder' => new external_value(PARAM_INT, 'category sorting order'),
1585 'coursecount' => new external_value(PARAM_INT, 'number of courses in this category'),
1586 'visible' => new external_value(PARAM_INT, '1: available, 0:not available', VALUE_OPTIONAL),
1587 'visibleold' => new external_value(PARAM_INT, '1: available, 0:not available', VALUE_OPTIONAL),
1588 'timemodified' => new external_value(PARAM_INT, 'timestamp', VALUE_OPTIONAL),
1589 'depth' => new external_value(PARAM_INT, 'category depth'),
1590 'path' => new external_value(PARAM_TEXT, 'category path'),
1591 'theme' => new external_value(PARAM_THEME, 'category theme', VALUE_OPTIONAL),
1592 ), 'List of categories'
1598 * Returns description of method parameters
1600 * @return external_function_parameters
1601 * @since Moodle 2.3
1603 public static function create_categories_parameters() {
1604 return new external_function_parameters(
1605 array(
1606 'categories' => new external_multiple_structure(
1607 new external_single_structure(
1608 array(
1609 'name' => new external_value(PARAM_TEXT, 'new category name'),
1610 'parent' => new external_value(PARAM_INT,
1611 'the parent category id inside which the new category will be created
1612 - set to 0 for a root category',
1613 VALUE_DEFAULT, 0),
1614 'idnumber' => new external_value(PARAM_RAW,
1615 'the new category idnumber', VALUE_OPTIONAL),
1616 'description' => new external_value(PARAM_RAW,
1617 'the new category description', VALUE_OPTIONAL),
1618 'descriptionformat' => new external_format_value('description', VALUE_DEFAULT),
1619 'theme' => new external_value(PARAM_THEME,
1620 'the new category theme. This option must be enabled on moodle',
1621 VALUE_OPTIONAL),
1630 * Create categories
1632 * @param array $categories - see create_categories_parameters() for the array structure
1633 * @return array - see create_categories_returns() for the array structure
1634 * @since Moodle 2.3
1636 public static function create_categories($categories) {
1637 global $CFG, $DB;
1638 require_once($CFG->libdir . "/coursecatlib.php");
1640 $params = self::validate_parameters(self::create_categories_parameters(),
1641 array('categories' => $categories));
1643 $transaction = $DB->start_delegated_transaction();
1645 $createdcategories = array();
1646 foreach ($params['categories'] as $category) {
1647 if ($category['parent']) {
1648 if (!$DB->record_exists('course_categories', array('id' => $category['parent']))) {
1649 throw new moodle_exception('unknowcategory');
1651 $context = context_coursecat::instance($category['parent']);
1652 } else {
1653 $context = context_system::instance();
1655 self::validate_context($context);
1656 require_capability('moodle/category:manage', $context);
1658 // this will validate format and throw an exception if there are errors
1659 external_validate_format($category['descriptionformat']);
1661 $newcategory = coursecat::create($category);
1663 $createdcategories[] = array('id' => $newcategory->id, 'name' => $newcategory->name);
1666 $transaction->allow_commit();
1668 return $createdcategories;
1672 * Returns description of method parameters
1674 * @return external_function_parameters
1675 * @since Moodle 2.3
1677 public static function create_categories_returns() {
1678 return new external_multiple_structure(
1679 new external_single_structure(
1680 array(
1681 'id' => new external_value(PARAM_INT, 'new category id'),
1682 'name' => new external_value(PARAM_TEXT, 'new category name'),
1689 * Returns description of method parameters
1691 * @return external_function_parameters
1692 * @since Moodle 2.3
1694 public static function update_categories_parameters() {
1695 return new external_function_parameters(
1696 array(
1697 'categories' => new external_multiple_structure(
1698 new external_single_structure(
1699 array(
1700 'id' => new external_value(PARAM_INT, 'course id'),
1701 'name' => new external_value(PARAM_TEXT, 'category name', VALUE_OPTIONAL),
1702 'idnumber' => new external_value(PARAM_RAW, 'category id number', VALUE_OPTIONAL),
1703 'parent' => new external_value(PARAM_INT, 'parent category id', VALUE_OPTIONAL),
1704 'description' => new external_value(PARAM_RAW, 'category description', VALUE_OPTIONAL),
1705 'descriptionformat' => new external_format_value('description', VALUE_DEFAULT),
1706 'theme' => new external_value(PARAM_THEME,
1707 'the category theme. This option must be enabled on moodle', VALUE_OPTIONAL),
1716 * Update categories
1718 * @param array $categories The list of categories to update
1719 * @return null
1720 * @since Moodle 2.3
1722 public static function update_categories($categories) {
1723 global $CFG, $DB;
1724 require_once($CFG->libdir . "/coursecatlib.php");
1726 // Validate parameters.
1727 $params = self::validate_parameters(self::update_categories_parameters(), array('categories' => $categories));
1729 $transaction = $DB->start_delegated_transaction();
1731 foreach ($params['categories'] as $cat) {
1732 $category = coursecat::get($cat['id']);
1734 $categorycontext = context_coursecat::instance($cat['id']);
1735 self::validate_context($categorycontext);
1736 require_capability('moodle/category:manage', $categorycontext);
1738 // this will throw an exception if descriptionformat is not valid
1739 external_validate_format($cat['descriptionformat']);
1741 $category->update($cat);
1744 $transaction->allow_commit();
1748 * Returns description of method result value
1750 * @return external_description
1751 * @since Moodle 2.3
1753 public static function update_categories_returns() {
1754 return null;
1758 * Returns description of method parameters
1760 * @return external_function_parameters
1761 * @since Moodle 2.3
1763 public static function delete_categories_parameters() {
1764 return new external_function_parameters(
1765 array(
1766 'categories' => new external_multiple_structure(
1767 new external_single_structure(
1768 array(
1769 'id' => new external_value(PARAM_INT, 'category id to delete'),
1770 'newparent' => new external_value(PARAM_INT,
1771 'the parent category to move the contents to, if specified', VALUE_OPTIONAL),
1772 'recursive' => new external_value(PARAM_BOOL, '1: recursively delete all contents inside this
1773 category, 0 (default): move contents to newparent or current parent category (except if parent is root)', VALUE_DEFAULT, 0)
1782 * Delete categories
1784 * @param array $categories A list of category ids
1785 * @return array
1786 * @since Moodle 2.3
1788 public static function delete_categories($categories) {
1789 global $CFG, $DB;
1790 require_once($CFG->dirroot . "/course/lib.php");
1791 require_once($CFG->libdir . "/coursecatlib.php");
1793 // Validate parameters.
1794 $params = self::validate_parameters(self::delete_categories_parameters(), array('categories' => $categories));
1796 $transaction = $DB->start_delegated_transaction();
1798 foreach ($params['categories'] as $category) {
1799 $deletecat = coursecat::get($category['id'], MUST_EXIST);
1800 $context = context_coursecat::instance($deletecat->id);
1801 require_capability('moodle/category:manage', $context);
1802 self::validate_context($context);
1803 self::validate_context(get_category_or_system_context($deletecat->parent));
1805 if ($category['recursive']) {
1806 // If recursive was specified, then we recursively delete the category's contents.
1807 if ($deletecat->can_delete_full()) {
1808 $deletecat->delete_full(false);
1809 } else {
1810 throw new moodle_exception('youcannotdeletecategory', '', '', $deletecat->get_formatted_name());
1812 } else {
1813 // In this situation, we don't delete the category's contents, we either move it to newparent or parent.
1814 // If the parent is the root, moving is not supported (because a course must always be inside a category).
1815 // We must move to an existing category.
1816 if (!empty($category['newparent'])) {
1817 $newparentcat = coursecat::get($category['newparent']);
1818 } else {
1819 $newparentcat = coursecat::get($deletecat->parent);
1822 // This operation is not allowed. We must move contents to an existing category.
1823 if (!$newparentcat->id) {
1824 throw new moodle_exception('movecatcontentstoroot');
1827 self::validate_context(context_coursecat::instance($newparentcat->id));
1828 if ($deletecat->can_move_content_to($newparentcat->id)) {
1829 $deletecat->delete_move($newparentcat->id, false);
1830 } else {
1831 throw new moodle_exception('youcannotdeletecategory', '', '', $deletecat->get_formatted_name());
1836 $transaction->allow_commit();
1840 * Returns description of method parameters
1842 * @return external_function_parameters
1843 * @since Moodle 2.3
1845 public static function delete_categories_returns() {
1846 return null;
1850 * Describes the parameters for delete_modules.
1852 * @return external_external_function_parameters
1853 * @since Moodle 2.5
1855 public static function delete_modules_parameters() {
1856 return new external_function_parameters (
1857 array(
1858 'cmids' => new external_multiple_structure(new external_value(PARAM_INT, 'course module ID',
1859 VALUE_REQUIRED, '', NULL_NOT_ALLOWED), 'Array of course module IDs'),
1865 * Deletes a list of provided module instances.
1867 * @param array $cmids the course module ids
1868 * @since Moodle 2.5
1870 public static function delete_modules($cmids) {
1871 global $CFG, $DB;
1873 // Require course file containing the course delete module function.
1874 require_once($CFG->dirroot . "/course/lib.php");
1876 // Clean the parameters.
1877 $params = self::validate_parameters(self::delete_modules_parameters(), array('cmids' => $cmids));
1879 // Keep track of the course ids we have performed a capability check on to avoid repeating.
1880 $arrcourseschecked = array();
1882 foreach ($params['cmids'] as $cmid) {
1883 // Get the course module.
1884 $cm = $DB->get_record('course_modules', array('id' => $cmid), '*', MUST_EXIST);
1886 // Check if we have not yet confirmed they have permission in this course.
1887 if (!in_array($cm->course, $arrcourseschecked)) {
1888 // Ensure the current user has required permission in this course.
1889 $context = context_course::instance($cm->course);
1890 self::validate_context($context);
1891 // Add to the array.
1892 $arrcourseschecked[] = $cm->course;
1895 // Ensure they can delete this module.
1896 $modcontext = context_module::instance($cm->id);
1897 require_capability('moodle/course:manageactivities', $modcontext);
1899 // Delete the module.
1900 course_delete_module($cm->id);
1905 * Describes the delete_modules return value.
1907 * @return external_single_structure
1908 * @since Moodle 2.5
1910 public static function delete_modules_returns() {
1911 return null;
1916 * Deprecated course external functions
1918 * @package core_course
1919 * @copyright 2009 Petr Skodak
1920 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1921 * @since Moodle 2.0
1922 * @deprecated Moodle 2.2 MDL-29106 - Please do not use this class any more.
1923 * @see core_course_external
1925 class moodle_course_external extends external_api {
1928 * Returns description of method parameters
1930 * @return external_function_parameters
1931 * @since Moodle 2.0
1932 * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
1933 * @see core_course_external::get_courses_parameters()
1935 public static function get_courses_parameters() {
1936 return core_course_external::get_courses_parameters();
1940 * Get courses
1942 * @param array $options
1943 * @return array
1944 * @since Moodle 2.0
1945 * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
1946 * @see core_course_external::get_courses()
1948 public static function get_courses($options) {
1949 return core_course_external::get_courses($options);
1953 * Returns description of method result value
1955 * @return external_description
1956 * @since Moodle 2.0
1957 * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
1958 * @see core_course_external::get_courses_returns()
1960 public static function get_courses_returns() {
1961 return core_course_external::get_courses_returns();
1965 * Returns description of method parameters
1967 * @return external_function_parameters
1968 * @since Moodle 2.0
1969 * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
1970 * @see core_course_external::create_courses_parameters()
1972 public static function create_courses_parameters() {
1973 return core_course_external::create_courses_parameters();
1977 * Create courses
1979 * @param array $courses
1980 * @return array courses (id and shortname only)
1981 * @since Moodle 2.0
1982 * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
1983 * @see core_course_external::create_courses()
1985 public static function create_courses($courses) {
1986 return core_course_external::create_courses($courses);
1990 * Returns description of method result value
1992 * @return external_description
1993 * @since Moodle 2.0
1994 * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
1995 * @see core_course_external::create_courses_returns()
1997 public static function create_courses_returns() {
1998 return core_course_external::create_courses_returns();