5017145a762f7e06e359fd103a76756c11e99c94
[moodle.git] / course / externallib.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
18 /**
19  * External course API
20  *
21  * @package    core_course
22  * @category   external
23  * @copyright  2009 Petr Skodak
24  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25  */
27 defined('MOODLE_INTERNAL') || die;
29 require_once("$CFG->libdir/externallib.php");
31 /**
32  * Course external functions
33  *
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
39  */
40 class core_course_external extends external_api {
42     /**
43      * Returns description of method parameters
44      *
45      * @return external_function_parameters
46      * @since Moodle 2.2
47      */
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.')
55                               )
56                       ), 'Options, not used yet, might be used in later version', VALUE_DEFAULT, array())
57                 )
58         );
59     }
61     /**
62      * Get course contents
63      *
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
68      */
69     public static function get_course_contents($courseid, $options) {
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         //check course format exist
81         if (!file_exists($CFG->dirroot . '/course/format/' . $course->format . '/lib.php')) {
82             throw new moodle_exception('cannotgetcoursecontents', 'webservice', '', null, get_string('courseformatnotfound', 'error', '', $course->format));
83         } else {
84             require_once($CFG->dirroot . '/course/format/' . $course->format . '/lib.php');
85         }
87         // now security checks
88         $context = get_context_instance(CONTEXT_COURSE, $course->id);
89         try {
90             self::validate_context($context);
91         } catch (Exception $e) {
92             $exceptionparam = new stdClass();
93             $exceptionparam->message = $e->getMessage();
94             $exceptionparam->courseid = $course->id;
95             throw new moodle_exception('errorcoursecontextnotvalid', 'webservice', '', $exceptionparam);
96         }
98         $canupdatecourse = has_capability('moodle/course:update', $context);
100         //create return value
101         $coursecontents = array();
103         if ($canupdatecourse or $course->visible
104                 or has_capability('moodle/course:viewhiddencourses', $context)) {
106             //retrieve sections
107             $modinfo = get_fast_modinfo($course);
108             $sections = get_all_sections($course->id);
110             //for each sections (first displayed to last displayed)
111             foreach ($sections as $key => $section) {
113                 $showsection = (has_capability('moodle/course:viewhiddensections', $context) or $section->visible or !$course->hiddensections);
114                 if (!$showsection) {
115                     continue;
116                 }
118                 // reset $sectioncontents
119                 $sectionvalues = array();
120                 $sectionvalues['id'] = $section->id;
121                 $sectionvalues['name'] = get_section_name($course, $section);
122                 $summary = file_rewrite_pluginfile_urls($section->summary, 'webservice/pluginfile.php', $context->id, 'course', 'section', $section->id);
123                 $sectionvalues['visible'] = $section->visible;
124                 $sectionvalues['summary'] = format_text($summary, $section->summaryformat);
125                 $sectioncontents = array();
127                 //for each module of the section
128                 foreach ($modinfo->sections[$section->section] as $cmid) { //matching /course/lib.php:print_section() logic
129                     $cm = $modinfo->cms[$cmid];
131                     // stop here if the module is not visible to the user
132                     if (!$cm->uservisible) {
133                         continue;
134                     }
136                     $module = array();
138                     //common info (for people being able to see the module or availability dates)
139                     $module['id'] = $cm->id;
140                     $module['name'] = format_string($cm->name, true);
141                     $module['modname'] = $cm->modname;
142                     $module['modplural'] = $cm->modplural;
143                     $module['modicon'] = $cm->get_icon_url()->out(false);
144                     $module['indent'] = $cm->indent;
146                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
148                     if (!empty($cm->showdescription)) {
149                         $module['description'] = $cm->get_content();
150                     }
152                     //url of the module
153                     $url = $cm->get_url();
154                     if ($url) { //labels don't have url
155                         $module['url'] = $cm->get_url()->out();
156                     }
158                     $canviewhidden = has_capability('moodle/course:viewhiddenactivities',
159                                         get_context_instance(CONTEXT_MODULE, $cm->id));
160                     //user that can view hidden module should know about the visibility
161                     $module['visible'] = $cm->visible;
163                     //availability date (also send to user who can see hidden module when the showavailabilyt is ON)
164                     if ($canupdatecourse or ($CFG->enableavailability && $canviewhidden && $cm->showavailability)) {
165                         $module['availablefrom'] = $cm->availablefrom;
166                         $module['availableuntil'] = $cm->availableuntil;
167                     }
169                     $baseurl = 'webservice/pluginfile.php';
171                     //call $modulename_export_contents
172                     //(each module callback take care about checking the capabilities)
173                     require_once($CFG->dirroot . '/mod/' . $cm->modname . '/lib.php');
174                     $getcontentfunction = $cm->modname.'_export_contents';
175                     if (function_exists($getcontentfunction)) {
176                         if ($contents = $getcontentfunction($cm, $baseurl)) {
177                             $module['contents'] = $contents;
178                         }
179                     }
181                     //assign result to $sectioncontents
182                     $sectioncontents[] = $module;
184                 }
185                 $sectionvalues['modules'] = $sectioncontents;
187                 // assign result to $coursecontents
188                 $coursecontents[] = $sectionvalues;
189             }
190         }
191         return $coursecontents;
192     }
194     /**
195      * Returns description of method result value
196      *
197      * @return external_description
198      * @since Moodle 2.2
199      */
200     public static function get_course_contents_returns() {
201         return new external_multiple_structure(
202             new external_single_structure(
203                 array(
204                     'id' => new external_value(PARAM_INT, 'Section ID'),
205                     'name' => new external_value(PARAM_TEXT, 'Section name'),
206                     'visible' => new external_value(PARAM_INT, 'is the section visible', VALUE_OPTIONAL),
207                     'summary' => new external_value(PARAM_RAW, 'Section description'),
208                     'modules' => new external_multiple_structure(
209                             new external_single_structure(
210                                 array(
211                                     'id' => new external_value(PARAM_INT, 'activity id'),
212                                     'url' => new external_value(PARAM_URL, 'activity url', VALUE_OPTIONAL),
213                                     'name' => new external_value(PARAM_TEXT, 'activity module name'),
214                                     'description' => new external_value(PARAM_RAW, 'activity description', VALUE_OPTIONAL),
215                                     'visible' => new external_value(PARAM_INT, 'is the module visible', VALUE_OPTIONAL),
216                                     'modicon' => new external_value(PARAM_URL, 'activity icon url'),
217                                     'modname' => new external_value(PARAM_PLUGIN, 'activity module type'),
218                                     'modplural' => new external_value(PARAM_TEXT, 'activity module plural name'),
219                                     'availablefrom' => new external_value(PARAM_INT, 'module availability start date', VALUE_OPTIONAL),
220                                     'availableuntil' => new external_value(PARAM_INT, 'module availability en date', VALUE_OPTIONAL),
221                                     'indent' => new external_value(PARAM_INT, 'number of identation in the site'),
222                                     'contents' => new external_multiple_structure(
223                                           new external_single_structure(
224                                               array(
225                                                   // content info
226                                                   'type'=> new external_value(PARAM_TEXT, 'a file or a folder or external link'),
227                                                   'filename'=> new external_value(PARAM_FILE, 'filename'),
228                                                   'filepath'=> new external_value(PARAM_PATH, 'filepath'),
229                                                   'filesize'=> new external_value(PARAM_INT, 'filesize'),
230                                                   'fileurl' => new external_value(PARAM_URL, 'downloadable file url', VALUE_OPTIONAL),
231                                                   'content' => new external_value(PARAM_RAW, 'Raw content, will be used when type is content', VALUE_OPTIONAL),
232                                                   'timecreated' => new external_value(PARAM_INT, 'Time created'),
233                                                   'timemodified' => new external_value(PARAM_INT, 'Time modified'),
234                                                   'sortorder' => new external_value(PARAM_INT, 'Content sort order'),
236                                                   // copyright related info
237                                                   'userid' => new external_value(PARAM_INT, 'User who added this content to moodle'),
238                                                   'author' => new external_value(PARAM_TEXT, 'Content owner'),
239                                                   'license' => new external_value(PARAM_TEXT, 'Content license'),
240                                               )
241                                           ), VALUE_DEFAULT, array()
242                                       )
243                                 )
244                             ), 'list of module'
245                     )
246                 )
247             )
248         );
249     }
251     /**
252      * Returns description of method parameters
253      *
254      * @return external_function_parameters
255      * @since Moodle 2.3
256      */
257     public static function get_courses_parameters() {
258         return new external_function_parameters(
259                 array('options' => new external_single_structure(
260                             array('ids' => new external_multiple_structure(
261                                         new external_value(PARAM_INT, 'Course id')
262                                         , 'List of course id. If empty return all courses
263                                             except front page course.',
264                                         VALUE_OPTIONAL)
265                             ), 'options - operator OR is used', VALUE_DEFAULT, array())
266                 )
267         );
268     }
270     /**
271      * Get courses
272      *
273      * @param array $options It contains an array (list of ids)
274      * @return array
275      * @since Moodle 2.2
276      */
277     public static function get_courses($options) {
278         global $CFG, $DB;
279         require_once($CFG->dirroot . "/course/lib.php");
281         //validate parameter
282         $params = self::validate_parameters(self::get_courses_parameters(),
283                         array('options' => $options));
285         //retrieve courses
286         if (!key_exists('ids', $params['options'])
287                 or empty($params['options']['ids'])) {
288             $courses = $DB->get_records('course');
289         } else {
290             $courses = $DB->get_records_list('course', 'id', $params['options']['ids']);
291         }
293         //create return value
294         $coursesinfo = array();
295         foreach ($courses as $course) {
297             // now security checks
298             $context = get_context_instance(CONTEXT_COURSE, $course->id);
299             try {
300                 self::validate_context($context);
301             } catch (Exception $e) {
302                 $exceptionparam = new stdClass();
303                 $exceptionparam->message = $e->getMessage();
304                 $exceptionparam->courseid = $course->id;
305                 throw new moodle_exception('errorcoursecontextnotvalid', 'webservice', '', $exceptionparam);
306             }
307             require_capability('moodle/course:view', $context);
309             $courseinfo = array();
310             $courseinfo['id'] = $course->id;
311             $courseinfo['fullname'] = $course->fullname;
312             $courseinfo['shortname'] = $course->shortname;
313             $courseinfo['categoryid'] = $course->category;
314             $courseinfo['summary'] = $course->summary;
315             $courseinfo['summaryformat'] = $course->summaryformat;
316             $courseinfo['format'] = $course->format;
317             $courseinfo['startdate'] = $course->startdate;
318             $courseinfo['numsections'] = $course->numsections;
320             //some field should be returned only if the user has update permission
321             $courseadmin = has_capability('moodle/course:update', $context);
322             if ($courseadmin) {
323                 $courseinfo['categorysortorder'] = $course->sortorder;
324                 $courseinfo['idnumber'] = $course->idnumber;
325                 $courseinfo['showgrades'] = $course->showgrades;
326                 $courseinfo['showreports'] = $course->showreports;
327                 $courseinfo['newsitems'] = $course->newsitems;
328                 $courseinfo['visible'] = $course->visible;
329                 $courseinfo['maxbytes'] = $course->maxbytes;
330                 $courseinfo['hiddensections'] = $course->hiddensections;
331                 $courseinfo['groupmode'] = $course->groupmode;
332                 $courseinfo['groupmodeforce'] = $course->groupmodeforce;
333                 $courseinfo['defaultgroupingid'] = $course->defaultgroupingid;
334                 $courseinfo['lang'] = $course->lang;
335                 $courseinfo['timecreated'] = $course->timecreated;
336                 $courseinfo['timemodified'] = $course->timemodified;
337                 $courseinfo['forcetheme'] = $course->theme;
338                 $courseinfo['enablecompletion'] = $course->enablecompletion;
339                 $courseinfo['completionstartonenrol'] = $course->completionstartonenrol;
340                 $courseinfo['completionnotify'] = $course->completionnotify;
341             }
343             if ($courseadmin or $course->visible
344                     or has_capability('moodle/course:viewhiddencourses', $context)) {
345                 $coursesinfo[] = $courseinfo;
346             }
347         }
349         return $coursesinfo;
350     }
352     /**
353      * Returns description of method result value
354      *
355      * @return external_description
356      * @since Moodle 2.2
357      */
358     public static function get_courses_returns() {
359         return new external_multiple_structure(
360                 new external_single_structure(
361                         array(
362                             'id' => new external_value(PARAM_INT, 'course id'),
363                             'shortname' => new external_value(PARAM_TEXT, 'course short name'),
364                             'categoryid' => new external_value(PARAM_INT, 'category id'),
365                             'categorysortorder' => new external_value(PARAM_INT,
366                                     'sort order into the category', VALUE_OPTIONAL),
367                             'fullname' => new external_value(PARAM_TEXT, 'full name'),
368                             'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
369                             'summary' => new external_value(PARAM_RAW, 'summary'),
370                             'summaryformat' => new external_value(PARAM_INT,
371                                     'the summary text Moodle format'),
372                             'format' => new external_value(PARAM_PLUGIN,
373                                     'course format: weeks, topics, social, site,..'),
374                             'showgrades' => new external_value(PARAM_INT,
375                                     '1 if grades are shown, otherwise 0', VALUE_OPTIONAL),
376                             'newsitems' => new external_value(PARAM_INT,
377                                     'number of recent items appearing on the course page', VALUE_OPTIONAL),
378                             'startdate' => new external_value(PARAM_INT,
379                                     'timestamp when the course start'),
380                             'numsections' => new external_value(PARAM_INT, 'number of weeks/topics'),
381                             'maxbytes' => new external_value(PARAM_INT,
382                                     'largest size of file that can be uploaded into the course',
383                                     VALUE_OPTIONAL),
384                             'showreports' => new external_value(PARAM_INT,
385                                     'are activity report shown (yes = 1, no =0)', VALUE_OPTIONAL),
386                             'visible' => new external_value(PARAM_INT,
387                                     '1: available to student, 0:not available', VALUE_OPTIONAL),
388                             'hiddensections' => new external_value(PARAM_INT,
389                                     'How the hidden sections in the course are displayed to students',
390                                     VALUE_OPTIONAL),
391                             'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible',
392                                     VALUE_OPTIONAL),
393                             'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no',
394                                     VALUE_OPTIONAL),
395                             'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id',
396                                     VALUE_OPTIONAL),
397                             'timecreated' => new external_value(PARAM_INT,
398                                     'timestamp when the course have been created', VALUE_OPTIONAL),
399                             'timemodified' => new external_value(PARAM_INT,
400                                     'timestamp when the course have been modified', VALUE_OPTIONAL),
401                             'enablecompletion' => new external_value(PARAM_INT,
402                                     'Enabled, control via completion and activity settings. Disbaled,
403                                         not shown in activity settings.',
404                                     VALUE_OPTIONAL),
405                             'completionstartonenrol' => new external_value(PARAM_INT,
406                                     '1: begin tracking a student\'s progress in course completion
407                                         after course enrolment. 0: does not',
408                                     VALUE_OPTIONAL),
409                             'completionnotify' => new external_value(PARAM_INT,
410                                     '1: yes 0: no', VALUE_OPTIONAL),
411                             'lang' => new external_value(PARAM_SAFEDIR,
412                                     'forced course language', VALUE_OPTIONAL),
413                             'forcetheme' => new external_value(PARAM_PLUGIN,
414                                     'name of the force theme', VALUE_OPTIONAL),
415                         ), 'course'
416                 )
417         );
418     }
420     /**
421      * Returns description of method parameters
422      *
423      * @return external_function_parameters
424      * @since Moodle 2.2
425      */
426     public static function create_courses_parameters() {
427         $courseconfig = get_config('moodlecourse'); //needed for many default values
428         return new external_function_parameters(
429             array(
430                 'courses' => new external_multiple_structure(
431                     new external_single_structure(
432                         array(
433                             'fullname' => new external_value(PARAM_TEXT, 'full name'),
434                             'shortname' => new external_value(PARAM_TEXT, 'course short name'),
435                             'categoryid' => new external_value(PARAM_INT, 'category id'),
436                             'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
437                             'summary' => new external_value(PARAM_RAW, 'summary', VALUE_OPTIONAL),
438                             'summaryformat' => new external_value(PARAM_INT,
439                                     'the summary text Moodle format', VALUE_DEFAULT, FORMAT_MOODLE),
440                             'format' => new external_value(PARAM_PLUGIN,
441                                     'course format: weeks, topics, social, site,..',
442                                     VALUE_DEFAULT, $courseconfig->format),
443                             'showgrades' => new external_value(PARAM_INT,
444                                     '1 if grades are shown, otherwise 0', VALUE_DEFAULT,
445                                     $courseconfig->showgrades),
446                             'newsitems' => new external_value(PARAM_INT,
447                                     'number of recent items appearing on the course page',
448                                     VALUE_DEFAULT, $courseconfig->newsitems),
449                             'startdate' => new external_value(PARAM_INT,
450                                     'timestamp when the course start', VALUE_OPTIONAL),
451                             'numsections' => new external_value(PARAM_INT, 'number of weeks/topics',
452                                     VALUE_DEFAULT, $courseconfig->numsections),
453                             'maxbytes' => new external_value(PARAM_INT,
454                                     'largest size of file that can be uploaded into the course',
455                                     VALUE_DEFAULT, $courseconfig->maxbytes),
456                             'showreports' => new external_value(PARAM_INT,
457                                     'are activity report shown (yes = 1, no =0)', VALUE_DEFAULT,
458                                     $courseconfig->showreports),
459                             'visible' => new external_value(PARAM_INT,
460                                     '1: available to student, 0:not available', VALUE_OPTIONAL),
461                             'hiddensections' => new external_value(PARAM_INT,
462                                     'How the hidden sections in the course are displayed to students',
463                                     VALUE_DEFAULT, $courseconfig->hiddensections),
464                             'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible',
465                                     VALUE_DEFAULT, $courseconfig->groupmode),
466                             'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no',
467                                     VALUE_DEFAULT, $courseconfig->groupmodeforce),
468                             'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id',
469                                     VALUE_DEFAULT, 0),
470                             'enablecompletion' => new external_value(PARAM_INT,
471                                     'Enabled, control via completion and activity settings. Disabled,
472                                         not shown in activity settings.',
473                                     VALUE_OPTIONAL),
474                             'completionstartonenrol' => new external_value(PARAM_INT,
475                                     '1: begin tracking a student\'s progress in course completion after
476                                         course enrolment. 0: does not',
477                                     VALUE_OPTIONAL),
478                             'completionnotify' => new external_value(PARAM_INT,
479                                     '1: yes 0: no', VALUE_OPTIONAL),
480                             'lang' => new external_value(PARAM_SAFEDIR,
481                                     'forced course language', VALUE_OPTIONAL),
482                             'forcetheme' => new external_value(PARAM_PLUGIN,
483                                     'name of the force theme', VALUE_OPTIONAL),
484                         )
485                     ), 'courses to create'
486                 )
487             )
488         );
489     }
491     /**
492      * Create  courses
493      *
494      * @param array $courses
495      * @return array courses (id and shortname only)
496      * @since Moodle 2.2
497      */
498     public static function create_courses($courses) {
499         global $CFG, $DB;
500         require_once($CFG->dirroot . "/course/lib.php");
501         require_once($CFG->libdir . '/completionlib.php');
503         $params = self::validate_parameters(self::create_courses_parameters(),
504                         array('courses' => $courses));
506         $availablethemes = get_plugin_list('theme');
507         $availablelangs = get_string_manager()->get_list_of_translations();
509         $transaction = $DB->start_delegated_transaction();
511         foreach ($params['courses'] as $course) {
513             // Ensure the current user is allowed to run this function
514             $context = get_context_instance(CONTEXT_COURSECAT, $course['categoryid']);
515             try {
516                 self::validate_context($context);
517             } catch (Exception $e) {
518                 $exceptionparam = new stdClass();
519                 $exceptionparam->message = $e->getMessage();
520                 $exceptionparam->catid = $course['categoryid'];
521                 throw new moodle_exception('errorcatcontextnotvalid', 'webservice', '', $exceptionparam);
522             }
523             require_capability('moodle/course:create', $context);
525             // Make sure lang is valid
526             if (key_exists('lang', $course) and empty($availablelangs[$course['lang']])) {
527                 throw new moodle_exception('errorinvalidparam', 'webservice', '', 'lang');
528             }
530             // Make sure theme is valid
531             if (key_exists('forcetheme', $course)) {
532                 if (!empty($CFG->allowcoursethemes)) {
533                     if (empty($availablethemes[$course['forcetheme']])) {
534                         throw new moodle_exception('errorinvalidparam', 'webservice', '', 'forcetheme');
535                     } else {
536                         $course['theme'] = $course['forcetheme'];
537                     }
538                 }
539             }
541             //force visibility if ws user doesn't have the permission to set it
542             $category = $DB->get_record('course_categories', array('id' => $course['categoryid']));
543             if (!has_capability('moodle/course:visibility', $context)) {
544                 $course['visible'] = $category->visible;
545             }
547             //set default value for completion
548             $courseconfig = get_config('moodlecourse');
549             if (completion_info::is_enabled_for_site()) {
550                 if (!key_exists('enablecompletion', $course)) {
551                     $course['enablecompletion'] = $courseconfig->enablecompletion;
552                 }
553                 if (!key_exists('completionstartonenrol', $course)) {
554                     $course['completionstartonenrol'] = $courseconfig->completionstartonenrol;
555                 }
556             } else {
557                 $course['enablecompletion'] = 0;
558                 $course['completionstartonenrol'] = 0;
559             }
561             $course['category'] = $course['categoryid'];
563             //Note: create_course() core function check shortname, idnumber, category
564             $course['id'] = create_course((object) $course)->id;
566             $resultcourses[] = array('id' => $course['id'], 'shortname' => $course['shortname']);
567         }
569         $transaction->allow_commit();
571         return $resultcourses;
572     }
574     /**
575      * Returns description of method result value
576      *
577      * @return external_description
578      * @since Moodle 2.2
579      */
580     public static function create_courses_returns() {
581         return new external_multiple_structure(
582             new external_single_structure(
583                 array(
584                     'id'       => new external_value(PARAM_INT, 'course id'),
585                     'shortname' => new external_value(PARAM_TEXT, 'short name'),
586                 )
587             )
588         );
589     }
591     /**
592      * Returns description of method parameters
593      *
594      * @return external_function_parameters
595      * @since Moodle 2.2
596      */
597     public static function delete_courses_parameters() {
598         return new external_function_parameters(
599             array(
600                 'courseids' => new external_multiple_structure(new external_value(PARAM_INT, 'course ID')),
601             )
602         );
603     }
605     /**
606      * Delete courses
607      *
608      * @param array $courseids A list of course ids
609      * @since Moodle 2.2
610      */
611     public static function delete_courses($courseids) {
612         global $CFG, $DB;
613         require_once($CFG->dirroot."/course/lib.php");
615         // Parameter validation.
616         $params = self::validate_parameters(self::delete_courses_parameters(), array('courseids'=>$courseids));
618         $transaction = $DB->start_delegated_transaction();
620         foreach ($params['courseids'] as $courseid) {
621             $course = $DB->get_record('course', array('id'=>$courseid), '*', MUST_EXIST);
623             // Check if the context is valid.
624             $coursecontext = context_course::instance($course->id);
625             self::validate_context($coursecontext);
627             // Check if the current user has enought permissions.
628             if (!can_delete_course($courseid)) {
629                 throw new moodle_exception('cannotdeletecategorycourse', 'error',
630                     '', format_string($course->fullname)." (id: $courseid)");
631             }
633             delete_course($course, false);
634         }
636         $transaction->allow_commit();
638         return null;
639     }
641     /**
642      * Returns description of method result value
643      *
644      * @return external_description
645      * @since Moodle 2.2
646      */
647     public static function delete_courses_returns() {
648         return null;
649     }
651     /**
652      * Returns description of method parameters
653      *
654      * @return external_function_parameters
655      * @since Moodle 2.3
656      */
657     public static function duplicate_course_parameters() {
658         return new external_function_parameters(
659             array(
660                 'courseid' => new external_value(PARAM_INT, 'course to duplicate id'),
661                 'fullname' => new external_value(PARAM_TEXT, 'duplicated course full name'),
662                 'shortname' => new external_value(PARAM_TEXT, 'duplicated course short name'),
663                 'categoryid' => new external_value(PARAM_INT, 'duplicated course category parent'),
664                 'visible' => new external_value(PARAM_INT, 'duplicated course visible, default to yes', VALUE_DEFAULT, 1),
665                 'options' => new external_multiple_structure(
666                     new external_single_structure(
667                         array(
668                                 'name' => new external_value(PARAM_ALPHA, 'The backup option name:
669                                             "activities" (int) Include course activites (default to 1 that is equal to yes),
670                                             "blocks" (int) Include course blocks (default to 1 that is equal to yes),
671                                             "filters" (int) Include course filters  (default to 1 that is equal to yes),
672                                             "users" (int) Include users (default to 0 that is equal to no),
673                                             "role_assignments" (int) Include role assignments  (default to 0 that is equal to no),
674                                             "user_files" (int) Include user files  (default to 0 that is equal to no),
675                                             "comments" (int) Include user comments  (default to 0 that is equal to no),
676                                             "completion_information" (int) Include user course completion information  (default to 0 that is equal to no),
677                                             "logs" (int) Include course logs  (default to 0 that is equal to no),
678                                             "histories" (int) Include histories  (default to 0 that is equal to no)'
679                                             ),
680                                 'value' => new external_value(PARAM_RAW, 'the value for the option 1 (yes) or 0 (no)'
681                             )
682                         )
683                     ), VALUE_DEFAULT, array()
684                 ),
685             )
686         );
687     }
689     /**
690      * Duplicate a course
691      *
692      * @param int $courseid
693      * @param string $fullname Duplicated course fullname
694      * @param string $shortname Duplicated course shortname
695      * @param int $categoryid Duplicated course parent category id
696      * @param int $visible Duplicated course availability
697      * @param array $options List of backup options
698      * @return array New course info
699      * @since Moodle 2.3
700      */
701     public static function duplicate_course($courseid, $fullname, $shortname, $categoryid, $visible, $options) {
702         global $CFG, $USER, $DB;
703         require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
704         require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
706         // Parameter validation.
707         $params = self::validate_parameters(
708                 self::duplicate_course_parameters(),
709                 array(
710                       'courseid' => $courseid,
711                       'fullname' => $fullname,
712                       'shortname' => $shortname,
713                       'categoryid' => $categoryid,
714                       'visible' => $visible,
715                       'options' => $options
716                 )
717         );
719         // Context validation.
721         if (! ($course = $DB->get_record('course', array('id'=>$params['courseid'])))) {
722             throw new moodle_exception('invalidcourseid', 'error', '', $params['courseid']);
723         }
725         // Category where duplicated course is going to be created.
726         $categorycontext = context_coursecat::instance($params['categoryid']);
727         self::validate_context($categorycontext);
729         // Course to be duplicated.
730         $coursecontext = context_course::instance($course->id);
731         self::validate_context($coursecontext);
733         $backupdefaults = array(
734             'activities' => 1,
735             'blocks' => 1,
736             'filters' => 1,
737             'users' => 0,
738             'role_assignments' => 0,
739             'user_files' => 0,
740             'comments' => 0,
741             'completion_information' => 0,
742             'logs' => 0,
743             'histories' => 0
744         );
746         $backupsettings = array();
747         // Check for backup and restore options.
748         if (!empty($params['options'])) {
749             foreach ($params['options'] as $option) {
751                 // Strict check for a correct value (allways 1 or 0, true or false).
752                 $value = clean_param($option['value'], PARAM_INT);
754                 if ($value !== 0 and $value !== 1) {
755                     throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
756                 }
758                 if (!isset($backupdefaults[$option['name']])) {
759                     throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
760                 }
762                 $backupsettings[$option['name']] = $value;
763             }
764         }
766         // Capability checking.
768         // The backup controller check for this currently, this may be redundant.
769         require_capability('moodle/course:create', $categorycontext);
770         require_capability('moodle/restore:restorecourse', $categorycontext);
771         require_capability('moodle/backup:backupcourse', $coursecontext);
773         if (!empty($backupsettings['users'])) {
774             require_capability('moodle/backup:userinfo', $coursecontext);
775             require_capability('moodle/restore:userinfo', $categorycontext);
776         }
778         // Check if the shortname is used.
779         if ($foundcourses = $DB->get_records('course', array('shortname'=>$shortname))) {
780             foreach ($foundcourses as $foundcourse) {
781                 $foundcoursenames[] = $foundcourse->fullname;
782             }
784             $foundcoursenamestring = implode(',', $foundcoursenames);
785             throw new moodle_exception('shortnametaken', '', '', $foundcoursenamestring);
786         }
788         // Backup the course.
790         $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
791         backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $USER->id);
793         foreach ($backupsettings as $name => $value) {
794             $bc->get_plan()->get_setting($name)->set_value($value);
795         }
797         $backupid       = $bc->get_backupid();
798         $backupbasepath = $bc->get_plan()->get_basepath();
800         $bc->execute_plan();
801         $results = $bc->get_results();
802         $file = $results['backup_destination'];
804         $bc->destroy();
806         // Restore the backup immediately.
808         // Check if we need to unzip the file because the backup temp dir does not contains backup files.
809         if (!file_exists($backupbasepath . "/moodle_backup.xml")) {
810             $file->extract_to_pathname(get_file_packer(), $backupbasepath);
811         }
813         // Create new course.
814         $newcourseid = restore_dbops::create_new_course($params['fullname'], $params['shortname'], $params['categoryid']);
816         $rc = new restore_controller($backupid, $newcourseid,
817                 backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $USER->id, backup::TARGET_NEW_COURSE);
819         foreach ($backupsettings as $name => $value) {
820             $setting = $rc->get_plan()->get_setting($name);
821             if ($setting->get_status() == backup_setting::NOT_LOCKED) {
822                 $setting->set_value($value);
823             }
824         }
826         if (!$rc->execute_precheck()) {
827             $precheckresults = $rc->get_precheck_results();
828             if (is_array($precheckresults) && !empty($precheckresults['errors'])) {
829                 if (empty($CFG->keeptempdirectoriesonbackup)) {
830                     fulldelete($backupbasepath);
831                 }
833                 $errorinfo = '';
835                 foreach ($precheckresults['errors'] as $error) {
836                     $errorinfo .= $error;
837                 }
839                 if (array_key_exists('warnings', $precheckresults)) {
840                     foreach ($precheckresults['warnings'] as $warning) {
841                         $errorinfo .= $warning;
842                     }
843                 }
845                 throw new moodle_exception('backupprecheckerrors', 'webservice', '', $errorinfo);
846             }
847         }
849         $rc->execute_plan();
850         $rc->destroy();
852         $course = $DB->get_record('course', array('id' => $newcourseid), '*', MUST_EXIST);
853         $course->fullname = $params['fullname'];
854         $course->shortname = $params['shortname'];
855         $course->visible = $params['visible'];
857         // Set shortname and fullname back.
858         $DB->update_record('course', $course);
860         if (empty($CFG->keeptempdirectoriesonbackup)) {
861             fulldelete($backupbasepath);
862         }
864         // Delete the course backup file created by this WebService. Originally located in the course backups area.
865         $file->delete();
867         return array('id' => $course->id, 'shortname' => $course->shortname);
868     }
870     /**
871      * Returns description of method result value
872      *
873      * @return external_description
874      * @since Moodle 2.3
875      */
876     public static function duplicate_course_returns() {
877         return new external_single_structure(
878             array(
879                 'id'       => new external_value(PARAM_INT, 'course id'),
880                 'shortname' => new external_value(PARAM_TEXT, 'short name'),
881             )
882         );
883     }
885     /**
886      * Returns description of method parameters
887      *
888      * @return external_function_parameters
889      * @since Moodle 2.3
890      */
891     public static function get_categories_parameters() {
892         return new external_function_parameters(
893             array(
894                 'criteria' => new external_multiple_structure(
895                     new external_single_structure(
896                         array(
897                             'key' => new external_value(PARAM_ALPHA,
898                                          'The category column to search, expected keys (value format) are:'.
899                                          '"id" (int) the category id,'.
900                                          '"name" (string) the category name,'.
901                                          '"parent" (int) the parent category id,'.
902                                          '"idnumber" (string) category idnumber'.
903                                          ' - user must have \'moodle/category:manage\' to search on idnumber,'.
904                                          '"visible" (int) whether the category is visible or not'.
905                                          ' - user must have \'moodle/category:manage\' or \'moodle/category:viewhiddencategories\' to search on visible,'.
906                                          '"theme" (string) category theme'.
907                                          ' - user must have \'moodle/category:manage\' to search on theme'),
908                             'value' => new external_value(PARAM_RAW, 'the value to match')
909                         )
910                     ), VALUE_DEFAULT, array()
911                 ),
912                 'addsubcategories' => new external_value(PARAM_BOOL, 'return the sub categories infos
913                                           (1 - default) otherwise only the category info (0)', VALUE_DEFAULT, 1)
914             )
915         );
916     }
918     /**
919      * Get categories
920      *
921      * @param array $criteria Criteria to match the results
922      * @param booln $addsubcategories obtain only the category (false) or its subcategories (true - default)
923      * @return array list of categories
924      * @since Moodle 2.3
925      */
926     public static function get_categories($criteria = array(), $addsubcategories = true) {
927         global $CFG, $DB;
928         require_once($CFG->dirroot . "/course/lib.php");
930         // Validate parameters.
931         $params = self::validate_parameters(self::get_categories_parameters(),
932                 array('criteria' => $criteria, 'addsubcategories' => $addsubcategories));
934         // Retrieve the categories.
935         $categories = array();
936         if (!empty($params['criteria'])) {
938             $conditions = array();
939             $wheres = array();
940             foreach ($params['criteria'] as $crit) {
941                 $key = trim($crit['key']);
943                 // Trying to avoid duplicate keys.
944                 if (!isset($conditions[$key])) {
946                     $context = context_system::instance();
947                     $value = null;
948                     switch ($key) {
949                         case 'id':
950                             $value = clean_param($crit['value'], PARAM_INT);
951                             break;
953                         case 'idnumber':
954                             if (has_capability('moodle/category:manage', $context)) {
955                                 $value = clean_param($crit['value'], PARAM_RAW);
956                             } else {
957                                 // We must throw an exception.
958                                 // Otherwise the dev client would think no idnumber exists.
959                                 throw new moodle_exception('criteriaerror',
960                                         'webservice', '', null,
961                                         'You don\'t have the permissions to search on the "idnumber" field.');
962                             }
963                             break;
965                         case 'name':
966                             $value = clean_param($crit['value'], PARAM_TEXT);
967                             break;
969                         case 'parent':
970                             $value = clean_param($crit['value'], PARAM_INT);
971                             break;
973                         case 'visible':
974                             if (has_capability('moodle/category:manage', $context)
975                                 or has_capability('moodle/category:viewhiddencategories',
976                                         context_system::instance())) {
977                                 $value = clean_param($crit['value'], PARAM_INT);
978                             } else {
979                                 throw new moodle_exception('criteriaerror',
980                                         'webservice', '', null,
981                                         'You don\'t have the permissions to search on the "visible" field.');
982                             }
983                             break;
985                         case 'theme':
986                             if (has_capability('moodle/category:manage', $context)) {
987                                 $value = clean_param($crit['value'], PARAM_THEME);
988                             } else {
989                                 throw new moodle_exception('criteriaerror',
990                                         'webservice', '', null,
991                                         'You don\'t have the permissions to search on the "theme" field.');
992                             }
993                             break;
995                         default:
996                             throw new moodle_exception('criteriaerror',
997                                     'webservice', '', null,
998                                     'You can not search on this criteria: ' . $key);
999                     }
1001                     if (isset($value)) {
1002                         $conditions[$key] = $crit['value'];
1003                         $wheres[] = $key . " = :" . $key;
1004                     }
1005                 }
1006             }
1008             if (!empty($wheres)) {
1009                 $wheres = implode(" AND ", $wheres);
1011                 $categories = $DB->get_records_select('course_categories', $wheres, $conditions);
1013                 // Retrieve its sub subcategories (all levels).
1014                 if ($categories and !empty($params['addsubcategories'])) {
1015                     $newcategories = array();
1017                     foreach ($categories as $category) {
1018                         $sqllike = $DB->sql_like('path', ':path');
1019                         $sqlparams = array('path' => $category->path.'/%'); // It will NOT include the specified category.
1020                         $subcategories = $DB->get_records_select('course_categories', $sqllike, $sqlparams);
1021                         $newcategories = $newcategories + $subcategories;   // Both arrays have integer as keys.
1022                     }
1023                     $categories = $categories + $newcategories;
1024                 }
1025             }
1027         } else {
1028             // Retrieve all categories in the database.
1029             $categories = $DB->get_records('course_categories');
1030         }
1032         // The not returned categories. key => category id, value => reason of exclusion.
1033         $excludedcats = array();
1035         // The returned categories.
1036         $categoriesinfo = array();
1038         // We need to sort the categories by path.
1039         // The parent cats need to be checked by the algo first.
1040         usort($categories, "core_course_external::compare_categories_by_path");
1042         foreach ($categories as $category) {
1044             // Check if the category is a child of an excluded category, if yes exclude it too (excluded => do not return).
1045             $parents = explode('/', $category->path);
1046             unset($parents[0]); // First key is always empty because path start with / => /1/2/4.
1047             foreach ($parents as $parentid) {
1048                 // Note: when the parent exclusion was due to the context,
1049                 // the sub category could still be returned.
1050                 if (isset($excludedcats[$parentid]) and $excludedcats[$parentid] != 'context') {
1051                     $excludedcats[$category->id] = 'parent';
1052                 }
1053             }
1055             // Check category depth is <= maxdepth (do not check for user who can manage categories).
1056             if ((!empty($CFG->maxcategorydepth) && count($parents) > $CFG->maxcategorydepth)
1057                     and !has_capability('moodle/category:manage', $context)) {
1058                 $excludedcats[$category->id] = 'depth';
1059             }
1061             // Check the user can use the category context.
1062             $context = context_coursecat::instance($category->id);
1063             try {
1064                 self::validate_context($context);
1065             } catch (Exception $e) {
1066                 $excludedcats[$category->id] = 'context';
1068                 // If it was the requested category then throw an exception.
1069                 if (isset($params['categoryid']) && $category->id == $params['categoryid']) {
1070                     $exceptionparam = new stdClass();
1071                     $exceptionparam->message = $e->getMessage();
1072                     $exceptionparam->catid = $category->id;
1073                     throw new moodle_exception('errorcatcontextnotvalid', 'webservice', '', $exceptionparam);
1074                 }
1075             }
1077             // Return the category information.
1078             if (!isset($excludedcats[$category->id])) {
1080                 // Final check to see if the category is visible to the user.
1081                 if ($category->visible
1082                         or has_capability('moodle/category:viewhiddencategories', context_system::instance())
1083                         or has_capability('moodle/category:manage', $context)) {
1085                     $categoryinfo = array();
1086                     $categoryinfo['id'] = $category->id;
1087                     $categoryinfo['name'] = $category->name;
1088                     $categoryinfo['description'] = file_rewrite_pluginfile_urls($category->description,
1089                             'webservice/pluginfile.php', $context->id, 'coursecat', 'description', null);
1090                     $options = new stdClass;
1091                     $options->noclean = true;
1092                     $options->para = false;
1093                     $categoryinfo['description'] = format_text($categoryinfo['description'],
1094                             $category->descriptionformat, $options);
1095                     $categoryinfo['parent'] = $category->parent;
1096                     $categoryinfo['sortorder'] = $category->sortorder;
1097                     $categoryinfo['coursecount'] = $category->coursecount;
1098                     $categoryinfo['depth'] = $category->depth;
1099                     $categoryinfo['path'] = $category->path;
1101                     // Some fields only returned for admin.
1102                     if (has_capability('moodle/category:manage', $context)) {
1103                         $categoryinfo['idnumber'] = $category->idnumber;
1104                         $categoryinfo['visible'] = $category->visible;
1105                         $categoryinfo['visibleold'] = $category->visibleold;
1106                         $categoryinfo['timemodified'] = $category->timemodified;
1107                         $categoryinfo['theme'] = $category->theme;
1108                     }
1110                     $categoriesinfo[] = $categoryinfo;
1111                 } else {
1112                     $excludedcats[$category->id] = 'visibility';
1113                 }
1114             }
1115         }
1117         // Sorting the resulting array so it looks a bit better for the client developer.
1118         usort($categoriesinfo, "core_course_external::compare_categories_by_sortorder");
1120         return $categoriesinfo;
1121     }
1123     /**
1124      * Sort categories array by path
1125      * private function: only used by get_categories
1126      *
1127      * @param array $category1
1128      * @param array $category2
1129      * @return int result of strcmp
1130      * @since Moodle 2.3
1131      */
1132     private static function compare_categories_by_path($category1, $category2) {
1133         return strcmp($category1->path, $category2->path);
1134     }
1136     /**
1137      * Sort categories array by sortorder
1138      * private function: only used by get_categories
1139      *
1140      * @param array $category1
1141      * @param array $category2
1142      * @return int result of strcmp
1143      * @since Moodle 2.3
1144      */
1145     private static function compare_categories_by_sortorder($category1, $category2) {
1146         return strcmp($category1['sortorder'], $category2['sortorder']);
1147     }
1149     /**
1150      * Returns description of method result value
1151      *
1152      * @return external_description
1153      * @since Moodle 2.3
1154      */
1155     public static function get_categories_returns() {
1156         return new external_multiple_structure(
1157             new external_single_structure(
1158                 array(
1159                     'id' => new external_value(PARAM_INT, 'category id'),
1160                     'name' => new external_value(PARAM_TEXT, 'category name'),
1161                     'idnumber' => new external_value(PARAM_RAW, 'category id number', VALUE_OPTIONAL),
1162                     'description' => new external_value(PARAM_RAW, 'category description'),
1163                     'parent' => new external_value(PARAM_INT, 'parent category id'),
1164                     'sortorder' => new external_value(PARAM_INT, 'category sorting order'),
1165                     'coursecount' => new external_value(PARAM_INT, 'number of courses in this category'),
1166                     'visible' => new external_value(PARAM_INT, '1: available, 0:not available', VALUE_OPTIONAL),
1167                     'visibleold' => new external_value(PARAM_INT, '1: available, 0:not available', VALUE_OPTIONAL),
1168                     'timemodified' => new external_value(PARAM_INT, 'timestamp', VALUE_OPTIONAL),
1169                     'depth' => new external_value(PARAM_INT, 'category depth'),
1170                     'path' => new external_value(PARAM_TEXT, 'category path'),
1171                     'theme' => new external_value(PARAM_THEME, 'category theme', VALUE_OPTIONAL),
1172                 ), 'List of categories'
1173             )
1174         );
1175     }
1177     /**
1178      * Returns description of method parameters
1179      *
1180      * @return external_function_parameters
1181      * @since Moodle 2.3
1182      */
1183     public static function create_categories_parameters() {
1184         return new external_function_parameters(
1185             array(
1186                 'categories' => new external_multiple_structure(
1187                         new external_single_structure(
1188                             array(
1189                                 'name' => new external_value(PARAM_TEXT, 'new category name'),
1190                                 'parent' => new external_value(PARAM_INT,
1191                                         'the parent category id inside which the new category will be created'),
1192                                 'idnumber' => new external_value(PARAM_RAW,
1193                                         'the new category idnumber', VALUE_OPTIONAL),
1194                                 'description' => new external_value(PARAM_RAW,
1195                                         'the new category description', VALUE_OPTIONAL),
1196                                 'theme' => new external_value(PARAM_THEME,
1197                                         'the new category theme. This option must be enabled on moodle',
1198                                         VALUE_OPTIONAL),
1199                         )
1200                     )
1201                 )
1202             )
1203         );
1204     }
1206     /**
1207      * Create categories
1208      *
1209      * @param array $categories - see create_categories_parameters() for the array structure
1210      * @return array - see create_categories_returns() for the array structure
1211      * @since Moodle 2.3
1212      */
1213     public static function create_categories($categories) {
1214         global $CFG, $DB;
1215         require_once($CFG->dirroot . "/course/lib.php");
1217         $params = self::validate_parameters(self::create_categories_parameters(),
1218                         array('categories' => $categories));
1220         $transaction = $DB->start_delegated_transaction();
1222         $createdcategories = array();
1223         foreach ($params['categories'] as $category) {
1224             if ($category['parent']) {
1225                 if (!$DB->record_exists('course_categories', array('id' => $category['parent']))) {
1226                     throw new moodle_exception('unknowcategory');
1227                 }
1228                 $context = context_coursecat::instance($category['parent']);
1229             } else {
1230                 $context = context_system::instance();
1231             }
1232             self::validate_context($context);
1233             require_capability('moodle/category:manage', $context);
1235             // Check id number.
1236             if (!empty($category['idnumber'])) { // Same as in course/editcategory_form.php .
1237                 if (textlib::strlen($category['idnumber'])>100) {
1238                     throw new moodle_exception('idnumbertoolong');
1239                 }
1240                 if ($existing = $DB->get_record('course_categories', array('idnumber' => $category['idnumber']))) {
1241                     if ($existing->id) {
1242                         throw new moodle_exception('idnumbertaken');
1243                     }
1244                 }
1245             }
1246             // Check name.
1247             if (textlib::strlen($category['name'])>255) {
1248                 throw new moodle_exception('categorytoolong');
1249             }
1251             $newcategory = new stdClass();
1252             $newcategory->name = $category['name'];
1253             $newcategory->parent = $category['parent'];
1254             $newcategory->idnumber = $category['idnumber'];
1255             $newcategory->sortorder = 999; // Same as in the course/editcategory.php .
1256             // Format the description.
1257             if (!empty($category['description'])) {
1258                 $newcategory->description = $category['description'];
1259             }
1260             $newcategory->descriptionformat = FORMAT_HTML;
1261             if (isset($category['theme']) and !empty($CFG->allowcategorythemes)) {
1262                 $newcategory->theme = $category['theme'];
1263             }
1265             $newcategory = create_course_category($newcategory);
1266             // Populate special fields.
1267             fix_course_sortorder();
1269             $createdcategories[] = array('id' => $newcategory->id, 'name' => $newcategory->name);
1270         }
1272         $transaction->allow_commit();
1274         return $createdcategories;
1275     }
1277     /**
1278      * Returns description of method parameters
1279      *
1280      * @return external_function_parameters
1281      * @since Moodle 2.3
1282      */
1283     public static function create_categories_returns() {
1284         return new external_multiple_structure(
1285             new external_single_structure(
1286                 array(
1287                     'id' => new external_value(PARAM_INT, 'new category id'),
1288                     'name' => new external_value(PARAM_TEXT, 'new category name'),
1289                 )
1290             )
1291         );
1292     }
1294     /**
1295      * Returns description of method parameters
1296      *
1297      * @return external_function_parameters
1298      * @since Moodle 2.3
1299      */
1300     public static function update_categories_parameters() {
1301         return new external_function_parameters(
1302             array(
1303                 'categories' => new external_multiple_structure(
1304                     new external_single_structure(
1305                         array(
1306                             'id'       => new external_value(PARAM_INT, 'course id'),
1307                             'name' => new external_value(PARAM_TEXT, 'category name', VALUE_OPTIONAL),
1308                             'idnumber' => new external_value(PARAM_RAW, 'category id number', VALUE_OPTIONAL),
1309                             'parent' => new external_value(PARAM_INT, 'parent category id', VALUE_OPTIONAL),
1310                             'description' => new external_value(PARAM_RAW, 'category description', VALUE_OPTIONAL),
1311                             'theme' => new external_value(PARAM_THEME,
1312                                     'the category theme. This option must be enabled on moodle', VALUE_OPTIONAL),
1313                         )
1314                     )
1315                 )
1316             )
1317         );
1318     }
1320     /**
1321      * Update categories
1322      *
1323      * @param array $categories The list of categories to update
1324      * @return null
1325      * @since Moodle 2.3
1326      */
1327     public static function update_categories($categories) {
1328         global $CFG, $DB;
1329         require_once($CFG->dirroot . "/course/lib.php");
1331         // Validate parameters.
1332         $params = self::validate_parameters(self::update_categories_parameters(), array('categories' => $categories));
1334         $transaction = $DB->start_delegated_transaction();
1336         foreach ($params['categories'] as $cat) {
1337             if (!$category = $DB->get_record('course_categories', array('id' => $cat['id']))) {
1338                 throw new moodle_exception('unknowcategory');
1339             }
1341             $categorycontext = context_coursecat::instance($cat['id']);
1342             self::validate_context($categorycontext);
1343             require_capability('moodle/category:manage', $categorycontext);
1345             if (!empty($cat['name'])) {
1346                 if (textlib::strlen($cat['name'])>255) {
1347                      throw new moodle_exception('categorytoolong');
1348                 }
1349                 $category->name = $cat['name'];
1350             }
1351             if (!empty($cat['idnumber'])) {
1352                 if (textlib::strlen($cat['idnumber'])>100) {
1353                     throw new moodle_exception('idnumbertoolong');
1354                 }
1355                 $category->idnumber = $cat['idnumber'];
1356             }
1357             if (!empty($cat['description'])) {
1358                 $category->description = $cat['description'];
1359                 $category->descriptionformat = FORMAT_HTML;
1360             }
1361             if (!empty($cat['theme'])) {
1362                 $category->theme = $cat['theme'];
1363             }
1364             if (!empty($cat['parent']) && ($category->parent != $cat['parent'])) {
1365                 // First check if parent exists.
1366                 if (!$parent_cat = $DB->get_record('course_categories', array('id' => $cat['parent']))) {
1367                     throw new moodle_exception('unknowcategory');
1368                 }
1369                 // Then check if we have capability.
1370                 self::validate_context(get_category_or_system_context((int)$cat['parent']));
1371                 require_capability('moodle/category:manage', get_category_or_system_context((int)$cat['parent']));
1372                 // Finally move the category.
1373                 move_category($category, $parent_cat);
1374                 $category->parent = $cat['parent'];
1375                 // Get updated path by move_category().
1376                 $category->path = $DB->get_field('course_categories', 'path',
1377                         array('id' => $category->id));
1378             }
1379             $DB->update_record('course_categories', $category);
1380         }
1382         $transaction->allow_commit();
1383     }
1385     /**
1386      * Returns description of method result value
1387      *
1388      * @return external_description
1389      * @since Moodle 2.3
1390      */
1391     public static function update_categories_returns() {
1392         return null;
1393     }
1395     /**
1396      * Returns description of method parameters
1397      *
1398      * @return external_function_parameters
1399      * @since Moodle 2.3
1400      */
1401     public static function delete_categories_parameters() {
1402         return new external_function_parameters(
1403             array(
1404                 'categories' => new external_multiple_structure(
1405                     new external_single_structure(
1406                         array(
1407                             'id' => new external_value(PARAM_INT, 'category id to delete'),
1408                             'newparent' => new external_value(PARAM_INT,
1409                                 'the parent category to move the contents to, if specified', VALUE_OPTIONAL),
1410                             'recursive' => new external_value(PARAM_BOOL, '1: recursively delete all contents inside this
1411                                 category, 0 (default): move contents to newparent or current parent category (except if parent is root)', VALUE_DEFAULT, 0)
1412                         )
1413                     )
1414                 )
1415             )
1416         );
1417     }
1419     /**
1420      * Delete categories
1421      *
1422      * @param array $categories A list of category ids
1423      * @return array
1424      * @since Moodle 2.3
1425      */
1426     public static function delete_categories($categories) {
1427         global $CFG, $DB;
1428         require_once($CFG->dirroot . "/course/lib.php");
1430         // Validate parameters.
1431         $params = self::validate_parameters(self::delete_categories_parameters(), array('categories' => $categories));
1433         $transaction = $DB->start_delegated_transaction();
1435         foreach ($params['categories'] as $category) {
1436             if (!$deletecat = $DB->get_record('course_categories', array('id' => $category['id']))) {
1437                 throw new moodle_exception('unknowcategory');
1438             }
1439             $context = context_coursecat::instance($deletecat->id);
1440             require_capability('moodle/category:manage', $context);
1441             self::validate_context($context);
1442             self::validate_context(get_category_or_system_context($deletecat->parent));
1444             if ($category['recursive']) {
1445                 // If recursive was specified, then we recursively delete the category's contents.
1446                 category_delete_full($deletecat, false);
1447             } else {
1448                 // In this situation, we don't delete the category's contents, we either move it to newparent or parent.
1449                 // If the parent is the root, moving is not supported (because a course must always be inside a category).
1450                 // We must move to an existing category.
1451                 if (!empty($category['newparent'])) {
1452                     if (!$DB->record_exists('course_categories', array('id' => $category['newparent']))) {
1453                         throw new moodle_exception('unknowcategory');
1454                     }
1455                     $newparent = $category['newparent'];
1456                 } else {
1457                     $newparent = $deletecat->parent;
1458                 }
1460                 // This operation is not allowed. We must move contents to an existing category.
1461                 if ($newparent == 0) {
1462                     throw new moodle_exception('movecatcontentstoroot');
1463                 }
1465                 $parentcontext = get_category_or_system_context($newparent);
1466                 require_capability('moodle/category:manage', $parentcontext);
1467                 self::validate_context($parentcontext);
1468                 category_delete_move($deletecat, $newparent, false);
1469             }
1470         }
1472         $transaction->allow_commit();
1473     }
1475     /**
1476      * Returns description of method parameters
1477      *
1478      * @return external_function_parameters
1479      * @since Moodle 2.3
1480      */
1481     public static function delete_categories_returns() {
1482         return null;
1483     }
1487 /**
1488  * Deprecated course external functions
1489  *
1490  * @package    core_course
1491  * @copyright  2009 Petr Skodak
1492  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1493  * @since Moodle 2.0
1494  * @deprecated Moodle 2.2 MDL-29106 - Please do not use this class any more.
1495  * @todo MDL-31194 This will be deleted in Moodle 2.5.
1496  * @see core_course_external
1497  */
1498 class moodle_course_external extends external_api {
1500     /**
1501      * Returns description of method parameters
1502      *
1503      * @return external_function_parameters
1504      * @since Moodle 2.0
1505      * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
1506      * @todo MDL-31194 This will be deleted in Moodle 2.5.
1507      * @see core_course_external::get_courses_parameters()
1508      */
1509     public static function get_courses_parameters() {
1510         return core_course_external::get_courses_parameters();
1511     }
1513     /**
1514      * Get courses
1515      *
1516      * @param array $options
1517      * @return array
1518      * @since Moodle 2.0
1519      * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
1520      * @todo MDL-31194 This will be deleted in Moodle 2.5.
1521      * @see core_course_external::get_courses()
1522      */
1523     public static function get_courses($options) {
1524         return core_course_external::get_courses($options);
1525     }
1527     /**
1528      * Returns description of method result value
1529      *
1530      * @return external_description
1531      * @since Moodle 2.0
1532      * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
1533      * @todo MDL-31194 This will be deleted in Moodle 2.5.
1534      * @see core_course_external::get_courses_returns()
1535      */
1536     public static function get_courses_returns() {
1537         return core_course_external::get_courses_returns();
1538     }
1540     /**
1541      * Returns description of method parameters
1542      *
1543      * @return external_function_parameters
1544      * @since Moodle 2.0
1545      * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
1546      * @todo MDL-31194 This will be deleted in Moodle 2.5.
1547      * @see core_course_external::create_courses_parameters()
1548      */
1549     public static function create_courses_parameters() {
1550         return core_course_external::create_courses_parameters();
1551     }
1553     /**
1554      * Create  courses
1555      *
1556      * @param array $courses
1557      * @return array courses (id and shortname only)
1558      * @since Moodle 2.0
1559      * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
1560      * @todo MDL-31194 This will be deleted in Moodle 2.5.
1561      * @see core_course_external::create_courses()
1562      */
1563     public static function create_courses($courses) {
1564         return core_course_external::create_courses($courses);
1565     }
1567     /**
1568      * Returns description of method result value
1569      *
1570      * @return external_description
1571      * @since Moodle 2.0
1572      * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
1573      * @todo MDL-31194 This will be deleted in Moodle 2.5.
1574      * @see core_course_external::create_courses_returns()
1575      */
1576     public static function create_courses_returns() {
1577         return core_course_external::create_courses_returns();
1578     }