MDL-32941 Reorganise functions a bit
[moodle.git] / course / externallib.php
index dc2f725..d770ac8 100644 (file)
@@ -254,298 +254,6 @@ class core_course_external extends external_api {
      * @return external_function_parameters
      * @since Moodle 2.3
      */
-    public static function get_categories_parameters() {
-        return new external_function_parameters(
-            array(
-                'criteria' => new external_multiple_structure(
-                    new external_single_structure(
-                        array(
-                            'key' => new external_value(PARAM_ALPHA,
-                                         'The category column to search, expected keys (value format) are:'.
-                                         '"id" (int) the category id,'.
-                                         '"name" (string) the category name,'.
-                                         '"parent" (int) the parent category id,'.
-                                         '"idnumber" (string) category idnumber'.
-                                         ' - user must have \'moodle/category:manage\' to search on idnumber,'.
-                                         '"visible" (int) whether the category is visible or not'.
-                                         ' - user must have \'moodle/category:manage\' or \'moodle/category:viewhiddencategories\' to search on visible,'.
-                                         '"theme" (string) category theme'.
-                                         ' - user must have \'moodle/category:manage\' to search on theme'),
-                            'value' => new external_value(PARAM_RAW, 'the value to match')
-                        )
-                    ), VALUE_DEFAULT, array()
-                ),
-                'addsubcategories' => new external_value(PARAM_BOOL, 'return the sub categories infos
-                                          (1 - default) otherwise only the category info (0)', VALUE_DEFAULT, 1)
-            )
-        );
-    }
-
-    /**
-     * Get categories
-     *
-     * @param array $criteria Criteria to match the results
-     * @param booln $addsubcategories obtain only the category (false) or its subcategories (true - default)
-     * @return array list of categories
-     * @since Moodle 2.3
-     */
-    public static function get_categories($criteria = array(), $addsubcategories = true) {
-        global $CFG, $DB;
-        require_once($CFG->dirroot . "/course/lib.php");
-
-        // Validate parameters.
-        $params = self::validate_parameters(self::get_categories_parameters(),
-                array('criteria' => $criteria, 'addsubcategories' => $addsubcategories));
-
-        // Retrieve the categories.
-        $categories = array();
-        if (!empty($params['criteria'])) {
-
-            $conditions = array();
-            $wheres = array();
-            foreach ($params['criteria'] as $crit) {
-                $key = trim($crit['key']);
-
-                // Trying to avoid duplicate keys.
-                if (!isset($conditions[$key])) {
-
-                    $context = context_system::instance();
-                    $value = null;
-                    switch ($key) {
-                        case 'id':
-                            $value = clean_param($crit['value'], PARAM_INT);
-                            break;
-
-                        case 'idnumber':
-                            if (has_capability('moodle/category:manage', $context)) {
-                                $value = clean_param($crit['value'], PARAM_RAW);
-                            } else {
-                                // We must throw an exception.
-                                // Otherwise the dev client would think no idnumber exists.
-                                throw new moodle_exception('criteriaerror',
-                                        'webservice', '', null,
-                                        'You don\'t have the permissions to search on the "idnumber" field.');
-                            }
-                            break;
-
-                        case 'name':
-                            $value = clean_param($crit['value'], PARAM_TEXT);
-                            break;
-
-                        case 'parent':
-                            $value = clean_param($crit['value'], PARAM_INT);
-                            break;
-
-                        case 'visible':
-                            if (has_capability('moodle/category:manage', $context)
-                                or has_capability('moodle/category:viewhiddencategories',
-                                        context_system::instance())) {
-                                $value = clean_param($crit['value'], PARAM_INT);
-                            } else {
-                                throw new moodle_exception('criteriaerror',
-                                        'webservice', '', null,
-                                        'You don\'t have the permissions to search on the "visible" field.');
-                            }
-                            break;
-
-                        case 'theme':
-                            if (has_capability('moodle/category:manage', $context)) {
-                                $value = clean_param($crit['value'], PARAM_THEME);
-                            } else {
-                                throw new moodle_exception('criteriaerror',
-                                        'webservice', '', null,
-                                        'You don\'t have the permissions to search on the "theme" field.');
-                            }
-                            break;
-
-                        default:
-                            throw new moodle_exception('criteriaerror',
-                                    'webservice', '', null,
-                                    'You can not search on this criteria: ' . $key);
-                    }
-
-                    if (isset($value)) {
-                        $conditions[$key] = $crit['value'];
-                        $wheres[] = $key . " = :" . $key;
-                    }
-                }
-            }
-
-            if (!empty($wheres)) {
-                $wheres = implode(" AND ", $wheres);
-
-                $categories = $DB->get_records_select('course_categories', $wheres, $conditions);
-
-                // Retrieve its sub subcategories (all levels).
-                if ($categories and !empty($params['addsubcategories'])) {
-                    $newcategories = array();
-
-                    foreach ($categories as $category) {
-                        $sqllike = $DB->sql_like('path', ':path');
-                        $sqlparams = array('path' => $category->path.'/%'); // It will NOT include the specified category.
-                        $subcategories = $DB->get_records_select('course_categories', $sqllike, $sqlparams);
-                        $newcategories = $newcategories + $subcategories;   // Both arrays have integer as keys.
-                    }
-                    $categories = $categories + $newcategories;
-                }
-            }
-
-        } else {
-            // Retrieve all categories in the database.
-            $categories = $DB->get_records('course_categories');
-        }
-
-        // The not returned categories. key => category id, value => reason of exclusion.
-        $excludedcats = array();
-
-        // The returned categories.
-        $categoriesinfo = array();
-
-        // We need to sort the categories by path.
-        // The parent cats need to be checked by the algo first.
-        usort($categories, "core_course_external::compare_categories_by_path");
-
-        foreach ($categories as $category) {
-
-            // Check if the category is a child of an excluded category, if yes exclude it too (excluded => do not return).
-            $parents = explode('/', $category->path);
-            unset($parents[0]); // First key is always empty because path start with / => /1/2/4.
-            foreach ($parents as $parentid) {
-                // Note: when the parent exclusion was due to the context,
-                // the sub category could still be returned.
-                if (isset($excludedcats[$parentid]) and $excludedcats[$parentid] != 'context') {
-                    $excludedcats[$category->id] = 'parent';
-                }
-            }
-
-            // Check category depth is <= maxdepth (do not check for user who can manage categories).
-            if ((!empty($CFG->maxcategorydepth) && count($parents) > $CFG->maxcategorydepth)
-                    and !has_capability('moodle/category:manage', $context)) {
-                $excludedcats[$category->id] = 'depth';
-            }
-
-            // Check the user can use the category context.
-            $context = context_coursecat::instance($category->id);
-            try {
-                self::validate_context($context);
-            } catch (Exception $e) {
-                $excludedcats[$category->id] = 'context';
-
-                // If it was the requested category then throw an exception.
-                if (isset($params['categoryid']) && $category->id == $params['categoryid']) {
-                    $exceptionparam = new stdClass();
-                    $exceptionparam->message = $e->getMessage();
-                    $exceptionparam->catid = $category->id;
-                    throw new moodle_exception('errorcatcontextnotvalid', 'webservice', '', $exceptionparam);
-                }
-            }
-
-            // Return the category information.
-            if (!isset($excludedcats[$category->id])) {
-
-                // Final check to see if the category is visible to the user.
-                if ($category->visible
-                        or has_capability('moodle/category:viewhiddencategories', context_system::instance())
-                        or has_capability('moodle/category:manage', $context)) {
-
-                    $categoryinfo = array();
-                    $categoryinfo['id'] = $category->id;
-                    $categoryinfo['name'] = $category->name;
-                    $categoryinfo['description'] = file_rewrite_pluginfile_urls($category->description,
-                            'webservice/pluginfile.php', $context->id, 'coursecat', 'description', null);
-                    $options = new stdClass;
-                    $options->noclean = true;
-                    $options->para = false;
-                    $categoryinfo['description'] = format_text($categoryinfo['description'],
-                            $category->descriptionformat, $options);
-                    $categoryinfo['parent'] = $category->parent;
-                    $categoryinfo['sortorder'] = $category->sortorder;
-                    $categoryinfo['coursecount'] = $category->coursecount;
-                    $categoryinfo['depth'] = $category->depth;
-                    $categoryinfo['path'] = $category->path;
-
-                    // Some fields only returned for admin.
-                    if (has_capability('moodle/category:manage', $context)) {
-                        $categoryinfo['idnumber'] = $category->idnumber;
-                        $categoryinfo['visible'] = $category->visible;
-                        $categoryinfo['visibleold'] = $category->visibleold;
-                        $categoryinfo['timemodified'] = $category->timemodified;
-                        $categoryinfo['theme'] = $category->theme;
-                    }
-
-                    $categoriesinfo[] = $categoryinfo;
-                } else {
-                    $excludedcats[$category->id] = 'visibility';
-                }
-            }
-        }
-
-        // Sorting the resulting array so it looks a bit better for the client developer.
-        usort($categoriesinfo, "core_course_external::compare_categories_by_sortorder");
-
-        return $categoriesinfo;
-    }
-
-    /**
-     * Sort categories array by path
-     * private function: only used by get_categories
-     *
-     * @param array $category1
-     * @param array $category2
-     * @return int result of strcmp
-     * @since Moodle 2.3
-     */
-    private static function compare_categories_by_path($category1, $category2) {
-        return strcmp($category1->path, $category2->path);
-    }
-
-    /**
-     * Sort categories array by sortorder
-     * private function: only used by get_categories
-     *
-     * @param array $category1
-     * @param array $category2
-     * @return int result of strcmp
-     * @since Moodle 2.3
-     */
-    private static function compare_categories_by_sortorder($category1, $category2) {
-        return strcmp($category1['sortorder'], $category2['sortorder']);
-    }
-
-    /**
-     * Returns description of method result value
-     *
-     * @return external_description
-     * @since Moodle 2.3
-     */
-    public static function get_categories_returns() {
-        return new external_multiple_structure(
-            new external_single_structure(
-                array(
-                    'id' => new external_value(PARAM_INT, 'category id'),
-                    'name' => new external_value(PARAM_TEXT, 'category name'),
-                    'idnumber' => new external_value(PARAM_RAW, 'category id number', VALUE_OPTIONAL),
-                    'description' => new external_value(PARAM_RAW, 'category description'),
-                    'parent' => new external_value(PARAM_INT, 'parent category id'),
-                    'sortorder' => new external_value(PARAM_INT, 'category sorting order'),
-                    'coursecount' => new external_value(PARAM_INT, 'number of courses in this category'),
-                    'visible' => new external_value(PARAM_INT, '1: available, 0:not available', VALUE_OPTIONAL),
-                    'visibleold' => new external_value(PARAM_INT, '1: available, 0:not available', VALUE_OPTIONAL),
-                    'timemodified' => new external_value(PARAM_INT, 'timestamp', VALUE_OPTIONAL),
-                    'depth' => new external_value(PARAM_INT, 'category depth'),
-                    'path' => new external_value(PARAM_TEXT, 'category path'),
-                    'theme' => new external_value(PARAM_THEME, 'category theme', VALUE_OPTIONAL),
-                ), 'List of categories'
-            )
-        );
-    }
-
-    /**
-     * Returns description of method parameters
-     *
-     * @return external_function_parameters
-     * @since Moodle 2.2
-     */
     public static function get_courses_parameters() {
         return new external_function_parameters(
                 array('options' => new external_single_structure(
@@ -680,150 +388,33 @@ class core_course_external extends external_api {
                                     '1: available to student, 0:not available', VALUE_OPTIONAL),
                             'hiddensections' => new external_value(PARAM_INT,
                                     'How the hidden sections in the course are displayed to students',
-                                    VALUE_OPTIONAL),
-                            'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible',
-                                    VALUE_OPTIONAL),
-                            'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no',
-                                    VALUE_OPTIONAL),
-                            'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id',
-                                    VALUE_OPTIONAL),
-                            'timecreated' => new external_value(PARAM_INT,
-                                    'timestamp when the course have been created', VALUE_OPTIONAL),
-                            'timemodified' => new external_value(PARAM_INT,
-                                    'timestamp when the course have been modified', VALUE_OPTIONAL),
-                            'enablecompletion' => new external_value(PARAM_INT,
-                                    'Enabled, control via completion and activity settings. Disbaled,
-                                        not shown in activity settings.',
-                                    VALUE_OPTIONAL),
-                            'completionstartonenrol' => new external_value(PARAM_INT,
-                                    '1: begin tracking a student\'s progress in course completion
-                                        after course enrolment. 0: does not',
-                                    VALUE_OPTIONAL),
-                            'completionnotify' => new external_value(PARAM_INT,
-                                    '1: yes 0: no', VALUE_OPTIONAL),
-                            'lang' => new external_value(PARAM_SAFEDIR,
-                                    'forced course language', VALUE_OPTIONAL),
-                            'forcetheme' => new external_value(PARAM_PLUGIN,
-                                    'name of the force theme', VALUE_OPTIONAL),
-                        ), 'course'
-                )
-        );
-    }
-
-    /**
-     * Returns description of method parameters
-     *
-     * @return external_function_parameters
-     * @since Moodle 2.3
-     */
-    public static function create_categories_parameters() {
-        return new external_function_parameters(
-            array(
-                'categories' => new external_multiple_structure(
-                        new external_single_structure(
-                            array(
-                                'name' => new external_value(PARAM_TEXT, 'new category name'),
-                                'parent' => new external_value(PARAM_INT,
-                                        'the parent category id inside which the new category will be created'),
-                                'idnumber' => new external_value(PARAM_RAW,
-                                        'the new category idnumber', VALUE_OPTIONAL),
-                                'description' => new external_value(PARAM_RAW,
-                                        'the new category description', VALUE_OPTIONAL),
-                                'theme' => new external_value(PARAM_THEME,
-                                        'the new category theme. This option must be enabled on moodle',
-                                        VALUE_OPTIONAL),
-                        )
-                    )
-                )
-            )
-        );
-    }
-
-    /**
-     * Create categories
-     *
-     * @param array $categories - see create_categories_parameters() for the array structure
-     * @return array - see create_categories_returns() for the array structure
-     * @since Moodle 2.3
-     */
-    public static function create_categories($categories) {
-        global $CFG, $DB;
-        require_once($CFG->dirroot . "/course/lib.php");
-
-        $params = self::validate_parameters(self::create_categories_parameters(),
-                        array('categories' => $categories));
-
-        $transaction = $DB->start_delegated_transaction();
-
-        $createdcategories = array();
-        foreach ($params['categories'] as $category) {
-            if ($category['parent']) {
-                if (!$DB->record_exists('course_categories', array('id' => $category['parent']))) {
-                    throw new moodle_exception('unknowcategory');
-                }
-                $context = context_coursecat::instance($category['parent']);
-            } else {
-                $context = context_system::instance();
-            }
-            self::validate_context($context);
-            require_capability('moodle/category:manage', $context);
-
-            // Check id number.
-            if (!empty($category['idnumber'])) { // Same as in course/editcategory_form.php .
-                if (textlib::strlen($category['idnumber'])>100) {
-                    throw new moodle_exception('idnumbertoolong');
-                }
-                if ($existing = $DB->get_record('course_categories', array('idnumber' => $category['idnumber']))) {
-                    if ($existing->id) {
-                        throw new moodle_exception('idnumbertaken');
-                    }
-                }
-            }
-            // Check name.
-            if (textlib::strlen($category['name'])>255) {
-                throw new moodle_exception('categorytoolong');
-            }
-
-            $newcategory = new stdClass();
-            $newcategory->name = $category['name'];
-            $newcategory->parent = $category['parent'];
-            $newcategory->idnumber = $category['idnumber'];
-            $newcategory->sortorder = 999; // Same as in the course/editcategory.php .
-            // Format the description.
-            if (!empty($category['description'])) {
-                $newcategory->description = $category['description'];
-            }
-            $newcategory->descriptionformat = FORMAT_HTML;
-            if (isset($category['theme']) and !empty($CFG->allowcategorythemes)) {
-                $newcategory->theme = $category['theme'];
-            }
-
-            $newcategory = create_course_category($newcategory);
-            // Populate special fields.
-            fix_course_sortorder();
-
-            $createdcategories[] = array('id' => $newcategory->id, 'name' => $newcategory->name);
-        }
-
-        $transaction->allow_commit();
-
-        return $createdcategories;
-    }
-
-    /**
-     * Returns description of method parameters
-     *
-     * @return external_function_parameters
-     * @since Moodle 2.3
-     */
-    public static function create_categories_returns() {
-        return new external_multiple_structure(
-            new external_single_structure(
-                array(
-                    'id' => new external_value(PARAM_INT, 'new category id'),
-                    'name' => new external_value(PARAM_TEXT, 'new category name'),
+                                    VALUE_OPTIONAL),
+                            'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible',
+                                    VALUE_OPTIONAL),
+                            'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no',
+                                    VALUE_OPTIONAL),
+                            'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id',
+                                    VALUE_OPTIONAL),
+                            'timecreated' => new external_value(PARAM_INT,
+                                    'timestamp when the course have been created', VALUE_OPTIONAL),
+                            'timemodified' => new external_value(PARAM_INT,
+                                    'timestamp when the course have been modified', VALUE_OPTIONAL),
+                            'enablecompletion' => new external_value(PARAM_INT,
+                                    'Enabled, control via completion and activity settings. Disbaled,
+                                        not shown in activity settings.',
+                                    VALUE_OPTIONAL),
+                            'completionstartonenrol' => new external_value(PARAM_INT,
+                                    '1: begin tracking a student\'s progress in course completion
+                                        after course enrolment. 0: does not',
+                                    VALUE_OPTIONAL),
+                            'completionnotify' => new external_value(PARAM_INT,
+                                    '1: yes 0: no', VALUE_OPTIONAL),
+                            'lang' => new external_value(PARAM_SAFEDIR,
+                                    'forced course language', VALUE_OPTIONAL),
+                            'forcetheme' => new external_value(PARAM_PLUGIN,
+                                    'name of the force theme', VALUE_OPTIONAL),
+                        ), 'course'
                 )
