MDL-30775 get_course_contents: remove warnings when the section is empty
[moodle.git] / course / externallib.php
CommitLineData
8e7d3fe4 1<?php
8e7d3fe4
PS
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/>.
16
4615817d 17
8e7d3fe4 18/**
6bb31e40 19 * External course API
8e7d3fe4 20 *
4615817d
JM
21 * @package core_course
22 * @category external
23 * @copyright 2009 Petr Skodak
8e7d3fe4
PS
24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 */
26
df997f84
PS
27defined('MOODLE_INTERNAL') || die;
28
8e7d3fe4
PS
29require_once("$CFG->libdir/externallib.php");
30
5d1017e1 31/**
4615817d
JM
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
5d1017e1
JM
39 */
40class core_course_external extends external_api {
8e7d3fe4 41
ec0d6ea2
DC
42 /**
43 * Returns description of method parameters
4615817d 44 *
ec0d6ea2 45 * @return external_function_parameters
4615817d 46 * @since Moodle 2.2
ec0d6ea2
DC
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 }
60
61 /**
62 * Get course contents
4615817d
JM
63 *
64 * @param int $courseid course id
65 * @param array $options These options are not used yet, might be used in later version
ec0d6ea2 66 * @return array
4615817d 67 * @since Moodle 2.2
ec0d6ea2 68 */
3297d575 69 public static function get_course_contents($courseid, $options = array()) {
ec0d6ea2
DC
70 global $CFG, $DB;
71 require_once($CFG->dirroot . "/course/lib.php");
72
73 //validate parameter
74 $params = self::validate_parameters(self::get_course_contents_parameters(),
75 array('courseid' => $courseid, 'options' => $options));
76
77 //retrieve the course
78 $course = $DB->get_record('course', array('id' => $params['courseid']), '*', MUST_EXIST);
79
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 }
86
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 }
97
98 $canupdatecourse = has_capability('moodle/course:update', $context);
99
100 //create return value
101 $coursecontents = array();
102
103 if ($canupdatecourse or $course->visible
104 or has_capability('moodle/course:viewhiddencourses', $context)) {
105
106 //retrieve sections
107 $modinfo = get_fast_modinfo($course);
108 $sections = get_all_sections($course->id);
109
110 //for each sections (first displayed to last displayed)
46295524 111 $modinfosections = $modinfo->get_sections();
ec0d6ea2
DC
112 foreach ($sections as $key => $section) {
113
114 $showsection = (has_capability('moodle/course:viewhiddensections', $context) or $section->visible or !$course->hiddensections);
115 if (!$showsection) {
116 continue;
117 }
118
119 // reset $sectioncontents
120 $sectionvalues = array();
121 $sectionvalues['id'] = $section->id;
122 $sectionvalues['name'] = get_section_name($course, $section);
ec0d6ea2 123 $sectionvalues['visible'] = $section->visible;
93ce0e82
JM
124 list($sectionvalues['summary'], $sectionvalues['summaryformat']) =
125 external_format_text($section->summary, $section->summaryformat,
126 $context->id, 'course', 'section', $section->id);
ec0d6ea2
DC
127 $sectioncontents = array();
128
129 //for each module of the section
46295524
JM
130 if (!empty($modinfosections[$section->section])) {
131 foreach ($modinfosections[$section->section] as $cmid) {
132 $cm = $modinfo->cms[$cmid];
ec0d6ea2 133
46295524
JM
134 // stop here if the module is not visible to the user
135 if (!$cm->uservisible) {
136 continue;
137 }
ec0d6ea2 138
46295524 139 $module = array();
ec0d6ea2 140
46295524
JM
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;
ec0d6ea2 148
46295524 149 $modcontext = context_module::instance($cm->id);
ec0d6ea2 150
46295524
JM
151 if (!empty($cm->showdescription)) {
152 $module['description'] = $cm->get_content();
153 }
ec0d6ea2 154
46295524
JM
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 }
ec0d6ea2 160
46295524
JM
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;
ec0d6ea2 165
46295524
JM
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 }
ec0d6ea2 171
46295524 172 $baseurl = 'webservice/pluginfile.php';
ec0d6ea2 173
46295524
JM
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 }
ec0d6ea2 182 }
ec0d6ea2 183
46295524
JM
184 //assign result to $sectioncontents
185 $sectioncontents[] = $module;
ec0d6ea2 186
46295524 187 }
ec0d6ea2
DC
188 }
189 $sectionvalues['modules'] = $sectioncontents;
190
191 // assign result to $coursecontents
192 $coursecontents[] = $sectionvalues;
193 }
194 }
195 return $coursecontents;
196 }
197
198 /**
199 * Returns description of method result value
4615817d 200 *
ec0d6ea2 201 * @return external_description
4615817d 202 * @since Moodle 2.2
ec0d6ea2
DC
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'),
93ce0e82 212 'summaryformat' => new external_format_value('summary'),
ec0d6ea2
DC
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),
d872b3dc 218 'name' => new external_value(PARAM_RAW, 'activity module name'),
ec0d6ea2
DC
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'),
240
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 }
255
6bb31e40 256 /**
257 * Returns description of method parameters
4615817d 258 *
6bb31e40 259 * @return external_function_parameters
754c2dea 260 * @since Moodle 2.3
6bb31e40 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 }
274
275 /**
276 * Get courses
4615817d
JM
277 *
278 * @param array $options It contains an array (list of ids)
6bb31e40 279 * @return array
4615817d 280 * @since Moodle 2.2
6bb31e40 281 */
3297d575 282 public static function get_courses($options = array()) {
6bb31e40 283 global $CFG, $DB;
284 require_once($CFG->dirroot . "/course/lib.php");
285
286 //validate parameter
287 $params = self::validate_parameters(self::get_courses_parameters(),
288 array('options' => $options));
289
290 //retrieve courses
786b8316 291 if (!array_key_exists('ids', $params['options'])
6bb31e40 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 }
297
298 //create return value
299 $coursesinfo = array();
300 foreach ($courses as $course) {
301
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;
96d3b93b 310 throw new moodle_exception('errorcoursecontextnotvalid', 'webservice', '', $exceptionparam);
6bb31e40 311 }
312 require_capability('moodle/course:view', $context);
313
314 $courseinfo = array();
315 $courseinfo['id'] = $course->id;
316 $courseinfo['fullname'] = $course->fullname;
317 $courseinfo['shortname'] = $course->shortname;
318 $courseinfo['categoryid'] = $course->category;
93ce0e82
JM
319 list($courseinfo['summary'], $courseinfo['summaryformat']) =
320 external_format_text($course->summary, $course->summaryformat, $context->id, 'course', 'summary', 0);
6bb31e40 321 $courseinfo['format'] = $course->format;
322 $courseinfo['startdate'] = $course->startdate;
323 $courseinfo['numsections'] = $course->numsections;
324
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 }
347
348 if ($courseadmin or $course->visible
349 or has_capability('moodle/course:viewhiddencourses', $context)) {
350 $coursesinfo[] = $courseinfo;
351 }
352 }
353
354 return $coursesinfo;
355 }
356
357 /**
358 * Returns description of method result value
4615817d 359 *
6bb31e40 360 * @return external_description
4615817d 361 * @since Moodle 2.2
6bb31e40 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'),
93ce0e82 375 'summaryformat' => new external_format_value('summary'),
aff24313 376 'format' => new external_value(PARAM_PLUGIN,
6bb31e40 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',
3ec163dd
EL
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'
479a5db1 420 )
479a5db1
FS
421 );
422 }
423
6bb31e40 424 /**
425 * Returns description of method parameters
4615817d 426 *
6bb31e40 427 * @return external_function_parameters
4615817d 428 * @since Moodle 2.2
6bb31e40 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),
93ce0e82 442 'summaryformat' => new external_format_value('summary', VALUE_DEFAULT),
aff24313 443 'format' => new external_value(PARAM_PLUGIN,
6bb31e40 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,
8a6b1193 474 'Enabled, control via completion and activity settings. Disabled,
6bb31e40 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),
aff24313 483 'lang' => new external_value(PARAM_SAFEDIR,
6bb31e40 484 'forced course language', VALUE_OPTIONAL),
aff24313 485 'forcetheme' => new external_value(PARAM_PLUGIN,
6bb31e40 486 'name of the force theme', VALUE_OPTIONAL),
487 )
488 ), 'courses to create'
489 )
490 )
491 );
492 }
493
494 /**
495 * Create courses
4615817d 496 *
6bb31e40 497 * @param array $courses
498 * @return array courses (id and shortname only)
4615817d 499 * @since Moodle 2.2
6bb31e40 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');
505
6bb31e40 506 $params = self::validate_parameters(self::create_courses_parameters(),
507 array('courses' => $courses));
508
509 $availablethemes = get_plugin_list('theme');
510 $availablelangs = get_string_manager()->get_list_of_translations();
511
512 $transaction = $DB->start_delegated_transaction();
513
514 foreach ($params['courses'] as $course) {
515
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'];
96d3b93b 524 throw new moodle_exception('errorcatcontextnotvalid', 'webservice', '', $exceptionparam);
6bb31e40 525 }
526 require_capability('moodle/course:create', $context);
527
528 // Make sure lang is valid
786b8316 529 if (array_key_exists('lang', $course) and empty($availablelangs[$course['lang']])) {
96d3b93b 530 throw new moodle_exception('errorinvalidparam', 'webservice', '', 'lang');
6bb31e40 531 }
532
533 // Make sure theme is valid
786b8316 534 if (array_key_exists('forcetheme', $course)) {
6bb31e40 535 if (!empty($CFG->allowcoursethemes)) {
536 if (empty($availablethemes[$course['forcetheme']])) {
96d3b93b 537 throw new moodle_exception('errorinvalidparam', 'webservice', '', 'forcetheme');
6bb31e40 538 } else {
539 $course['theme'] = $course['forcetheme'];
540 }
541 }
542 }
543
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 }
549
550 //set default value for completion
8a6b1193 551 $courseconfig = get_config('moodlecourse');
6bb31e40 552 if (completion_info::is_enabled_for_site()) {
786b8316 553 if (!array_key_exists('enablecompletion', $course)) {
8a6b1193 554 $course['enablecompletion'] = $courseconfig->enablecompletion;
6bb31e40 555 }
786b8316 556 if (!array_key_exists('completionstartonenrol', $course)) {
8a6b1193 557 $course['completionstartonenrol'] = $courseconfig->completionstartonenrol;
6bb31e40 558 }
559 } else {
560 $course['enablecompletion'] = 0;
561 $course['completionstartonenrol'] = 0;
562 }
563
564 $course['category'] = $course['categoryid'];
565
93ce0e82
JM
566 // Summary format.
567 $course['summaryformat'] = external_validate_format($course['summaryformat']);
568
6bb31e40 569 //Note: create_course() core function check shortname, idnumber, category
570 $course['id'] = create_course((object) $course)->id;
571
572 $resultcourses[] = array('id' => $course['id'], 'shortname' => $course['shortname']);
573 }
574
575 $transaction->allow_commit();
576
577 return $resultcourses;
578 }
579
580 /**
581 * Returns description of method result value
4615817d 582 *
6bb31e40 583 * @return external_description
4615817d 584 * @since Moodle 2.2
6bb31e40 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 }
596
63a85dc7
JL
597 /**
598 * Returns description of method parameters
3ec163dd 599 *
63a85dc7 600 * @return external_function_parameters
3ec163dd 601 * @since Moodle 2.2
63a85dc7
JL
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 }
610
611 /**
612 * Delete courses
3ec163dd 613 *
63a85dc7 614 * @param array $courseids A list of course ids
3ec163dd 615 * @since Moodle 2.2
63a85dc7
JL
616 */
617 public static function delete_courses($courseids) {
618 global $CFG, $DB;
619 require_once($CFG->dirroot."/course/lib.php");
620
621 // Parameter validation.
622 $params = self::validate_parameters(self::delete_courses_parameters(), array('courseids'=>$courseids));
623
624 $transaction = $DB->start_delegated_transaction();
625
626 foreach ($params['courseids'] as $courseid) {
627 $course = $DB->get_record('course', array('id'=>$courseid), '*', MUST_EXIST);
628
629 // Check if the context is valid.
630 $coursecontext = context_course::instance($course->id);
631 self::validate_context($coursecontext);
632
633 // Check if the current user has enought permissions.
634 if (!can_delete_course($courseid)) {
d6ebe011
JM
635 throw new moodle_exception('cannotdeletecategorycourse', 'error',
636 '', format_string($course->fullname)." (id: $courseid)");
63a85dc7
JL
637 }
638
639 delete_course($course, false);
640 }
641
642 $transaction->allow_commit();
643
644 return null;
645 }
646
647 /**
648 * Returns description of method result value
3ec163dd 649 *
63a85dc7 650 * @return external_description
3ec163dd 651 * @since Moodle 2.2
63a85dc7
JL
652 */
653 public static function delete_courses_returns() {
654 return null;
655 }
656
3dc1d76e
JL
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(
3240c710 674 'name' => new external_value(PARAM_ALPHAEXT, 'The backup option name:
9aa84e91
JL
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),
9aa84e91
JL
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 )
3dc1d76e
JL
687 )
688 ), VALUE_DEFAULT, array()
689 ),
690 )
691 );
692 }
693
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 */
3297d575 706 public static function duplicate_course($courseid, $fullname, $shortname, $categoryid, $visible = 1, $options = array()) {
3dc1d76e
JL
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');
710
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 );
723
3ec163dd
EL
724 // Context validation.
725
726 if (! ($course = $DB->get_record('course', array('id'=>$params['courseid'])))) {
28fc596d 727 throw new moodle_exception('invalidcourseid', 'error');
3ec163dd
EL
728 }
729
730 // Category where duplicated course is going to be created.
731 $categorycontext = context_coursecat::instance($params['categoryid']);
732 self::validate_context($categorycontext);
733
734 // Course to be duplicated.
735 $coursecontext = context_course::instance($course->id);
736 self::validate_context($coursecontext);
737
738 $backupdefaults = array(
739 'activities' => 1,
740 'blocks' => 1,
741 'filters' => 1,
742 'users' => 0,
743 'role_assignments' => 0,
3ec163dd
EL
744 'comments' => 0,
745 'completion_information' => 0,
746 'logs' => 0,
747 'histories' => 0
748 );
749
750 $backupsettings = array();
751 // Check for backup and restore options.
752 if (!empty($params['options'])) {
753 foreach ($params['options'] as $option) {
754
755 // Strict check for a correct value (allways 1 or 0, true or false).
756 $value = clean_param($option['value'], PARAM_INT);
757
758 if ($value !== 0 and $value !== 1) {
759 throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
760 }
761
762 if (!isset($backupdefaults[$option['name']])) {
763 throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
764 }
765
766 $backupsettings[$option['name']] = $value;
767 }
768 }
769
770 // Capability checking.
771
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);
776
777 if (!empty($backupsettings['users'])) {
778 require_capability('moodle/backup:userinfo', $coursecontext);
779 require_capability('moodle/restore:userinfo', $categorycontext);
780 }
781
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 }
787
788 $foundcoursenamestring = implode(',', $foundcoursenames);
789 throw new moodle_exception('shortnametaken', '', '', $foundcoursenamestring);
790 }
791
792 // Backup the course.
793
794 $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
795 backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $USER->id);
796
797 foreach ($backupsettings as $name => $value) {
798 $bc->get_plan()->get_setting($name)->set_value($value);
799 }
800
801 $backupid = $bc->get_backupid();
802 $backupbasepath = $bc->get_plan()->get_basepath();
803
804 $bc->execute_plan();
805 $results = $bc->get_results();
806 $file = $results['backup_destination'];
807
808 $bc->destroy();
809
810 // Restore the backup immediately.
811
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 }
816
817 // Create new course.
818 $newcourseid = restore_dbops::create_new_course($params['fullname'], $params['shortname'], $params['categoryid']);
819
820 $rc = new restore_controller($backupid, $newcourseid,
821 backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $USER->id, backup::TARGET_NEW_COURSE);
822
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 }
829
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 }
836
837 $errorinfo = '';
838
839 foreach ($precheckresults['errors'] as $error) {
840 $errorinfo .= $error;
841 }
842
843 if (array_key_exists('warnings', $precheckresults)) {
844 foreach ($precheckresults['warnings'] as $warning) {
845 $errorinfo .= $warning;
846 }
847 }
848
849 throw new moodle_exception('backupprecheckerrors', 'webservice', '', $errorinfo);
850 }
851 }
852
853 $rc->execute_plan();
854 $rc->destroy();
855
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'];
860
861 // Set shortname and fullname back.
862 $DB->update_record('course', $course);
863
864 if (empty($CFG->keeptempdirectoriesonbackup)) {
865 fulldelete($backupbasepath);
866 }
867
868 // Delete the course backup file created by this WebService. Originally located in the course backups area.
869 $file->delete();
870
871 return array('id' => $course->id, 'shortname' => $course->shortname);
872 }
873
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 }
888
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,'.
6b5ce20c
JM
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.'.
3ec163dd 910 ' - user must have \'moodle/category:manage\' or \'moodle/category:viewhiddencategories\' to search on visible,'.
6b5ce20c 911 '"theme" (string) only return the categories having this theme'.
3ec163dd
EL
912 ' - user must have \'moodle/category:manage\' to search on theme'),
913 'value' => new external_value(PARAM_RAW, 'the value to match')
914 )
7a384506 915 ), 'criteria', VALUE_DEFAULT, array()
3ec163dd
EL
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 }
922
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");
934
935 // Validate parameters.
936 $params = self::validate_parameters(self::get_categories_parameters(),
937 array('criteria' => $criteria, 'addsubcategories' => $addsubcategories));
938
939 // Retrieve the categories.
940 $categories = array();
941 if (!empty($params['criteria'])) {
942
943 $conditions = array();
944 $wheres = array();
945 foreach ($params['criteria'] as $crit) {
946 $key = trim($crit['key']);
947
948 // Trying to avoid duplicate keys.
949 if (!isset($conditions[$key])) {
3dc1d76e 950
3ec163dd
EL
951 $context = context_system::instance();
952 $value = null;
953 switch ($key) {
954 case 'id':
955 $value = clean_param($crit['value'], PARAM_INT);
956 break;
3dc1d76e 957
3ec163dd
EL
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;
3dc1d76e 969
3ec163dd
EL
970 case 'name':
971 $value = clean_param($crit['value'], PARAM_TEXT);
972 break;
3dc1d76e 973
3ec163dd
EL
974 case 'parent':
975 $value = clean_param($crit['value'], PARAM_INT);
976 break;
9aa84e91 977
3ec163dd
EL
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;
9aa84e91 989
3ec163dd
EL
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;
9aa84e91 999
3ec163dd
EL
1000 default:
1001 throw new moodle_exception('criteriaerror',
1002 'webservice', '', null,
1003 'You can not search on this criteria: ' . $key);
1004 }
9aa84e91 1005
3ec163dd
EL
1006 if (isset($value)) {
1007 $conditions[$key] = $crit['value'];
1008 $wheres[] = $key . " = :" . $key;
1009 }
9aa84e91 1010 }
9aa84e91 1011 }
9aa84e91 1012
3ec163dd
EL
1013 if (!empty($wheres)) {
1014 $wheres = implode(" AND ", $wheres);
3dc1d76e 1015
3ec163dd 1016 $categories = $DB->get_records_select('course_categories', $wheres, $conditions);
3dc1d76e 1017
3ec163dd
EL
1018 // Retrieve its sub subcategories (all levels).
1019 if ($categories and !empty($params['addsubcategories'])) {
1020 $newcategories = array();
9aa84e91 1021
6b5ce20c
JM
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 }
1033
3ec163dd 1034 foreach ($categories as $category) {
6b5ce20c
JM
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);
3ec163dd
EL
1038 $newcategories = $newcategories + $subcategories; // Both arrays have integer as keys.
1039 }
1040 $categories = $categories + $newcategories;
1041 }
3dc1d76e
JL
1042 }
1043
3ec163dd
EL
1044 } else {
1045 // Retrieve all categories in the database.
1046 $categories = $DB->get_records('course_categories');
3dc1d76e
JL
1047 }
1048
3ec163dd
EL
1049 // The not returned categories. key => category id, value => reason of exclusion.
1050 $excludedcats = array();
3dc1d76e 1051
3ec163dd
EL
1052 // The returned categories.
1053 $categoriesinfo = array();
6c7d3e31 1054
3ec163dd
EL
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");
3dc1d76e 1058
3ec163dd 1059 foreach ($categories as $category) {
3dc1d76e 1060
3ec163dd
EL
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 }
9aa84e91 1071
3ec163dd
EL
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 }
3dc1d76e 1077
3ec163dd
EL
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';
3dc1d76e 1084
3ec163dd
EL
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 }
9aa84e91 1092 }
3dc1d76e 1093
3ec163dd
EL
1094 // Return the category information.
1095 if (!isset($excludedcats[$category->id])) {
3dc1d76e 1096
3ec163dd
EL
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)) {
3dc1d76e 1101
3ec163dd
EL
1102 $categoryinfo = array();
1103 $categoryinfo['id'] = $category->id;
1104 $categoryinfo['name'] = $category->name;
93ce0e82
JM
1105 list($categoryinfo['description'], $categoryinfo['descriptionformat']) =
1106 external_format_text($category->description, $category->descriptionformat,
1107 $context->id, 'coursecat', 'description', null);
3ec163dd
EL
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;
3dc1d76e 1113
3ec163dd
EL
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;
3dc1d76e 1121 }
3dc1d76e 1122
3ec163dd
EL
1123 $categoriesinfo[] = $categoryinfo;
1124 } else {
1125 $excludedcats[$category->id] = 'visibility';
1126 }
3dc1d76e
JL
1127 }
1128 }
1129
3ec163dd
EL
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");
3dc1d76e 1132
3ec163dd
EL
1133 return $categoriesinfo;
1134 }
3dc1d76e 1135
3ec163dd
EL
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 }
6c7d3e31 1148
3ec163dd
EL
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']);
3dc1d76e
JL
1160 }
1161
1162 /**
1163 * Returns description of method result value
1164 *
1165 * @return external_description
1166 * @since Moodle 2.3
1167 */
3ec163dd
EL
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'),
93ce0e82 1176 'descriptionformat' => new external_format_value('description'),
3ec163dd
EL
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'
3dc1d76e
JL
1187 )
1188 );
1189 }
1190
2f951d86
FS
1191 /**
1192 * Returns description of method parameters
3ec163dd 1193 *
2f951d86
FS
1194 * @return external_function_parameters
1195 * @since Moodle 2.3
1196 */
3ec163dd 1197 public static function create_categories_parameters() {
2f951d86
FS
1198 return new external_function_parameters(
1199 array(
1200 'categories' => new external_multiple_structure(
3ec163dd
EL
1201 new external_single_structure(
1202 array(
1203 'name' => new external_value(PARAM_TEXT, 'new category name'),
1204 'parent' => new external_value(PARAM_INT,
9615b623
JM
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),
3ec163dd
EL
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),
93ce0e82 1212 'descriptionformat' => new external_format_value('description', VALUE_DEFAULT),
3ec163dd
EL
1213 'theme' => new external_value(PARAM_THEME,
1214 'the new category theme. This option must be enabled on moodle',
1215 VALUE_OPTIONAL),
2f951d86
FS
1216 )
1217 )
1218 )
1219 )
1220 );
1221 }
1222
1223 /**
3ec163dd
EL
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
2f951d86
FS
1228 * @since Moodle 2.3
1229 */
3ec163dd 1230 public static function create_categories($categories) {
2f951d86
FS
1231 global $CFG, $DB;
1232 require_once($CFG->dirroot . "/course/lib.php");
1233
3ec163dd
EL
1234 $params = self::validate_parameters(self::create_categories_parameters(),
1235 array('categories' => $categories));
2f951d86 1236
3ec163dd
EL
1237 $transaction = $DB->start_delegated_transaction();
1238
1239 $createdcategories = array();
2f951d86 1240 foreach ($params['categories'] as $category) {
3ec163dd
EL
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();
2f951d86 1248 }
2f951d86 1249 self::validate_context($context);
3ec163dd 1250 require_capability('moodle/category:manage', $context);
2f951d86 1251
3ec163dd
EL
1252 // Check name.
1253 if (textlib::strlen($category['name'])>255) {
1254 throw new moodle_exception('categorytoolong');
1255 }
2f951d86 1256
3ec163dd
EL
1257 $newcategory = new stdClass();
1258 $newcategory->name = $category['name'];
1259 $newcategory->parent = $category['parent'];
3ec163dd
EL
1260 // Format the description.
1261 if (!empty($category['description'])) {
1262 $newcategory->description = $category['description'];
1263 }
93ce0e82 1264 $newcategory->descriptionformat = external_validate_format($category['descriptionformat']);
3ec163dd
EL
1265 if (isset($category['theme']) and !empty($CFG->allowcategorythemes)) {
1266 $newcategory->theme = $category['theme'];
2f951d86 1267 }
9615b623
JM
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 }
3ec163dd
EL
1280
1281 $newcategory = create_course_category($newcategory);
1282 // Populate special fields.
1283 fix_course_sortorder();
1284
1285 $createdcategories[] = array('id' => $newcategory->id, 'name' => $newcategory->name);
2f951d86
FS
1286 }
1287
3ec163dd
EL
1288 $transaction->allow_commit();
1289
1290 return $createdcategories;
2f951d86
FS
1291 }
1292
1293 /**
1294 * Returns description of method parameters
3ec163dd 1295 *
2f951d86
FS
1296 * @return external_function_parameters
1297 * @since Moodle 2.3
1298 */
3ec163dd
EL
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 );
2f951d86 1308 }
f2229c68
FS
1309
1310 /**
1311 * Returns description of method parameters
3ec163dd 1312 *
f2229c68
FS
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),
93ce0e82 1327 'descriptionformat' => new external_format_value('description', VALUE_DEFAULT),
f2229c68
FS
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 }
1336
1337 /**
1338 * Update categories
3ec163dd 1339 *
f2229c68
FS
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");
1347
1348 // Validate parameters.
1349 $params = self::validate_parameters(self::update_categories_parameters(), array('categories' => $categories));
1350
1351 $transaction = $DB->start_delegated_transaction();
1352
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 }
1357
1358 $categorycontext = context_coursecat::instance($cat['id']);
1359 self::validate_context($categorycontext);
1360 require_capability('moodle/category:manage', $categorycontext);
1361
1362 if (!empty($cat['name'])) {
d7238d08 1363 if (textlib::strlen($cat['name'])>255) {
f2229c68
FS
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'];
93ce0e82 1376 $category->descriptionformat = external_validate_format($cat['descriptionformat']);
f2229c68
FS
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'];
fe77a34e
JM
1392 // Get updated path by move_category().
1393 $category->path = $DB->get_field('course_categories', 'path',
1394 array('id' => $category->id));
f2229c68
FS
1395 }
1396 $DB->update_record('course_categories', $category);
1397 }
1398
1399 $transaction->allow_commit();
1400 }
1401
1402 /**
1403 * Returns description of method result value
3ec163dd 1404 *
f2229c68 1405 * @return external_description
3ec163dd 1406 * @since Moodle 2.3
f2229c68
FS
1407 */
1408 public static function update_categories_returns() {
1409 return null;
1410 }
3ec163dd
EL
1411
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 }
1435
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");
1446
1447 // Validate parameters.
1448 $params = self::validate_parameters(self::delete_categories_parameters(), array('categories' => $categories));
1449
f823158b
EL
1450 $transaction = $DB->start_delegated_transaction();
1451
3ec163dd
EL
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));
1460
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 }
1476
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 }
1481
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 }
1488
f823158b 1489 $transaction->allow_commit();
3ec163dd
EL
1490 }
1491
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 }
1501
5d1017e1
JM
1502}
1503
1504/**
4615817d
JM
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
5d1017e1
JM
1514 */
1515class moodle_course_external extends external_api {
1516
1517 /**
1518 * Returns description of method parameters
4615817d 1519 *
5d1017e1 1520 * @return external_function_parameters
4615817d
JM
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()
5d1017e1
JM
1525 */
1526 public static function get_courses_parameters() {
1527 return core_course_external::get_courses_parameters();
1528 }
1529
1530 /**
1531 * Get courses
4615817d 1532 *
5d1017e1 1533 * @param array $options
5d1017e1 1534 * @return array
4615817d
JM
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()
5d1017e1
JM
1539 */
1540 public static function get_courses($options) {
1541 return core_course_external::get_courses($options);
1542 }
1543
1544 /**
1545 * Returns description of method result value
4615817d 1546 *
5d1017e1 1547 * @return external_description
4615817d
JM
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()
5d1017e1
JM
1552 */
1553 public static function get_courses_returns() {
1554 return core_course_external::get_courses_returns();
1555 }
1556
1557 /**
1558 * Returns description of method parameters
4615817d 1559 *
5d1017e1 1560 * @return external_function_parameters
4615817d
JM
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()
5d1017e1
JM
1565 */
1566 public static function create_courses_parameters() {
1567 return core_course_external::create_courses_parameters();
1568 }
1569
1570 /**
1571 * Create courses
4615817d 1572 *
5d1017e1
JM
1573 * @param array $courses
1574 * @return array courses (id and shortname only)
4615817d
JM
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()
5d1017e1
JM
1579 */
1580 public static function create_courses($courses) {
1581 return core_course_external::create_courses($courses);
1582 }
1583
1584 /**
1585 * Returns description of method result value
4615817d 1586 *
5d1017e1 1587 * @return external_description
4615817d
JM
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()
5d1017e1
JM
1592 */
1593 public static function create_courses_returns() {
1594 return core_course_external::create_courses_returns();
1595 }
1596
ec0d6ea2 1597}