MDL-30775 get_course_contents: remove warnings when the section is empty
[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 = 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         //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             $modinfosections = $modinfo->get_sections();
112             foreach ($sections as $key => $section) {
114                 $showsection = (has_capability('moodle/course:viewhiddensections', $context) or $section->visible or !$course->hiddensections);
115                 if (!$showsection) {
116                     continue;
117                 }
119                 // reset $sectioncontents
120                 $sectionvalues = array();
121                 $sectionvalues['id'] = $section->id;
122                 $sectionvalues['name'] = get_section_name($course, $section);
123                 $sectionvalues['visible'] = $section->visible;
124                 list($sectionvalues['summary'], $sectionvalues['summaryformat']) =
125                         external_format_text($section->summary, $section->summaryformat,
126                                 $context->id, 'course', 'section', $section->id);
127                 $sectioncontents = array();
129                 //for each module of the section
130                 if (!empty($modinfosections[$section->section])) {
131                     foreach ($modinfosections[$section->section] as $cmid) {
132                         $cm = $modinfo->cms[$cmid];
134                         // stop here if the module is not visible to the user
135                         if (!$cm->uservisible) {
136                             continue;
137                         }
139                         $module = array();
141                         //common info (for people being able to see the module or availability dates)
142                         $module['id'] = $cm->id;
143                         $module['name'] = format_string($cm->name, true);
144                         $module['modname'] = $cm->modname;
145                         $module['modplural'] = $cm->modplural;
146                         $module['modicon'] = $cm->get_icon_url()->out(false);
147                         $module['indent'] = $cm->indent;
149                         $modcontext = context_module::instance($cm->id);
151                         if (!empty($cm->showdescription)) {
152                             $module['description'] = $cm->get_content();
153                         }
155                         //url of the module
156                         $url = $cm->get_url();
157                         if ($url) { //labels don't have url
158                             $module['url'] = $cm->get_url()->out();
159                         }
161                         $canviewhidden = has_capability('moodle/course:viewhiddenactivities',
162                                             context_module::instance($cm->id));
163                         //user that can view hidden module should know about the visibility
164                         $module['visible'] = $cm->visible;
166                         //availability date (also send to user who can see hidden module when the showavailabilyt is ON)
167                         if ($canupdatecourse or ($CFG->enableavailability && $canviewhidden && $cm->showavailability)) {
168                             $module['availablefrom'] = $cm->availablefrom;
169                             $module['availableuntil'] = $cm->availableuntil;
170                         }
172                         $baseurl = 'webservice/pluginfile.php';
174                         //call $modulename_export_contents
175                         //(each module callback take care about checking the capabilities)
176                         require_once($CFG->dirroot . '/mod/' . $cm->modname . '/lib.php');
177                         $getcontentfunction = $cm->modname.'_export_contents';
178                         if (function_exists($getcontentfunction)) {
179                             if ($contents = $getcontentfunction($cm, $baseurl)) {
180                                 $module['contents'] = $contents;
181                             }
182                         }
184                         //assign result to $sectioncontents
185                         $sectioncontents[] = $module;
187                     }
188                 }
189                 $sectionvalues['modules'] = $sectioncontents;
191                 // assign result to $coursecontents
192                 $coursecontents[] = $sectionvalues;
193             }
194         }
195         return $coursecontents;
196     }
198     /**
199      * Returns description of method result value
200      *
201      * @return external_description
202      * @since Moodle 2.2
203      */
204     public static function get_course_contents_returns() {
205         return new external_multiple_structure(
206             new external_single_structure(
207                 array(
208                     'id' => new external_value(PARAM_INT, 'Section ID'),
209                     'name' => new external_value(PARAM_TEXT, 'Section name'),
210                     'visible' => new external_value(PARAM_INT, 'is the section visible', VALUE_OPTIONAL),
211                     'summary' => new external_value(PARAM_RAW, 'Section description'),
212                     'summaryformat' => new external_format_value('summary'),
213                     'modules' => new external_multiple_structure(
214                             new external_single_structure(
215                                 array(
216                                     'id' => new external_value(PARAM_INT, 'activity id'),
217                                     'url' => new external_value(PARAM_URL, 'activity url', VALUE_OPTIONAL),
218                                     'name' => new external_value(PARAM_RAW, 'activity module name'),
219                                     'description' => new external_value(PARAM_RAW, 'activity description', VALUE_OPTIONAL),
220                                     'visible' => new external_value(PARAM_INT, 'is the module visible', VALUE_OPTIONAL),
221                                     'modicon' => new external_value(PARAM_URL, 'activity icon url'),
222                                     'modname' => new external_value(PARAM_PLUGIN, 'activity module type'),
223                                     'modplural' => new external_value(PARAM_TEXT, 'activity module plural name'),
224                                     'availablefrom' => new external_value(PARAM_INT, 'module availability start date', VALUE_OPTIONAL),
225                                     'availableuntil' => new external_value(PARAM_INT, 'module availability en date', VALUE_OPTIONAL),
226                                     'indent' => new external_value(PARAM_INT, 'number of identation in the site'),
227                                     'contents' => new external_multiple_structure(
228                                           new external_single_structure(
229                                               array(
230                                                   // content info
231                                                   'type'=> new external_value(PARAM_TEXT, 'a file or a folder or external link'),
232                                                   'filename'=> new external_value(PARAM_FILE, 'filename'),
233                                                   'filepath'=> new external_value(PARAM_PATH, 'filepath'),
234                                                   'filesize'=> new external_value(PARAM_INT, 'filesize'),
235                                                   'fileurl' => new external_value(PARAM_URL, 'downloadable file url', VALUE_OPTIONAL),
236                                                   'content' => new external_value(PARAM_RAW, 'Raw content, will be used when type is content', VALUE_OPTIONAL),
237                                                   'timecreated' => new external_value(PARAM_INT, 'Time created'),
238                                                   'timemodified' => new external_value(PARAM_INT, 'Time modified'),
239                                                   'sortorder' => new external_value(PARAM_INT, 'Content sort order'),
241                                                   // copyright related info
242                                                   'userid' => new external_value(PARAM_INT, 'User who added this content to moodle'),
243                                                   'author' => new external_value(PARAM_TEXT, 'Content owner'),
244                                                   'license' => new external_value(PARAM_TEXT, 'Content license'),
245                                               )
246                                           ), VALUE_DEFAULT, array()
247                                       )
248                                 )
249                             ), 'list of module'
250                     )
251                 )
252             )
253         );
254     }
256     /**
257      * Returns description of method parameters
258      *
259      * @return external_function_parameters
260      * @since Moodle 2.3
261      */
262     public static function get_courses_parameters() {
263         return new external_function_parameters(
264                 array('options' => new external_single_structure(
265                             array('ids' => new external_multiple_structure(
266                                         new external_value(PARAM_INT, 'Course id')
267                                         , 'List of course id. If empty return all courses
268                                             except front page course.',
269                                         VALUE_OPTIONAL)
270                             ), 'options - operator OR is used', VALUE_DEFAULT, array())
271                 )
272         );
273     }
275     /**
276      * Get courses
277      *
278      * @param array $options It contains an array (list of ids)
279      * @return array
280      * @since Moodle 2.2
281      */
282     public static function get_courses($options = array()) {
283         global $CFG, $DB;
284         require_once($CFG->dirroot . "/course/lib.php");
286         //validate parameter
287         $params = self::validate_parameters(self::get_courses_parameters(),
288                         array('options' => $options));
290         //retrieve courses
291         if (!array_key_exists('ids', $params['options'])
292                 or empty($params['options']['ids'])) {
293             $courses = $DB->get_records('course');
294         } else {
295             $courses = $DB->get_records_list('course', 'id', $params['options']['ids']);
296         }
298         //create return value
299         $coursesinfo = array();
300         foreach ($courses as $course) {
302             // now security checks
303             $context = get_context_instance(CONTEXT_COURSE, $course->id);
304             try {
305                 self::validate_context($context);
306             } catch (Exception $e) {
307                 $exceptionparam = new stdClass();
308                 $exceptionparam->message = $e->getMessage();
309                 $exceptionparam->courseid = $course->id;
310                 throw new moodle_exception('errorcoursecontextnotvalid', 'webservice', '', $exceptionparam);
311             }
312             require_capability('moodle/course:view', $context);
314             $courseinfo = array();
315             $courseinfo['id'] = $course->id;
316             $courseinfo['fullname'] = $course->fullname;
317             $courseinfo['shortname'] = $course->shortname;
318             $courseinfo['categoryid'] = $course->category;
319             list($courseinfo['summary'], $courseinfo['summaryformat']) =
320                 external_format_text($course->summary, $course->summaryformat, $context->id, 'course', 'summary', 0);
321             $courseinfo['format'] = $course->format;
322             $courseinfo['startdate'] = $course->startdate;
323             $courseinfo['numsections'] = $course->numsections;
325             //some field should be returned only if the user has update permission
326             $courseadmin = has_capability('moodle/course:update', $context);
327             if ($courseadmin) {
328                 $courseinfo['categorysortorder'] = $course->sortorder;
329                 $courseinfo['idnumber'] = $course->idnumber;
330                 $courseinfo['showgrades'] = $course->showgrades;
331                 $courseinfo['showreports'] = $course->showreports;
332                 $courseinfo['newsitems'] = $course->newsitems;
333                 $courseinfo['visible'] = $course->visible;
334                 $courseinfo['maxbytes'] = $course->maxbytes;
335                 $courseinfo['hiddensections'] = $course->hiddensections;
336                 $courseinfo['groupmode'] = $course->groupmode;
337                 $courseinfo['groupmodeforce'] = $course->groupmodeforce;
338                 $courseinfo['defaultgroupingid'] = $course->defaultgroupingid;
339                 $courseinfo['lang'] = $course->lang;
340                 $courseinfo['timecreated'] = $course->timecreated;
341                 $courseinfo['timemodified'] = $course->timemodified;
342                 $courseinfo['forcetheme'] = $course->theme;
343                 $courseinfo['enablecompletion'] = $course->enablecompletion;
344                 $courseinfo['completionstartonenrol'] = $course->completionstartonenrol;
345                 $courseinfo['completionnotify'] = $course->completionnotify;
346             }
348             if ($courseadmin or $course->visible
349                     or has_capability('moodle/course:viewhiddencourses', $context)) {
350                 $coursesinfo[] = $courseinfo;
351             }
352         }
354         return $coursesinfo;
355     }
357     /**
358      * Returns description of method result value
359      *
360      * @return external_description
361      * @since Moodle 2.2
362      */
363     public static function get_courses_returns() {
364         return new external_multiple_structure(
365                 new external_single_structure(
366                         array(
367                             'id' => new external_value(PARAM_INT, 'course id'),
368                             'shortname' => new external_value(PARAM_TEXT, 'course short name'),
369                             'categoryid' => new external_value(PARAM_INT, 'category id'),
370                             'categorysortorder' => new external_value(PARAM_INT,
371                                     'sort order into the category', VALUE_OPTIONAL),
372                             'fullname' => new external_value(PARAM_TEXT, 'full name'),
373                             'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
374                             'summary' => new external_value(PARAM_RAW, 'summary'),
375                             'summaryformat' => new external_format_value('summary'),
376                             'format' => new external_value(PARAM_PLUGIN,
377                                     'course format: weeks, topics, social, site,..'),
378                             'showgrades' => new external_value(PARAM_INT,
379                                     '1 if grades are shown, otherwise 0', VALUE_OPTIONAL),
380                             'newsitems' => new external_value(PARAM_INT,
381                                     'number of recent items appearing on the course page', VALUE_OPTIONAL),
382                             'startdate' => new external_value(PARAM_INT,
383                                     'timestamp when the course start'),
384                             'numsections' => new external_value(PARAM_INT, 'number of weeks/topics'),
385                             'maxbytes' => new external_value(PARAM_INT,
386                                     'largest size of file that can be uploaded into the course',
387                                     VALUE_OPTIONAL),
388                             'showreports' => new external_value(PARAM_INT,
389                                     'are activity report shown (yes = 1, no =0)', VALUE_OPTIONAL),
390                             'visible' => new external_value(PARAM_INT,
391                                     '1: available to student, 0:not available', VALUE_OPTIONAL),
392                             'hiddensections' => new external_value(PARAM_INT,
393                                     'How the hidden sections in the course are displayed to students',
394                                     VALUE_OPTIONAL),
395                             'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible',
396                                     VALUE_OPTIONAL),
397                             'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no',
398                                     VALUE_OPTIONAL),
399                             'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id',
400                                     VALUE_OPTIONAL),
401                             'timecreated' => new external_value(PARAM_INT,
402                                     'timestamp when the course have been created', VALUE_OPTIONAL),
403                             'timemodified' => new external_value(PARAM_INT,
404                                     'timestamp when the course have been modified', VALUE_OPTIONAL),
405                             'enablecompletion' => new external_value(PARAM_INT,
406                                     'Enabled, control via completion and activity settings. Disbaled,
407                                         not shown in activity settings.',
408                                     VALUE_OPTIONAL),
409                             'completionstartonenrol' => new external_value(PARAM_INT,
410                                     '1: begin tracking a student\'s progress in course completion
411                                         after course enrolment. 0: does not',
412                                     VALUE_OPTIONAL),
413                             'completionnotify' => new external_value(PARAM_INT,
414                                     '1: yes 0: no', VALUE_OPTIONAL),
415                             'lang' => new external_value(PARAM_SAFEDIR,
416                                     'forced course language', VALUE_OPTIONAL),
417                             'forcetheme' => new external_value(PARAM_PLUGIN,
418                                     'name of the force theme', VALUE_OPTIONAL),
419                         ), 'course'
420                 )
421         );
422     }
424     /**
425      * Returns description of method parameters
426      *
427      * @return external_function_parameters
428      * @since Moodle 2.2
429      */
430     public static function create_courses_parameters() {
431         $courseconfig = get_config('moodlecourse'); //needed for many default values
432         return new external_function_parameters(
433             array(
434                 'courses' => new external_multiple_structure(
435                     new external_single_structure(
436                         array(
437                             'fullname' => new external_value(PARAM_TEXT, 'full name'),
438                             'shortname' => new external_value(PARAM_TEXT, 'course short name'),
439                             'categoryid' => new external_value(PARAM_INT, 'category id'),
440                             'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
441                             'summary' => new external_value(PARAM_RAW, 'summary', VALUE_OPTIONAL),
442                             'summaryformat' => new external_format_value('summary', VALUE_DEFAULT),
443                             'format' => new external_value(PARAM_PLUGIN,
444                                     'course format: weeks, topics, social, site,..',
445                                     VALUE_DEFAULT, $courseconfig->format),
446                             'showgrades' => new external_value(PARAM_INT,
447                                     '1 if grades are shown, otherwise 0', VALUE_DEFAULT,
448                                     $courseconfig->showgrades),
449                             'newsitems' => new external_value(PARAM_INT,
450                                     'number of recent items appearing on the course page',
451                                     VALUE_DEFAULT, $courseconfig->newsitems),
452                             'startdate' => new external_value(PARAM_INT,
453                                     'timestamp when the course start', VALUE_OPTIONAL),
454                             'numsections' => new external_value(PARAM_INT, 'number of weeks/topics',
455                                     VALUE_DEFAULT, $courseconfig->numsections),
456                             'maxbytes' => new external_value(PARAM_INT,
457                                     'largest size of file that can be uploaded into the course',
458                                     VALUE_DEFAULT, $courseconfig->maxbytes),
459                             'showreports' => new external_value(PARAM_INT,
460                                     'are activity report shown (yes = 1, no =0)', VALUE_DEFAULT,
461                                     $courseconfig->showreports),
462                             'visible' => new external_value(PARAM_INT,
463                                     '1: available to student, 0:not available', VALUE_OPTIONAL),
464                             'hiddensections' => new external_value(PARAM_INT,
465                                     'How the hidden sections in the course are displayed to students',
466                                     VALUE_DEFAULT, $courseconfig->hiddensections),
467                             'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible',
468                                     VALUE_DEFAULT, $courseconfig->groupmode),
469                             'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no',
470                                     VALUE_DEFAULT, $courseconfig->groupmodeforce),
471                             'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id',
472                                     VALUE_DEFAULT, 0),
473                             'enablecompletion' => new external_value(PARAM_INT,
474                                     'Enabled, control via completion and activity settings. Disabled,
475                                         not shown in activity settings.',
476                                     VALUE_OPTIONAL),
477                             'completionstartonenrol' => new external_value(PARAM_INT,
478                                     '1: begin tracking a student\'s progress in course completion after
479                                         course enrolment. 0: does not',
480                                     VALUE_OPTIONAL),
481                             'completionnotify' => new external_value(PARAM_INT,
482                                     '1: yes 0: no', VALUE_OPTIONAL),
483                             'lang' => new external_value(PARAM_SAFEDIR,
484                                     'forced course language', VALUE_OPTIONAL),
485                             'forcetheme' => new external_value(PARAM_PLUGIN,
486                                     'name of the force theme', VALUE_OPTIONAL),
487                         )
488                     ), 'courses to create'
489                 )
490             )
491         );
492     }
494     /**
495      * Create  courses
496      *
497      * @param array $courses
498      * @return array courses (id and shortname only)
499      * @since Moodle 2.2
500      */
501     public static function create_courses($courses) {
502         global $CFG, $DB;
503         require_once($CFG->dirroot . "/course/lib.php");
504         require_once($CFG->libdir . '/completionlib.php');
506         $params = self::validate_parameters(self::create_courses_parameters(),
507                         array('courses' => $courses));
509         $availablethemes = get_plugin_list('theme');
510         $availablelangs = get_string_manager()->get_list_of_translations();
512         $transaction = $DB->start_delegated_transaction();
514         foreach ($params['courses'] as $course) {
516             // Ensure the current user is allowed to run this function
517             $context = get_context_instance(CONTEXT_COURSECAT, $course['categoryid']);
518             try {
519                 self::validate_context($context);
520             } catch (Exception $e) {
521                 $exceptionparam = new stdClass();
522                 $exceptionparam->message = $e->getMessage();
523                 $exceptionparam->catid = $course['categoryid'];
524                 throw new moodle_exception('errorcatcontextnotvalid', 'webservice', '', $exceptionparam);
525             }
526             require_capability('moodle/course:create', $context);
528             // Make sure lang is valid
529             if (array_key_exists('lang', $course) and empty($availablelangs[$course['lang']])) {
530                 throw new moodle_exception('errorinvalidparam', 'webservice', '', 'lang');
531             }
533             // Make sure theme is valid
534             if (array_key_exists('forcetheme', $course)) {
535                 if (!empty($CFG->allowcoursethemes)) {
536                     if (empty($availablethemes[$course['forcetheme']])) {
537                         throw new moodle_exception('errorinvalidparam', 'webservice', '', 'forcetheme');
538                     } else {
539                         $course['theme'] = $course['forcetheme'];
540                     }
541                 }
542             }
544             //force visibility if ws user doesn't have the permission to set it
545             $category = $DB->get_record('course_categories', array('id' => $course['categoryid']));
546             if (!has_capability('moodle/course:visibility', $context)) {
547                 $course['visible'] = $category->visible;
548             }
550             //set default value for completion
551             $courseconfig = get_config('moodlecourse');
552             if (completion_info::is_enabled_for_site()) {
553                 if (!array_key_exists('enablecompletion', $course)) {
554                     $course['enablecompletion'] = $courseconfig->enablecompletion;
555                 }
556                 if (!array_key_exists('completionstartonenrol', $course)) {
557                     $course['completionstartonenrol'] = $courseconfig->completionstartonenrol;
558                 }
559             } else {
560                 $course['enablecompletion'] = 0;
561                 $course['completionstartonenrol'] = 0;
562             }
564             $course['category'] = $course['categoryid'];
566             // Summary format.
567             $course['summaryformat'] = external_validate_format($course['summaryformat']);
569             //Note: create_course() core function check shortname, idnumber, category
570             $course['id'] = create_course((object) $course)->id;
572             $resultcourses[] = array('id' => $course['id'], 'shortname' => $course['shortname']);
573         }
575         $transaction->allow_commit();
577         return $resultcourses;
578     }
580     /**
581      * Returns description of method result value
582      *
583      * @return external_description
584      * @since Moodle 2.2
585      */
586     public static function create_courses_returns() {
587         return new external_multiple_structure(
588             new external_single_structure(
589                 array(
590                     'id'       => new external_value(PARAM_INT, 'course id'),
591                     'shortname' => new external_value(PARAM_TEXT, 'short name'),
592                 )
593             )
594         );
595     }
597     /**
598      * Returns description of method parameters
599      *
600      * @return external_function_parameters
601      * @since Moodle 2.2
602      */
603     public static function delete_courses_parameters() {
604         return new external_function_parameters(
605             array(
606                 'courseids' => new external_multiple_structure(new external_value(PARAM_INT, 'course ID')),
607             )
608         );
609     }
611     /**
612      * Delete courses
613      *
614      * @param array $courseids A list of course ids
615      * @since Moodle 2.2
616      */
617     public static function delete_courses($courseids) {
618         global $CFG, $DB;
619         require_once($CFG->dirroot."/course/lib.php");
621         // Parameter validation.
622         $params = self::validate_parameters(self::delete_courses_parameters(), array('courseids'=>$courseids));
624         $transaction = $DB->start_delegated_transaction();
626         foreach ($params['courseids'] as $courseid) {
627             $course = $DB->get_record('course', array('id'=>$courseid), '*', MUST_EXIST);
629             // Check if the context is valid.
630             $coursecontext = context_course::instance($course->id);
631             self::validate_context($coursecontext);
633             // Check if the current user has enought permissions.
634             if (!can_delete_course($courseid)) {
635                 throw new moodle_exception('cannotdeletecategorycourse', 'error',
636                     '', format_string($course->fullname)." (id: $courseid)");
637             }
639             delete_course($course, false);
640         }
642         $transaction->allow_commit();
644         return null;
645     }
647     /**
648      * Returns description of method result value
649      *
650      * @return external_description
651      * @since Moodle 2.2
652      */
653     public static function delete_courses_returns() {
654         return null;
655     }
657     /**
658      * Returns description of method parameters
659      *
660      * @return external_function_parameters
661      * @since Moodle 2.3
662      */
663     public static function duplicate_course_parameters() {
664         return new external_function_parameters(
665             array(
666                 'courseid' => new external_value(PARAM_INT, 'course to duplicate id'),
667                 'fullname' => new external_value(PARAM_TEXT, 'duplicated course full name'),
668                 'shortname' => new external_value(PARAM_TEXT, 'duplicated course short name'),
669                 'categoryid' => new external_value(PARAM_INT, 'duplicated course category parent'),
670                 'visible' => new external_value(PARAM_INT, 'duplicated course visible, default to yes', VALUE_DEFAULT, 1),
671                 'options' => new external_multiple_structure(
672                     new external_single_structure(
673                         array(
674                                 'name' => new external_value(PARAM_ALPHAEXT, 'The backup option name:
675                                             "activities" (int) Include course activites (default to 1 that is equal to yes),
676                                             "blocks" (int) Include course blocks (default to 1 that is equal to yes),
677                                             "filters" (int) Include course filters  (default to 1 that is equal to yes),
678                                             "users" (int) Include users (default to 0 that is equal to no),
679                                             "role_assignments" (int) Include role assignments  (default to 0 that is equal to no),
680                                             "comments" (int) Include user comments  (default to 0 that is equal to no),
681                                             "completion_information" (int) Include user course completion information  (default to 0 that is equal to no),
682                                             "logs" (int) Include course logs  (default to 0 that is equal to no),
683                                             "histories" (int) Include histories  (default to 0 that is equal to no)'
684                                             ),
685                                 'value' => new external_value(PARAM_RAW, 'the value for the option 1 (yes) or 0 (no)'
686                             )
687                         )
688                     ), VALUE_DEFAULT, array()
689                 ),
690             )
691         );
692     }
694     /**
695      * Duplicate a course
696      *
697      * @param int $courseid
698      * @param string $fullname Duplicated course fullname
699      * @param string $shortname Duplicated course shortname
700      * @param int $categoryid Duplicated course parent category id
701      * @param int $visible Duplicated course availability
702      * @param array $options List of backup options
703      * @return array New course info
704      * @since Moodle 2.3
705      */
706     public static function duplicate_course($courseid, $fullname, $shortname, $categoryid, $visible = 1, $options = array()) {
707         global $CFG, $USER, $DB;
708         require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
709         require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
711         // Parameter validation.
712         $params = self::validate_parameters(
713                 self::duplicate_course_parameters(),
714                 array(
715                       'courseid' => $courseid,
716                       'fullname' => $fullname,
717                       'shortname' => $shortname,
718                       'categoryid' => $categoryid,
719                       'visible' => $visible,
720                       'options' => $options
721                 )
722         );
724         // Context validation.
726         if (! ($course = $DB->get_record('course', array('id'=>$params['courseid'])))) {
727             throw new moodle_exception('invalidcourseid', 'error');
728         }
730         // Category where duplicated course is going to be created.
731         $categorycontext = context_coursecat::instance($params['categoryid']);
732         self::validate_context($categorycontext);
734         // Course to be duplicated.
735         $coursecontext = context_course::instance($course->id);
736         self::validate_context($coursecontext);
738         $backupdefaults = array(
739             'activities' => 1,
740             'blocks' => 1,
741             'filters' => 1,
742             'users' => 0,
743             'role_assignments' => 0,
744             'comments' => 0,
745             'completion_information' => 0,
746             'logs' => 0,
747             'histories' => 0
748         );
750         $backupsettings = array();
751         // Check for backup and restore options.
752         if (!empty($params['options'])) {
753             foreach ($params['options'] as $option) {
755                 // Strict check for a correct value (allways 1 or 0, true or false).
756                 $value = clean_param($option['value'], PARAM_INT);
758                 if ($value !== 0 and $value !== 1) {
759                     throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
760                 }
762                 if (!isset($backupdefaults[$option['name']])) {
763                     throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
764                 }
766                 $backupsettings[$option['name']] = $value;
767             }
768         }
770         // Capability checking.
772         // The backup controller check for this currently, this may be redundant.
773         require_capability('moodle/course:create', $categorycontext);
774         require_capability('moodle/restore:restorecourse', $categorycontext);
775         require_capability('moodle/backup:backupcourse', $coursecontext);
777         if (!empty($backupsettings['users'])) {
778             require_capability('moodle/backup:userinfo', $coursecontext);
779             require_capability('moodle/restore:userinfo', $categorycontext);
780         }
782         // Check if the shortname is used.
783         if ($foundcourses = $DB->get_records('course', array('shortname'=>$shortname))) {
784             foreach ($foundcourses as $foundcourse) {
785                 $foundcoursenames[] = $foundcourse->fullname;
786             }
788             $foundcoursenamestring = implode(',', $foundcoursenames);
789             throw new moodle_exception('shortnametaken', '', '', $foundcoursenamestring);
790         }
792         // Backup the course.
794         $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
795         backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $USER->id);
797         foreach ($backupsettings as $name => $value) {
798             $bc->get_plan()->get_setting($name)->set_value($value);
799         }
801         $backupid       = $bc->get_backupid();
802         $backupbasepath = $bc->get_plan()->get_basepath();
804         $bc->execute_plan();
805         $results = $bc->get_results();
806         $file = $results['backup_destination'];
808         $bc->destroy();
810         // Restore the backup immediately.
812         // Check if we need to unzip the file because the backup temp dir does not contains backup files.
813         if (!file_exists($backupbasepath . "/moodle_backup.xml")) {
814             $file->extract_to_pathname(get_file_packer(), $backupbasepath);
815         }
817         // Create new course.
818         $newcourseid = restore_dbops::create_new_course($params['fullname'], $params['shortname'], $params['categoryid']);
820         $rc = new restore_controller($backupid, $newcourseid,
821                 backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $USER->id, backup::TARGET_NEW_COURSE);
823         foreach ($backupsettings as $name => $value) {
824             $setting = $rc->get_plan()->get_setting($name);
825             if ($setting->get_status() == backup_setting::NOT_LOCKED) {
826                 $setting->set_value($value);
827             }
828         }
830         if (!$rc->execute_precheck()) {
831             $precheckresults = $rc->get_precheck_results();
832             if (is_array($precheckresults) && !empty($precheckresults['errors'])) {
833                 if (empty($CFG->keeptempdirectoriesonbackup)) {
834                     fulldelete($backupbasepath);
835                 }
837                 $errorinfo = '';
839                 foreach ($precheckresults['errors'] as $error) {
840                     $errorinfo .= $error;
841                 }
843                 if (array_key_exists('warnings', $precheckresults)) {
844                     foreach ($precheckresults['warnings'] as $warning) {
845                         $errorinfo .= $warning;
846                     }
847                 }
849                 throw new moodle_exception('backupprecheckerrors', 'webservice', '', $errorinfo);
850             }
851         }
853         $rc->execute_plan();
854         $rc->destroy();
856         $course = $DB->get_record('course', array('id' => $newcourseid), '*', MUST_EXIST);
857         $course->fullname = $params['fullname'];
858         $course->shortname = $params['shortname'];
859         $course->visible = $params['visible'];
861         // Set shortname and fullname back.
862         $DB->update_record('course', $course);
864         if (empty($CFG->keeptempdirectoriesonbackup)) {
865             fulldelete($backupbasepath);
866         }
868         // Delete the course backup file created by this WebService. Originally located in the course backups area.
869         $file->delete();
871         return array('id' => $course->id, 'shortname' => $course->shortname);
872     }
874     /**
875      * Returns description of method result value
876      *
877      * @return external_description
878      * @since Moodle 2.3
879      */
880     public static function duplicate_course_returns() {
881         return new external_single_structure(
882             array(
883                 'id'       => new external_value(PARAM_INT, 'course id'),
884                 'shortname' => new external_value(PARAM_TEXT, 'short name'),
885             )
886         );
887     }
889     /**
890      * Returns description of method parameters
891      *
892      * @return external_function_parameters
893      * @since Moodle 2.3
894      */
895     public static function get_categories_parameters() {
896         return new external_function_parameters(
897             array(
898                 'criteria' => new external_multiple_structure(
899                     new external_single_structure(
900                         array(
901                             'key' => new external_value(PARAM_ALPHA,
902                                          'The category column to search, expected keys (value format) are:'.
903                                          '"id" (int) the category id,'.
904                                          '"name" (string) the category name,'.
905                                          '"parent" (int) the parent category id,'.
906                                          '"idnumber" (string) category idnumber'.
907                                          ' - user must have \'moodle/category:manage\' to search on idnumber,'.
908                                          '"visible" (int) whether the returned categories must be visible or hidden. If the key is not passed,
909                                              then the function return all categories that the user can see.'.
910                                          ' - user must have \'moodle/category:manage\' or \'moodle/category:viewhiddencategories\' to search on visible,'.
911                                          '"theme" (string) only return the categories having this theme'.
912                                          ' - user must have \'moodle/category:manage\' to search on theme'),
913                             'value' => new external_value(PARAM_RAW, 'the value to match')
914                         )
915                     ), 'criteria', VALUE_DEFAULT, array()
916                 ),
917                 'addsubcategories' => new external_value(PARAM_BOOL, 'return the sub categories infos
918                                           (1 - default) otherwise only the category info (0)', VALUE_DEFAULT, 1)
919             )
920         );
921     }
923     /**
924      * Get categories
925      *
926      * @param array $criteria Criteria to match the results
927      * @param booln $addsubcategories obtain only the category (false) or its subcategories (true - default)
928      * @return array list of categories
929      * @since Moodle 2.3
930      */
931     public static function get_categories($criteria = array(), $addsubcategories = true) {
932         global $CFG, $DB;
933         require_once($CFG->dirroot . "/course/lib.php");
935         // Validate parameters.
936         $params = self::validate_parameters(self::get_categories_parameters(),
937                 array('criteria' => $criteria, 'addsubcategories' => $addsubcategories));
939         // Retrieve the categories.
940         $categories = array();
941         if (!empty($params['criteria'])) {
943             $conditions = array();
944             $wheres = array();
945             foreach ($params['criteria'] as $crit) {
946                 $key = trim($crit['key']);
948                 // Trying to avoid duplicate keys.
949                 if (!isset($conditions[$key])) {
951                     $context = context_system::instance();
952                     $value = null;
953                     switch ($key) {
954                         case 'id':
955                             $value = clean_param($crit['value'], PARAM_INT);
956                             break;
958                         case 'idnumber':
959                             if (has_capability('moodle/category:manage', $context)) {
960                                 $value = clean_param($crit['value'], PARAM_RAW);
961                             } else {
962                                 // We must throw an exception.
963                                 // Otherwise the dev client would think no idnumber exists.
964                                 throw new moodle_exception('criteriaerror',
965                                         'webservice', '', null,
966                                         'You don\'t have the permissions to search on the "idnumber" field.');
967                             }
968                             break;
970                         case 'name':
971                             $value = clean_param($crit['value'], PARAM_TEXT);
972                             break;
974                         case 'parent':
975                             $value = clean_param($crit['value'], PARAM_INT);
976                             break;
978                         case 'visible':
979                             if (has_capability('moodle/category:manage', $context)
980                                 or has_capability('moodle/category:viewhiddencategories',
981                                         context_system::instance())) {
982                                 $value = clean_param($crit['value'], PARAM_INT);
983                             } else {
984                                 throw new moodle_exception('criteriaerror',
985                                         'webservice', '', null,
986                                         'You don\'t have the permissions to search on the "visible" field.');
987                             }
988                             break;
990                         case 'theme':
991                             if (has_capability('moodle/category:manage', $context)) {
992                                 $value = clean_param($crit['value'], PARAM_THEME);
993                             } else {
994                                 throw new moodle_exception('criteriaerror',
995                                         'webservice', '', null,
996                                         'You don\'t have the permissions to search on the "theme" field.');
997                             }
998                             break;
1000                         default:
1001                             throw new moodle_exception('criteriaerror',
1002                                     'webservice', '', null,
1003                                     'You can not search on this criteria: ' . $key);
1004                     }
1006                     if (isset($value)) {
1007                         $conditions[$key] = $crit['value'];
1008                         $wheres[] = $key . " = :" . $key;
1009                     }
1010                 }
1011             }
1013             if (!empty($wheres)) {
1014                 $wheres = implode(" AND ", $wheres);
1016                 $categories = $DB->get_records_select('course_categories', $wheres, $conditions);
1018                 // Retrieve its sub subcategories (all levels).
1019                 if ($categories and !empty($params['addsubcategories'])) {
1020                     $newcategories = array();
1022                     // Check if we required visible/theme checks.
1023                     $additionalselect = '';
1024                     $additionalparams = array();
1025                     if (isset($conditions['visible'])) {
1026                         $additionalselect .= ' AND visible = :visible';
1027                         $additionalparams['visible'] = $conditions['visible'];
1028                     }
1029                     if (isset($conditions['theme'])) {
1030                         $additionalselect .= ' AND theme= :theme';
1031                         $additionalparams['theme'] = $conditions['theme'];
1032                     }
1034                     foreach ($categories as $category) {
1035                         $sqlselect = $DB->sql_like('path', ':path') . $additionalselect;
1036                         $sqlparams = array('path' => $category->path.'/%') + $additionalparams; // It will NOT include the specified category.
1037                         $subcategories = $DB->get_records_select('course_categories', $sqlselect, $sqlparams);
1038                         $newcategories = $newcategories + $subcategories;   // Both arrays have integer as keys.
1039                     }
1040                     $categories = $categories + $newcategories;
1041                 }
1042             }
1044         } else {
1045             // Retrieve all categories in the database.
1046             $categories = $DB->get_records('course_categories');
1047         }
1049         // The not returned categories. key => category id, value => reason of exclusion.
1050         $excludedcats = array();
1052         // The returned categories.
1053         $categoriesinfo = array();
1055         // We need to sort the categories by path.
1056         // The parent cats need to be checked by the algo first.
1057         usort($categories, "core_course_external::compare_categories_by_path");
1059         foreach ($categories as $category) {
1061             // Check if the category is a child of an excluded category, if yes exclude it too (excluded => do not return).
1062             $parents = explode('/', $category->path);
1063             unset($parents[0]); // First key is always empty because path start with / => /1/2/4.
1064             foreach ($parents as $parentid) {
1065                 // Note: when the parent exclusion was due to the context,
1066                 // the sub category could still be returned.
1067                 if (isset($excludedcats[$parentid]) and $excludedcats[$parentid] != 'context') {
1068                     $excludedcats[$category->id] = 'parent';
1069                 }
1070             }
1072             // Check category depth is <= maxdepth (do not check for user who can manage categories).
1073             if ((!empty($CFG->maxcategorydepth) && count($parents) > $CFG->maxcategorydepth)
1074                     and !has_capability('moodle/category:manage', $context)) {
1075                 $excludedcats[$category->id] = 'depth';
1076             }
1078             // Check the user can use the category context.
1079             $context = context_coursecat::instance($category->id);
1080             try {
1081                 self::validate_context($context);
1082             } catch (Exception $e) {
1083                 $excludedcats[$category->id] = 'context';
1085                 // If it was the requested category then throw an exception.
1086                 if (isset($params['categoryid']) && $category->id == $params['categoryid']) {
1087                     $exceptionparam = new stdClass();
1088                     $exceptionparam->message = $e->getMessage();
1089                     $exceptionparam->catid = $category->id;
1090                     throw new moodle_exception('errorcatcontextnotvalid', 'webservice', '', $exceptionparam);
1091                 }
1092             }
1094             // Return the category information.
1095             if (!isset($excludedcats[$category->id])) {
1097                 // Final check to see if the category is visible to the user.
1098                 if ($category->visible
1099                         or has_capability('moodle/category:viewhiddencategories', context_system::instance())
1100                         or has_capability('moodle/category:manage', $context)) {
1102                     $categoryinfo = array();
1103                     $categoryinfo['id'] = $category->id;
1104                     $categoryinfo['name'] = $category->name;
1105                     list($categoryinfo['description'], $categoryinfo['descriptionformat']) =
1106                         external_format_text($category->description, $category->descriptionformat,
1107                                 $context->id, 'coursecat', 'description', null);
1108                     $categoryinfo['parent'] = $category->parent;
1109                     $categoryinfo['sortorder'] = $category->sortorder;
1110                     $categoryinfo['coursecount'] = $category->coursecount;
1111                     $categoryinfo['depth'] = $category->depth;
1112                     $categoryinfo['path'] = $category->path;
1114                     // Some fields only returned for admin.
1115                     if (has_capability('moodle/category:manage', $context)) {
1116                         $categoryinfo['idnumber'] = $category->idnumber;
1117                         $categoryinfo['visible'] = $category->visible;
1118                         $categoryinfo['visibleold'] = $category->visibleold;
1119                         $categoryinfo['timemodified'] = $category->timemodified;
1120                         $categoryinfo['theme'] = $category->theme;
1121                     }
1123                     $categoriesinfo[] = $categoryinfo;
1124                 } else {
1125                     $excludedcats[$category->id] = 'visibility';
1126                 }
1127             }
1128         }
1130         // Sorting the resulting array so it looks a bit better for the client developer.
1131         usort($categoriesinfo, "core_course_external::compare_categories_by_sortorder");
1133         return $categoriesinfo;
1134     }
1136     /**
1137      * Sort categories array by path
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_path($category1, $category2) {
1146         return strcmp($category1->path, $category2->path);
1147     }
1149     /**
1150      * Sort categories array by sortorder
1151      * private function: only used by get_categories
1152      *
1153      * @param array $category1
1154      * @param array $category2
1155      * @return int result of strcmp
1156      * @since Moodle 2.3
1157      */
1158     private static function compare_categories_by_sortorder($category1, $category2) {
1159         return strcmp($category1['sortorder'], $category2['sortorder']);
1160     }
1162     /**
1163      * Returns description of method result value
1164      *
1165      * @return external_description
1166      * @since Moodle 2.3
1167      */
1168     public static function get_categories_returns() {
1169         return new external_multiple_structure(
1170             new external_single_structure(
1171                 array(
1172                     'id' => new external_value(PARAM_INT, 'category id'),
1173                     'name' => new external_value(PARAM_TEXT, 'category name'),
1174                     'idnumber' => new external_value(PARAM_RAW, 'category id number', VALUE_OPTIONAL),
1175                     'description' => new external_value(PARAM_RAW, 'category description'),
1176                     'descriptionformat' => new external_format_value('description'),
1177                     'parent' => new external_value(PARAM_INT, 'parent category id'),
1178                     'sortorder' => new external_value(PARAM_INT, 'category sorting order'),
1179                     'coursecount' => new external_value(PARAM_INT, 'number of courses in this category'),
1180                     'visible' => new external_value(PARAM_INT, '1: available, 0:not available', VALUE_OPTIONAL),
1181                     'visibleold' => new external_value(PARAM_INT, '1: available, 0:not available', VALUE_OPTIONAL),
1182                     'timemodified' => new external_value(PARAM_INT, 'timestamp', VALUE_OPTIONAL),
1183                     'depth' => new external_value(PARAM_INT, 'category depth'),
1184                     'path' => new external_value(PARAM_TEXT, 'category path'),
1185                     'theme' => new external_value(PARAM_THEME, 'category theme', VALUE_OPTIONAL),
1186                 ), 'List of categories'
1187             )
1188         );
1189     }
1191     /**
1192      * Returns description of method parameters
1193      *
1194      * @return external_function_parameters
1195      * @since Moodle 2.3
1196      */
1197     public static function create_categories_parameters() {
1198         return new external_function_parameters(
1199             array(
1200                 'categories' => new external_multiple_structure(
1201                         new external_single_structure(
1202                             array(
1203                                 'name' => new external_value(PARAM_TEXT, 'new category name'),
1204                                 'parent' => new external_value(PARAM_INT,
1205                                         'the parent category id inside which the new category will be created
1206                                          - set to 0 for a root category',
1207                                         VALUE_DEFAULT, 0),
1208                                 'idnumber' => new external_value(PARAM_RAW,
1209                                         'the new category idnumber', VALUE_OPTIONAL),
1210                                 'description' => new external_value(PARAM_RAW,
1211                                         'the new category description', VALUE_OPTIONAL),
1212                                 'descriptionformat' => new external_format_value('description', VALUE_DEFAULT),
1213                                 'theme' => new external_value(PARAM_THEME,
1214                                         'the new category theme. This option must be enabled on moodle',
1215                                         VALUE_OPTIONAL),
1216                         )
1217                     )
1218                 )
1219             )
1220         );
1221     }
1223     /**
1224      * Create categories
1225      *
1226      * @param array $categories - see create_categories_parameters() for the array structure
1227      * @return array - see create_categories_returns() for the array structure
1228      * @since Moodle 2.3
1229      */
1230     public static function create_categories($categories) {
1231         global $CFG, $DB;
1232         require_once($CFG->dirroot . "/course/lib.php");
1234         $params = self::validate_parameters(self::create_categories_parameters(),
1235                         array('categories' => $categories));
1237         $transaction = $DB->start_delegated_transaction();
1239         $createdcategories = array();
1240         foreach ($params['categories'] as $category) {
1241             if ($category['parent']) {
1242                 if (!$DB->record_exists('course_categories', array('id' => $category['parent']))) {
1243                     throw new moodle_exception('unknowcategory');
1244                 }
1245                 $context = context_coursecat::instance($category['parent']);
1246             } else {
1247                 $context = context_system::instance();
1248             }
1249             self::validate_context($context);
1250             require_capability('moodle/category:manage', $context);
1252             // Check name.
1253             if (textlib::strlen($category['name'])>255) {
1254                 throw new moodle_exception('categorytoolong');
1255             }
1257             $newcategory = new stdClass();
1258             $newcategory->name = $category['name'];
1259             $newcategory->parent = $category['parent'];
1260             // Format the description.
1261             if (!empty($category['description'])) {
1262                 $newcategory->description = $category['description'];
1263             }
1264             $newcategory->descriptionformat = external_validate_format($category['descriptionformat']);
1265             if (isset($category['theme']) and !empty($CFG->allowcategorythemes)) {
1266                 $newcategory->theme = $category['theme'];
1267             }
1268             // Check id number.
1269             if (!empty($category['idnumber'])) { // Same as in course/editcategory_form.php .
1270                 if (textlib::strlen($category['idnumber'])>100) {
1271                     throw new moodle_exception('idnumbertoolong');
1272                 }
1273                 if ($existing = $DB->get_record('course_categories', array('idnumber' => $category['idnumber']))) {
1274                     if ($existing->id) {
1275                         throw new moodle_exception('idnumbertaken');
1276                     }
1277                 }
1278                 $newcategory->idnumber = $category['idnumber'];
1279             }
1281             $newcategory = create_course_category($newcategory);
1282             // Populate special fields.
1283             fix_course_sortorder();
1285             $createdcategories[] = array('id' => $newcategory->id, 'name' => $newcategory->name);
1286         }
1288         $transaction->allow_commit();
1290         return $createdcategories;
1291     }
1293     /**
1294      * Returns description of method parameters
1295      *
1296      * @return external_function_parameters
1297      * @since Moodle 2.3
1298      */
1299     public static function create_categories_returns() {
1300         return new external_multiple_structure(
1301             new external_single_structure(
1302                 array(
1303                     'id' => new external_value(PARAM_INT, 'new category id'),
1304                     'name' => new external_value(PARAM_TEXT, 'new category name'),
1305                 )
1306             )
1307         );
1308     }
1310     /**
1311      * Returns description of method parameters
1312      *
1313      * @return external_function_parameters
1314      * @since Moodle 2.3
1315      */
1316     public static function update_categories_parameters() {
1317         return new external_function_parameters(
1318             array(
1319                 'categories' => new external_multiple_structure(
1320                     new external_single_structure(
1321                         array(
1322                             'id'       => new external_value(PARAM_INT, 'course id'),
1323                             'name' => new external_value(PARAM_TEXT, 'category name', VALUE_OPTIONAL),
1324                             'idnumber' => new external_value(PARAM_RAW, 'category id number', VALUE_OPTIONAL),
1325                             'parent' => new external_value(PARAM_INT, 'parent category id', VALUE_OPTIONAL),
1326                             'description' => new external_value(PARAM_RAW, 'category description', VALUE_OPTIONAL),
1327                             'descriptionformat' => new external_format_value('description', VALUE_DEFAULT),
1328                             'theme' => new external_value(PARAM_THEME,
1329                                     'the category theme. This option must be enabled on moodle', VALUE_OPTIONAL),
1330                         )
1331                     )
1332                 )
1333             )
1334         );
1335     }
1337     /**
1338      * Update categories
1339      *
1340      * @param array $categories The list of categories to update
1341      * @return null
1342      * @since Moodle 2.3
1343      */
1344     public static function update_categories($categories) {
1345         global $CFG, $DB;
1346         require_once($CFG->dirroot . "/course/lib.php");
1348         // Validate parameters.
1349         $params = self::validate_parameters(self::update_categories_parameters(), array('categories' => $categories));
1351         $transaction = $DB->start_delegated_transaction();
1353         foreach ($params['categories'] as $cat) {
1354             if (!$category = $DB->get_record('course_categories', array('id' => $cat['id']))) {
1355                 throw new moodle_exception('unknowcategory');
1356             }
1358             $categorycontext = context_coursecat::instance($cat['id']);
1359             self::validate_context($categorycontext);
1360             require_capability('moodle/category:manage', $categorycontext);
1362             if (!empty($cat['name'])) {
1363                 if (textlib::strlen($cat['name'])>255) {
1364                      throw new moodle_exception('categorytoolong');
1365                 }
1366                 $category->name = $cat['name'];
1367             }
1368             if (!empty($cat['idnumber'])) {
1369                 if (textlib::strlen($cat['idnumber'])>100) {
1370                     throw new moodle_exception('idnumbertoolong');
1371                 }
1372                 $category->idnumber = $cat['idnumber'];
1373             }
1374             if (!empty($cat['description'])) {
1375                 $category->description = $cat['description'];
1376                 $category->descriptionformat = external_validate_format($cat['descriptionformat']);
1377             }
1378             if (!empty($cat['theme'])) {
1379                 $category->theme = $cat['theme'];
1380             }
1381             if (!empty($cat['parent']) && ($category->parent != $cat['parent'])) {
1382                 // First check if parent exists.
1383                 if (!$parent_cat = $DB->get_record('course_categories', array('id' => $cat['parent']))) {
1384                     throw new moodle_exception('unknowcategory');
1385                 }
1386                 // Then check if we have capability.
1387                 self::validate_context(get_category_or_system_context((int)$cat['parent']));
1388                 require_capability('moodle/category:manage', get_category_or_system_context((int)$cat['parent']));
1389                 // Finally move the category.
1390                 move_category($category, $parent_cat);
1391                 $category->parent = $cat['parent'];
1392                 // Get updated path by move_category().
1393                 $category->path = $DB->get_field('course_categories', 'path',
1394                         array('id' => $category->id));
1395             }
1396             $DB->update_record('course_categories', $category);
1397         }
1399         $transaction->allow_commit();
1400     }
1402     /**
1403      * Returns description of method result value
1404      *
1405      * @return external_description
1406      * @since Moodle 2.3
1407      */
1408     public static function update_categories_returns() {
1409         return null;
1410     }
1412     /**
1413      * Returns description of method parameters
1414      *
1415      * @return external_function_parameters
1416      * @since Moodle 2.3
1417      */
1418     public static function delete_categories_parameters() {
1419         return new external_function_parameters(
1420             array(
1421                 'categories' => new external_multiple_structure(
1422                     new external_single_structure(
1423                         array(
1424                             'id' => new external_value(PARAM_INT, 'category id to delete'),
1425                             'newparent' => new external_value(PARAM_INT,
1426                                 'the parent category to move the contents to, if specified', VALUE_OPTIONAL),
1427                             'recursive' => new external_value(PARAM_BOOL, '1: recursively delete all contents inside this
1428                                 category, 0 (default): move contents to newparent or current parent category (except if parent is root)', VALUE_DEFAULT, 0)
1429                         )
1430                     )
1431                 )
1432             )
1433         );
1434     }
1436     /**
1437      * Delete categories
1438      *
1439      * @param array $categories A list of category ids
1440      * @return array
1441      * @since Moodle 2.3
1442      */
1443     public static function delete_categories($categories) {
1444         global $CFG, $DB;
1445         require_once($CFG->dirroot . "/course/lib.php");
1447         // Validate parameters.
1448         $params = self::validate_parameters(self::delete_categories_parameters(), array('categories' => $categories));
1450         $transaction = $DB->start_delegated_transaction();
1452         foreach ($params['categories'] as $category) {
1453             if (!$deletecat = $DB->get_record('course_categories', array('id' => $category['id']))) {
1454                 throw new moodle_exception('unknowcategory');
1455             }
1456             $context = context_coursecat::instance($deletecat->id);
1457             require_capability('moodle/category:manage', $context);
1458             self::validate_context($context);
1459             self::validate_context(get_category_or_system_context($deletecat->parent));
1461             if ($category['recursive']) {
1462                 // If recursive was specified, then we recursively delete the category's contents.
1463                 category_delete_full($deletecat, false);
1464             } else {
1465                 // In this situation, we don't delete the category's contents, we either move it to newparent or parent.
1466                 // If the parent is the root, moving is not supported (because a course must always be inside a category).
1467                 // We must move to an existing category.
1468                 if (!empty($category['newparent'])) {
1469                     if (!$DB->record_exists('course_categories', array('id' => $category['newparent']))) {
1470                         throw new moodle_exception('unknowcategory');
1471                     }
1472                     $newparent = $category['newparent'];
1473                 } else {
1474                     $newparent = $deletecat->parent;
1475                 }
1477                 // This operation is not allowed. We must move contents to an existing category.
1478                 if ($newparent == 0) {
1479                     throw new moodle_exception('movecatcontentstoroot');
1480                 }
1482                 $parentcontext = get_category_or_system_context($newparent);
1483                 require_capability('moodle/category:manage', $parentcontext);
1484                 self::validate_context($parentcontext);
1485                 category_delete_move($deletecat, $newparent, false);
1486             }
1487         }
1489         $transaction->allow_commit();
1490     }
1492     /**
1493      * Returns description of method parameters
1494      *
1495      * @return external_function_parameters
1496      * @since Moodle 2.3
1497      */
1498     public static function delete_categories_returns() {
1499         return null;
1500     }
1504 /**
1505  * Deprecated course external functions
1506  *
1507  * @package    core_course
1508  * @copyright  2009 Petr Skodak
1509  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1510  * @since Moodle 2.0
1511  * @deprecated Moodle 2.2 MDL-29106 - Please do not use this class any more.
1512  * @todo MDL-31194 This will be deleted in Moodle 2.5.
1513  * @see core_course_external
1514  */
1515 class moodle_course_external extends external_api {
1517     /**
1518      * Returns description of method parameters
1519      *
1520      * @return external_function_parameters
1521      * @since Moodle 2.0
1522      * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
1523      * @todo MDL-31194 This will be deleted in Moodle 2.5.
1524      * @see core_course_external::get_courses_parameters()
1525      */
1526     public static function get_courses_parameters() {
1527         return core_course_external::get_courses_parameters();
1528     }
1530     /**
1531      * Get courses
1532      *
1533      * @param array $options
1534      * @return array
1535      * @since Moodle 2.0
1536      * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
1537      * @todo MDL-31194 This will be deleted in Moodle 2.5.
1538      * @see core_course_external::get_courses()
1539      */
1540     public static function get_courses($options) {
1541         return core_course_external::get_courses($options);
1542     }
1544     /**
1545      * Returns description of method result value
1546      *
1547      * @return external_description
1548      * @since Moodle 2.0
1549      * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
1550      * @todo MDL-31194 This will be deleted in Moodle 2.5.
1551      * @see core_course_external::get_courses_returns()
1552      */
1553     public static function get_courses_returns() {
1554         return core_course_external::get_courses_returns();
1555     }
1557     /**
1558      * Returns description of method parameters
1559      *
1560      * @return external_function_parameters
1561      * @since Moodle 2.0
1562      * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
1563      * @todo MDL-31194 This will be deleted in Moodle 2.5.
1564      * @see core_course_external::create_courses_parameters()
1565      */
1566     public static function create_courses_parameters() {
1567         return core_course_external::create_courses_parameters();
1568     }
1570     /**
1571      * Create  courses
1572      *
1573      * @param array $courses
1574      * @return array courses (id and shortname only)
1575      * @since Moodle 2.0
1576      * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
1577      * @todo MDL-31194 This will be deleted in Moodle 2.5.
1578      * @see core_course_external::create_courses()
1579      */
1580     public static function create_courses($courses) {
1581         return core_course_external::create_courses($courses);
1582     }
1584     /**
1585      * Returns description of method result value
1586      *
1587      * @return external_description
1588      * @since Moodle 2.0
1589      * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
1590      * @todo MDL-31194 This will be deleted in Moodle 2.5.
1591      * @see core_course_external::create_courses_returns()
1592      */
1593     public static function create_courses_returns() {
1594         return core_course_external::create_courses_returns();
1595     }