-            )
         );
     }
 
@@ -1003,7 +594,9 @@ class core_course_external extends external_api {
 
     /**
      * Returns description of method parameters
+     *
      * @return external_function_parameters
+     * @since Moodle 2.2
      */
     public static function delete_courses_parameters() {
         return new external_function_parameters(
@@ -1015,7 +608,9 @@ class core_course_external extends external_api {
 
     /**
      * Delete courses
+     *
      * @param array $courseids A list of course ids
+     * @since Moodle 2.2
      */
     public static function delete_courses($courseids) {
         global $CFG, $DB;
@@ -1049,7 +644,9 @@ class core_course_external extends external_api {
 
     /**
      * Returns description of method result value
+     *
      * @return external_description
+     * @since Moodle 2.2
      */
     public static function delete_courses_returns() {
         return null;
@@ -1123,155 +720,434 @@ class core_course_external extends external_api {
                 )
         );
 
-        // Context validation.
+        // Context validation.
+
+        if (! ($course = $DB->get_record('course', array('id'=>$params['courseid'])))) {
+            throw new moodle_exception('invalidcourseid', 'error', '', $params['courseid']);
+        }
+
+        // Category where duplicated course is going to be created.
+        $categorycontext = context_coursecat::instance($params['categoryid']);
+        self::validate_context($categorycontext);
+
+        // Course to be duplicated.
+        $coursecontext = context_course::instance($course->id);
+        self::validate_context($coursecontext);
+
+        $backupdefaults = array(
+            'activities' => 1,
+            'blocks' => 1,
+            'filters' => 1,
+            'users' => 0,
+            'role_assignments' => 0,
+            'user_files' => 0,
+            'comments' => 0,
+            'completion_information' => 0,
+            'logs' => 0,
+            'histories' => 0
+        );
+
+        $backupsettings = array();
+        // Check for backup and restore options.
+        if (!empty($params['options'])) {
+            foreach ($params['options'] as $option) {
+
+                // Strict check for a correct value (allways 1 or 0, true or false).
+                $value = clean_param($option['value'], PARAM_INT);
+
+                if ($value !== 0 and $value !== 1) {
+                    throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
+                }
+
+                if (!isset($backupdefaults[$option['name']])) {
+                    throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
+                }
+
+                $backupsettings[$option['name']] = $value;
+            }
+        }
+
+        // Capability checking.
+
+        // The backup controller check for this currently, this may be redundant.
+        require_capability('moodle/course:create', $categorycontext);
+        require_capability('moodle/restore:restorecourse', $categorycontext);
+        require_capability('moodle/backup:backupcourse', $coursecontext);
+
+        if (!empty($backupsettings['users'])) {
+            require_capability('moodle/backup:userinfo', $coursecontext);
+            require_capability('moodle/restore:userinfo', $categorycontext);
+        }
+
+        // Check if the shortname is used.
+        if ($foundcourses = $DB->get_records('course', array('shortname'=>$shortname))) {
+            foreach ($foundcourses as $foundcourse) {
+                $foundcoursenames[] = $foundcourse->fullname;
+            }
+
+            $foundcoursenamestring = implode(',', $foundcoursenames);
+            throw new moodle_exception('shortnametaken', '', '', $foundcoursenamestring);
+        }
+
+        // Backup the course.
+
+        $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
+        backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $USER->id);
+
+        foreach ($backupsettings as $name => $value) {
+            $bc->get_plan()->get_setting($name)->set_value($value);
+        }
+
+        $backupid       = $bc->get_backupid();
+        $backupbasepath = $bc->get_plan()->get_basepath();
+
+        $bc->execute_plan();
+        $results = $bc->get_results();
+        $file = $results['backup_destination'];
+
+        $bc->destroy();
+
+        // Restore the backup immediately.
+
+        // Check if we need to unzip the file because the backup temp dir does not contains backup files.
+        if (!file_exists($backupbasepath . "/moodle_backup.xml")) {
+            $file->extract_to_pathname(get_file_packer(), $backupbasepath);
+        }
+
+        // Create new course.
+        $newcourseid = restore_dbops::create_new_course($params['fullname'], $params['shortname'], $params['categoryid']);
+
+        $rc = new restore_controller($backupid, $newcourseid,
+                backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $USER->id, backup::TARGET_NEW_COURSE);
+
+        foreach ($backupsettings as $name => $value) {
+            $setting = $rc->get_plan()->get_setting($name);
+            if ($setting->get_status() == backup_setting::NOT_LOCKED) {
+                $setting->set_value($value);
+            }
+        }
+
+        if (!$rc->execute_precheck()) {
+            $precheckresults = $rc->get_precheck_results();
+            if (is_array($precheckresults) && !empty($precheckresults['errors'])) {
+                if (empty($CFG->keeptempdirectoriesonbackup)) {
+                    fulldelete($backupbasepath);
+                }
+
+                $errorinfo = '';
+
+                foreach ($precheckresults['errors'] as $error) {
+                    $errorinfo .= $error;
+                }
+
+                if (array_key_exists('warnings', $precheckresults)) {
+                    foreach ($precheckresults['warnings'] as $warning) {
+                        $errorinfo .= $warning;
+                    }
+                }
+
+                throw new moodle_exception('backupprecheckerrors', 'webservice', '', $errorinfo);
+            }
+        }
+
+        $rc->execute_plan();
+        $rc->destroy();
+
+        $course = $DB->get_record('course', array('id' => $newcourseid), '*', MUST_EXIST);
+        $course->fullname = $params['fullname'];
+        $course->shortname = $params['shortname'];
+        $course->visible = $params['visible'];
+
+        // Set shortname and fullname back.
+        $DB->update_record('course', $course);
+
+        if (empty($CFG->keeptempdirectoriesonbackup)) {
+            fulldelete($backupbasepath);
+        }
+
+        // Delete the course backup file created by this WebService. Originally located in the course backups area.
+        $file->delete();
+
+        return array('id' => $course->id, 'shortname' => $course->shortname);
+    }
+
+    /**
+     * Returns description of method result value
+     *
+     * @return external_description
+     * @since Moodle 2.3
+     */
+    public static function duplicate_course_returns() {
+        return new external_single_structure(
+            array(
+                'id'       => new external_value(PARAM_INT, 'course id'),
+                'shortname' => new external_value(PARAM_TEXT, 'short name'),
+            )
+        );
+    }
+
+    /**
+     * Returns description of method parameters
+     *
+     * @return external_function_parameters
+     * @since Moodle 2.3
+     */
+    public static function get_categories_parameters() {
+        return new external_function_parameters(
+            array(
+                'criteria' => new external_multiple_structure(
+                    new external_single_structure(
+                        array(
+                            'key' => new external_value(PARAM_ALPHA,
+                                         'The category column to search, expected keys (value format) are:'.
+                                         '"id" (int) the category id,'.
+                                         '"name" (string) the category name,'.
+                                         '"parent" (int) the parent category id,'.
+                                         '"idnumber" (string) category idnumber'.
+                                         ' - user must have \'moodle/category:manage\' to search on idnumber,'.
+                                         '"visible" (int) whether the category is visible or not'.
+                                         ' - user must have \'moodle/category:manage\' or \'moodle/category:viewhiddencategories\' to search on visible,'.
+                                         '"theme" (string) category theme'.
+                                         ' - user must have \'moodle/category:manage\' to search on theme'),
+                            'value' => new external_value(PARAM_RAW, 'the value to match')
+                        )
+                    ), VALUE_DEFAULT, array()
+                ),
+                'addsubcategories' => new external_value(PARAM_BOOL, 'return the sub categories infos
+                                          (1 - default) otherwise only the category info (0)', VALUE_DEFAULT, 1)
+            )
+        );
+    }
+
+    /**
+     * Get categories
+     *
+     * @param array $criteria Criteria to match the results
+     * @param booln $addsubcategories obtain only the category (false) or its subcategories (true - default)
+     * @return array list of categories
+     * @since Moodle 2.3
+     */
+    public static function get_categories($criteria = array(), $addsubcategories = true) {
+        global $CFG, $DB;
+        require_once($CFG->dirroot . "/course/lib.php");
+
+        // Validate parameters.
+        $params = self::validate_parameters(self::get_categories_parameters(),
+                array('criteria' => $criteria, 'addsubcategories' => $addsubcategories));
+
+        // Retrieve the categories.
+        $categories = array();
+        if (!empty($params['criteria'])) {
+
+            $conditions = array();
+            $wheres = array();
+            foreach ($params['criteria'] as $crit) {
+                $key = trim($crit['key']);
+
+                // Trying to avoid duplicate keys.
+                if (!isset($conditions[$key])) {
 
-        if (! ($course = $DB->get_record('course', array('id'=>$params['courseid'])))) {
-            throw new moodle_exception('invalidcourseid', 'error', '', $params['courseid']);
-        }
+                    $context = context_system::instance();
+                    $value = null;
+                    switch ($key) {
+                        case 'id':
+                            $value = clean_param($crit['value'], PARAM_INT);
+                            break;
 
-        // Category where duplicated course is going to be created.
-        $categorycontext = context_coursecat::instance($params['categoryid']);
-        self::validate_context($categorycontext);
+                        case 'idnumber':
+                            if (has_capability('moodle/category:manage', $context)) {
+                                $value = clean_param($crit['value'], PARAM_RAW);
+                            } else {
+                                // We must throw an exception.
+                                // Otherwise the dev client would think no idnumber exists.
+                                throw new moodle_exception('criteriaerror',
+                                        'webservice', '', null,
+                                        'You don\'t have the permissions to search on the "idnumber" field.');
+                            }
+                            break;
 
-        // Course to be duplicated.
-        $coursecontext = context_course::instance($course->id);
-        self::validate_context($coursecontext);
+                        case 'name':
+                            $value = clean_param($crit['value'], PARAM_TEXT);
+                            break;
 
-        $backupdefaults = array(
-            'activities' => 1,
-            'blocks' => 1,
-            'filters' => 1,
-            'users' => 0,
-            'role_assignments' => 0,
-            'user_files' => 0,
-            'comments' => 0,
-            'completion_information' => 0,
-            'logs' => 0,
-            'histories' => 0
-        );
+                        case 'parent':
+                            $value = clean_param($crit['value'], PARAM_INT);
+                            break;
 
-        $backupsettings = array();
-        // Check for backup and restore options.
-        if (!empty($params['options'])) {
-            foreach ($params['options'] as $option) {
+                        case 'visible':
+                            if (has_capability('moodle/category:manage', $context)
+                                or has_capability('moodle/category:viewhiddencategories',
+                                        context_system::instance())) {
+                                $value = clean_param($crit['value'], PARAM_INT);
+                            } else {
+                                throw new moodle_exception('criteriaerror',
+                                        'webservice', '', null,
+                                        'You don\'t have the permissions to search on the "visible" field.');
+                            }
+                            break;
 
-                // Strict check for a correct value (allways 1 or 0, true or false).
-                $value = clean_param($option['value'], PARAM_INT);
+                        case 'theme':
+                            if (has_capability('moodle/category:manage', $context)) {
+                                $value = clean_param($crit['value'], PARAM_THEME);
+                            } else {
+                                throw new moodle_exception('criteriaerror',
+                                        'webservice', '', null,
+                                        'You don\'t have the permissions to search on the "theme" field.');
+                            }
+                            break;
 
-                if ($value !== 0 and $value !== 1) {
-                    throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
-                }
+                        default:
+                            throw new moodle_exception('criteriaerror',
+                                    'webservice', '', null,
+                                    'You can not search on this criteria: ' . $key);
+                    }
 
-                if (!isset($backupdefaults[$option['name']])) {
-                    throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
+                    if (isset($value)) {
+                        $conditions[$key] = $crit['value'];
+                        $wheres[] = $key . " = :" . $key;
+                    }
                 }
-
-                $backupsettings[$option['name']] = $value;
             }
-        }
 
-        // Capability checking.
+            if (!empty($wheres)) {
+                $wheres = implode(" AND ", $wheres);
 
-        // The backup controller check for this currently, this may be redundant.
-        require_capability('moodle/course:create', $categorycontext);
-        require_capability('moodle/restore:restorecourse', $categorycontext);
-        require_capability('moodle/backup:backupcourse', $coursecontext);
+                $categories = $DB->get_records_select('course_categories', $wheres, $conditions);
 
-        if (!empty($backupsettings['users'])) {
-            require_capability('moodle/backup:userinfo', $coursecontext);
-            require_capability('moodle/restore:userinfo', $categorycontext);
-        }
+                // Retrieve its sub subcategories (all levels).
+                if ($categories and !empty($params['addsubcategories'])) {
+                    $newcategories = array();
 
-        // Check if the shortname is used.
-        if ($foundcourses = $DB->get_records('course', array('shortname'=>$shortname))) {
-            foreach ($foundcourses as $foundcourse) {
-                $foundcoursenames[] = $foundcourse->fullname;
+                    foreach ($categories as $category) {
+                        $sqllike = $DB->sql_like('path', ':path');
+                        $sqlparams = array('path' => $category->path.'/%'); // It will NOT include the specified category.
+                        $subcategories = $DB->get_records_select('course_categories', $sqllike, $sqlparams);
+                        $newcategories = $newcategories + $subcategories;   // Both arrays have integer as keys.
+                    }
+                    $categories = $categories + $newcategories;
+                }
             }
 
-            $foundcoursenamestring = implode(',', $foundcoursenames);
-            throw new moodle_exception('shortnametaken', '', '', $foundcoursenamestring);
-        }
-
-        // Backup the course.
-
-        $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
-        backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $USER->id);
-
-        foreach ($backupsettings as $name => $value) {
-            $bc->get_plan()->get_setting($name)->set_value($value);
+        } else {
+            // Retrieve all categories in the database.
+            $categories = $DB->get_records('course_categories');
         }
 
-        $backupid       = $bc->get_backupid();
-        $backupbasepath = $bc->get_plan()->get_basepath();
+        // The not returned categories. key => category id, value => reason of exclusion.
+        $excludedcats = array();
 
-        $bc->execute_plan();
-        $results = $bc->get_results();
-        $file = $results['backup_destination'];
+        // The returned categories.
+        $categoriesinfo = array();
 
-        $bc->destroy();
+        // We need to sort the categories by path.
+        // The parent cats need to be checked by the algo first.
+        usort($categories, "core_course_external::compare_categories_by_path");
 
-        // Restore the backup immediately.
+        foreach ($categories as $category) {
 
-        // Check if we need to unzip the file because the backup temp dir does not contains backup files.
-        if (!file_exists($backupbasepath . "/moodle_backup.xml")) {
-            $file->extract_to_pathname(get_file_packer(), $backupbasepath);
-        }
+            // Check if the category is a child of an excluded category, if yes exclude it too (excluded => do not return).
+            $parents = explode('/', $category->path);
+            unset($parents[0]); // First key is always empty because path start with / => /1/2/4.
+            foreach ($parents as $parentid) {
+                // Note: when the parent exclusion was due to the context,
+                // the sub category could still be returned.
+                if (isset($excludedcats[$parentid]) and $excludedcats[$parentid] != 'context') {
+                    $excludedcats[$category->id] = 'parent';
+                }
+            }
 
-        // Create new course.
-        $newcourseid = restore_dbops::create_new_course($params['fullname'], $params['shortname'], $params['categoryid']);
+            // Check category depth is <= maxdepth (do not check for user who can manage categories).
+            if ((!empty($CFG->maxcategorydepth) && count($parents) > $CFG->maxcategorydepth)
+                    and !has_capability('moodle/category:manage', $context)) {
+                $excludedcats[$category->id] = 'depth';
+            }
 
-        $rc = new restore_controller($backupid, $newcourseid,
-                backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $USER->id, backup::TARGET_NEW_COURSE);
+            // Check the user can use the category context.
+            $context = context_coursecat::instance($category->id);
+            try {
+                self::validate_context($context);
+            } catch (Exception $e) {
+                $excludedcats[$category->id] = 'context';
 
-        foreach ($backupsettings as $name => $value) {
-            $setting = $rc->get_plan()->get_setting($name);
-            if ($setting->get_status() == backup_setting::NOT_LOCKED) {
-                $setting->set_value($value);
+                // If it was the requested category then throw an exception.
+                if (isset($params['categoryid']) && $category->id == $params['categoryid']) {
+                    $exceptionparam = new stdClass();
+                    $exceptionparam->message = $e->getMessage();
+                    $exceptionparam->catid = $category->id;
+                    throw new moodle_exception('errorcatcontextnotvalid', 'webservice', '', $exceptionparam);
+                }
             }
-        }
 
-        if (!$rc->execute_precheck()) {
-            $precheckresults = $rc->get_precheck_results();
-            if (is_array($precheckresults) && !empty($precheckresults['errors'])) {
-                if (empty($CFG->keeptempdirectoriesonbackup)) {
-                    fulldelete($backupbasepath);
-                }
+            // Return the category information.
+            if (!isset($excludedcats[$category->id])) {
 
-                $errorinfo = '';
+                // Final check to see if the category is visible to the user.
+                if ($category->visible
+                        or has_capability('moodle/category:viewhiddencategories', context_system::instance())
+                        or has_capability('moodle/category:manage', $context)) {
 
-                foreach ($precheckresults['errors'] as $error) {
-                    $errorinfo .= $error;
-                }
+                    $categoryinfo = array();
+                    $categoryinfo['id'] = $category->id;
+                    $categoryinfo['name'] = $category->name;
+                    $categoryinfo['description'] = file_rewrite_pluginfile_urls($category->description,
+                            'webservice/pluginfile.php', $context->id, 'coursecat', 'description', null);
+                    $options = new stdClass;
+                    $options->noclean = true;
+                    $options->para = false;
+                    $categoryinfo['description'] = format_text($categoryinfo['description'],
+                            $category->descriptionformat, $options);
+                    $categoryinfo['parent'] = $category->parent;
+                    $categoryinfo['sortorder'] = $category->sortorder;
+                    $categoryinfo['coursecount'] = $category->coursecount;
+                    $categoryinfo['depth'] = $category->depth;
+                    $categoryinfo['path'] = $category->path;
 
-                if (array_key_exists('warnings', $precheckresults)) {
-                    foreach ($precheckresults['warnings'] as $warning) {
-                        $errorinfo .= $warning;
+                    // Some fields only returned for admin.
+                    if (has_capability('moodle/category:manage', $context)) {
+                        $categoryinfo['idnumber'] = $category->idnumber;
+                        $categoryinfo['visible'] = $category->visible;
+                        $categoryinfo['visibleold'] = $category->visibleold;
+                        $categoryinfo['timemodified'] = $category->timemodified;
+                        $categoryinfo['theme'] = $category->theme;
                     }
-                }
 
-                throw new moodle_exception('backupprecheckerrors', 'webservice', '', $errorinfo);
+                    $categoriesinfo[] = $categoryinfo;
+                } else {
+                    $excludedcats[$category->id] = 'visibility';
+                }
             }
         }
 
-        $rc->execute_plan();
-        $rc->destroy();
-
-        $course = $DB->get_record('course', array('id' => $newcourseid), '*', MUST_EXIST);
-        $course->fullname = $params['fullname'];
-        $course->shortname = $params['shortname'];
-        $course->visible = $params['visible'];
-
-        // Set shortname and fullname back.
-        $DB->update_record('course', $course);
+        // Sorting the resulting array so it looks a bit better for the client developer.
+        usort($categoriesinfo, "core_course_external::compare_categories_by_sortorder");
 
-        if (empty($CFG->keeptempdirectoriesonbackup)) {
-            fulldelete($backupbasepath);
-        }
+        return $categoriesinfo;
+    }
 
-        // Delete the course backup file created by this WebService. Originally located in the course backups area.
-        $file->delete();
+    /**
+     * Sort categories array by path
+     * private function: only used by get_categories
+     *
+     * @param array $category1
+     * @param array $category2
+     * @return int result of strcmp
+     * @since Moodle 2.3
+     */
+    private static function compare_categories_by_path($category1, $category2) {
+        return strcmp($category1->path, $category2->path);
+    }
 
-        return array('id' => $course->id, 'shortname' => $course->shortname);
+    /**
+     * Sort categories array by sortorder
+     * private function: only used by get_categories
+     *
+     * @param array $category1
+     * @param array $category2
+     * @return int result of strcmp
+     * @since Moodle 2.3
+     */
+    private static function compare_categories_by_sortorder($category1, $category2) {
+        return strcmp($category1['sortorder'], $category2['sortorder']);
     }
 
     /**
@@ -1280,31 +1156,50 @@ class core_course_external extends external_api {
      * @return external_description
      * @since Moodle 2.3
      */
-    public static function duplicate_course_returns() {
-        return new external_single_structure(
-            array(
-                'id'       => new external_value(PARAM_INT, 'course id'),
-                'shortname' => new external_value(PARAM_TEXT, 'short name'),
+    public static function get_categories_returns() {
+        return new external_multiple_structure(
+            new external_single_structure(
+                array(
+                    'id' => new external_value(PARAM_INT, 'category id'),
+                    'name' => new external_value(PARAM_TEXT, 'category name'),
+                    'idnumber' => new external_value(PARAM_RAW, 'category id number', VALUE_OPTIONAL),
+                    'description' => new external_value(PARAM_RAW, 'category description'),
+                    'parent' => new external_value(PARAM_INT, 'parent category id'),
+                    'sortorder' => new external_value(PARAM_INT, 'category sorting order'),
+                    'coursecount' => new external_value(PARAM_INT, 'number of courses in this category'),
+                    'visible' => new external_value(PARAM_INT, '1: available, 0:not available', VALUE_OPTIONAL),
+                    'visibleold' => new external_value(PARAM_INT, '1: available, 0:not available', VALUE_OPTIONAL),
+                    'timemodified' => new external_value(PARAM_INT, 'timestamp', VALUE_OPTIONAL),
+                    'depth' => new external_value(PARAM_INT, 'category depth'),
+                    'path' => new external_value(PARAM_TEXT, 'category path'),
+                    'theme' => new external_value(PARAM_THEME, 'category theme', VALUE_OPTIONAL),
+                ), 'List of categories'
             )
         );
     }
 
     /**
      * Returns description of method parameters
+     *
      * @return external_function_parameters
      * @since Moodle 2.3
      */
-    public static function delete_categories_parameters() {
+    public static function create_categories_parameters() {
         return new external_function_parameters(
             array(
                 'categories' => new external_multiple_structure(
-                    new external_single_structure(
-                        array(
-                            'id' => new external_value(PARAM_INT, 'category id to delete'),
-                            'newparent' => new external_value(PARAM_INT,
-                                'the parent category to move the contents to, if specified', VALUE_OPTIONAL),
-                            'recursive' => new external_value(PARAM_BOOL, '1: recursively delete all contents inside this
-                                category, 0 (default): move contents to newparent or current parent category (except if parent is root)', VALUE_DEFAULT, 0)
+                        new external_single_structure(
+                            array(
+                                'name' => new external_value(PARAM_TEXT, 'new category name'),
+                                'parent' => new external_value(PARAM_INT,
+                                        'the parent category id inside which the new category will be created'),
+                                'idnumber' => new external_value(PARAM_RAW,
+                                        'the new category idnumber', VALUE_OPTIONAL),
+                                'description' => new external_value(PARAM_RAW,
+                                        'the new category description', VALUE_OPTIONAL),
+                                'theme' => new external_value(PARAM_THEME,
+                                        'the new category theme. This option must be enabled on moodle',
+                                        VALUE_OPTIONAL),
                         )
                     )
                 )
@@ -1313,68 +1208,96 @@ class core_course_external extends external_api {
     }
 
     /**
-     * Delete categories
-     * @param array $categories A list of category ids
-     * @return array
+     * Create categories
+     *
+     * @param array $categories - see create_categories_parameters() for the array structure
+     * @return array - see create_categories_returns() for the array structure
      * @since Moodle 2.3
      */
-    public static function delete_categories($categories) {
+    public static function create_categories($categories) {
         global $CFG, $DB;
         require_once($CFG->dirroot . "/course/lib.php");
 
-        // Validate parameters.
-        $params = self::validate_parameters(self::delete_categories_parameters(), array('categories' => $categories));
+        $params = self::validate_parameters(self::create_categories_parameters(),
+                        array('categories' => $categories));
 
+        $transaction = $DB->start_delegated_transaction();
+
+        $createdcategories = array();
         foreach ($params['categories'] as $category) {
-            if (!$deletecat = $DB->get_record('course_categories', array('id' => $category['id']))) {
-                throw new moodle_exception('unknowcategory');
+            if ($category['parent']) {
+                if (!$DB->record_exists('course_categories', array('id' => $category['parent']))) {
+                    throw new moodle_exception('unknowcategory');
+                }
+                $context = context_coursecat::instance($category['parent']);
+            } else {
+                $context = context_system::instance();
             }
-            $context = context_coursecat::instance($deletecat->id);
-            require_capability('moodle/category:manage', $context);
             self::validate_context($context);
-            self::validate_context(get_category_or_system_context($deletecat->parent));
+            require_capability('moodle/category:manage', $context);
 
-            if ($category['recursive']) {
-                // If recursive was specified, then we recursively delete the category's contents.
-                category_delete_full($deletecat, false);
-            } else {
-                // In this situation, we don't delete the category's contents, we either move it to newparent or parent.
-                // If the parent is the root, moving is not supported (because a course must always be inside a category).
-                // We must move to an existing category.
-                if (!empty($category['newparent'])) {
-                    if (!$DB->record_exists('course_categories', array('id' => $category['newparent']))) {
-                        throw new moodle_exception('unknowcategory');
-                    }
-                    $newparent = $category['newparent'];
-                } else {
-                    $newparent = $deletecat->parent;
+            // Check id number.
+            if (!empty($category['idnumber'])) { // Same as in course/editcategory_form.php .
+                if (textlib::strlen($category['idnumber'])>100) {
+                    throw new moodle_exception('idnumbertoolong');
                 }
-
-                // This operation is not allowed. We must move contents to an existing category.
-                if ($newparent == 0) {
-                    throw new moodle_exception('movecatcontentstoroot');
+                if ($existing = $DB->get_record('course_categories', array('idnumber' => $category['idnumber']))) {
+                    if ($existing->id) {
+                        throw new moodle_exception('idnumbertaken');
+                    }
                 }
+            }
+            // Check name.
+            if (textlib::strlen($category['name'])>255) {
+                throw new moodle_exception('categorytoolong');
+            }
 
-                $parentcontext = get_category_or_system_context($newparent);
-                require_capability('moodle/category:manage', $parentcontext);
-                self::validate_context($parentcontext);
-                category_delete_move($deletecat, $newparent, false);
+            $newcategory = new stdClass();
+            $newcategory->name = $category['name'];
+            $newcategory->parent = $category['parent'];
+            $newcategory->idnumber = $category['idnumber'];
+            $newcategory->sortorder = 999; // Same as in the course/editcategory.php .
+            // Format the description.
+            if (!empty($category['description'])) {
+                $newcategory->description = $category['description'];
+            }
+            $newcategory->descriptionformat = FORMAT_HTML;
+            if (isset($category['theme']) and !empty($CFG->allowcategorythemes)) {
+                $newcategory->theme = $category['theme'];
             }
+
+            $newcategory = create_course_category($newcategory);
+            // Populate special fields.
+            fix_course_sortorder();
+
+            $createdcategories[] = array('id' => $newcategory->id, 'name' => $newcategory->name);
         }
 
+        $transaction->allow_commit();
+
+        return $createdcategories;
     }
 
     /**
      * Returns description of method parameters
+     *
      * @return external_function_parameters
      * @since Moodle 2.3
      */
-    public static function delete_categories_returns() {
-        return null;
+    public static function create_categories_returns() {
+        return new external_multiple_structure(
+            new external_single_structure(
+                array(
+                    'id' => new external_value(PARAM_INT, 'new category id'),
+                    'name' => new external_value(PARAM_TEXT, 'new category name'),
+                )
+            )
+        );
     }
 
     /**
      * Returns description of method parameters
+     *
      * @return external_function_parameters
      * @since Moodle 2.3
      */
@@ -1400,6 +1323,7 @@ class core_course_external extends external_api {
 
     /**
      * Update categories
+     *
      * @param array $categories The list of categories to update
      * @return null
      * @since Moodle 2.3
@@ -1461,11 +1385,101 @@ class core_course_external extends external_api {
 
     /**
      * Returns description of method result value
+     *
      * @return external_description
+     * @since Moodle 2.3
      */
     public static function update_categories_returns() {
         return null;
     }
+
+    /**
+     * Returns description of method parameters
+     *
+     * @return external_function_parameters
+     * @since Moodle 2.3
+     */
+    public static function delete_categories_parameters() {
+        return new external_function_parameters(
+            array(
+                'categories' => new external_multiple_structure(
+                    new external_single_structure(
+                        array(
+                            'id' => new external_value(PARAM_INT, 'category id to delete'),
+                            'newparent' => new external_value(PARAM_INT,
+                                'the parent category to move the contents to, if specified', VALUE_OPTIONAL),
+                            'recursive' => new external_value(PARAM_BOOL, '1: recursively delete all contents inside this
+                                category, 0 (default): move contents to newparent or current parent category (except if parent is root)', VALUE_DEFAULT, 0)
+                        )
+                    )
+                )
+            )
+        );
+    }
+
+    /**
+     * Delete categories
+     *
+     * @param array $categories A list of category ids
+     * @return array
+     * @since Moodle 2.3
+     */
+    public static function delete_categories($categories) {
+        global $CFG, $DB;
+        require_once($CFG->dirroot . "/course/lib.php");
+
+        // Validate parameters.
+        $params = self::validate_parameters(self::delete_categories_parameters(), array('categories' => $categories));
+
+        foreach ($params['categories'] as $category) {
+            if (!$deletecat = $DB->get_record('course_categories', array('id' => $category['id']))) {
+                throw new moodle_exception('unknowcategory');
+            }
+            $context = context_coursecat::instance($deletecat->id);
+            require_capability('moodle/category:manage', $context);
+            self::validate_context($context);
+            self::validate_context(get_category_or_system_context($deletecat->parent));
+
+            if ($category['recursive']) {
+                // If recursive was specified, then we recursively delete the category's contents.
+                category_delete_full($deletecat, false);
+            } else {
+                // In this situation, we don't delete the category's contents, we either move it to newparent or parent.
+                // If the parent is the root, moving is not supported (because a course must always be inside a category).
+                // We must move to an existing category.
+                if (!empty($category['newparent'])) {
+                    if (!$DB->record_exists('course_categories', array('id' => $category['newparent']))) {
+                        throw new moodle_exception('unknowcategory');
+                    }
+                    $newparent = $category['newparent'];
+                } else {
+                    $newparent = $deletecat->parent;
+                }
+
+                // This operation is not allowed. We must move contents to an existing category.
+                if ($newparent == 0) {
+                    throw new moodle_exception('movecatcontentstoroot');
+                }
+
+                $parentcontext = get_category_or_system_context($newparent);
+                require_capability('moodle/category:manage', $parentcontext);
+                self::validate_context($parentcontext);
+                category_delete_move($deletecat, $newparent, false);
+            }
+        }
+
+    }
+
+    /**
+     * Returns description of method parameters
+     *
+     * @return external_function_parameters
+     * @since Moodle 2.3
+     */
+    public static function delete_categories_returns() {
+        return null;
+    }
+
 }
 
 /**