MDL-67264 core_course: Activity chooser new feature
[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
2c1d19fd
RW
29use core_course\external\course_summary_exporter;
30
8e7d3fe4 31require_once("$CFG->libdir/externallib.php");
98a52c80 32require_once("lib.php");
8e7d3fe4 33
5d1017e1 34/**
4615817d
JM
35 * Course external functions
36 *
37 * @package core_course
38 * @category external
39 * @copyright 2011 Jerome Mouneyrac
40 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
41 * @since Moodle 2.2
5d1017e1
JM
42 */
43class core_course_external extends external_api {
8e7d3fe4 44
ec0d6ea2
DC
45 /**
46 * Returns description of method parameters
4615817d 47 *
ec0d6ea2 48 * @return external_function_parameters
08b66e86 49 * @since Moodle 2.9 Options available
4615817d 50 * @since Moodle 2.2
ec0d6ea2
DC
51 */
52 public static function get_course_contents_parameters() {
53 return new external_function_parameters(
54 array('courseid' => new external_value(PARAM_INT, 'course id'),
55 'options' => new external_multiple_structure (
56 new external_single_structure(
08b66e86
JL
57 array(
58 'name' => new external_value(PARAM_ALPHANUM,
59 'The expected keys (value format) are:
60 excludemodules (bool) Do not return modules, return only the sections structure
61 excludecontents (bool) Do not return module contents (i.e: files inside a resource)
10b88bf2
JL
62 includestealthmodules (bool) Return stealth modules for students in a special
63 section (with id -1)
08b66e86
JL
64 sectionid (int) Return only this section
65 sectionnumber (int) Return only this section with number (order)
66 cmid (int) Return only this module information (among the whole sections structure)
67 modname (string) Return only modules with this name "label, forum, etc..."
68 modid (int) Return only the module with this id (to be used with modname'),
69 'value' => new external_value(PARAM_RAW, 'the value of the option,
70 this param is personaly validated in the external function.')
ec0d6ea2 71 )
08b66e86 72 ), 'Options, used since Moodle 2.9', VALUE_DEFAULT, array())
ec0d6ea2
DC
73 )
74 );
75 }
76
77 /**
78 * Get course contents
4615817d
JM
79 *
80 * @param int $courseid course id
08b66e86 81 * @param array $options Options for filtering the results, used since Moodle 2.9
ec0d6ea2 82 * @return array
08b66e86 83 * @since Moodle 2.9 Options available
4615817d 84 * @since Moodle 2.2
ec0d6ea2 85 */
3297d575 86 public static function get_course_contents($courseid, $options = array()) {
ec0d6ea2
DC
87 global $CFG, $DB;
88 require_once($CFG->dirroot . "/course/lib.php");
1de51367 89 require_once($CFG->libdir . '/completionlib.php');
ec0d6ea2
DC
90
91 //validate parameter
92 $params = self::validate_parameters(self::get_course_contents_parameters(),
93 array('courseid' => $courseid, 'options' => $options));
94
08b66e86
JL
95 $filters = array();
96 if (!empty($params['options'])) {
97
98 foreach ($params['options'] as $option) {
99 $name = trim($option['name']);
100 // Avoid duplicated options.
101 if (!isset($filters[$name])) {
102 switch ($name) {
103 case 'excludemodules':
104 case 'excludecontents':
10b88bf2 105 case 'includestealthmodules':
08b66e86
JL
106 $value = clean_param($option['value'], PARAM_BOOL);
107 $filters[$name] = $value;
108 break;
109 case 'sectionid':
110 case 'sectionnumber':
111 case 'cmid':
112 case 'modid':
113 $value = clean_param($option['value'], PARAM_INT);
114 if (is_numeric($value)) {
115 $filters[$name] = $value;
116 } else {
117 throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
118 }
119 break;
120 case 'modname':
121 $value = clean_param($option['value'], PARAM_PLUGIN);
122 if ($value) {
123 $filters[$name] = $value;
124 } else {
125 throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
126 }
127 break;
128 default:
129 throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
130 }
131 }
132 }
133 }
134
ec0d6ea2
DC
135 //retrieve the course
136 $course = $DB->get_record('course', array('id' => $params['courseid']), '*', MUST_EXIST);
137
12da294e
JL
138 if ($course->id != SITEID) {
139 // Check course format exist.
140 if (!file_exists($CFG->dirroot . '/course/format/' . $course->format . '/lib.php')) {
141 throw new moodle_exception('cannotgetcoursecontents', 'webservice', '', null,
142 get_string('courseformatnotfound', 'error', $course->format));
143 } else {
144 require_once($CFG->dirroot . '/course/format/' . $course->format . '/lib.php');
145 }
ec0d6ea2
DC
146 }
147
148 // now security checks
1f364c87 149 $context = context_course::instance($course->id, IGNORE_MISSING);
ec0d6ea2
DC
150 try {
151 self::validate_context($context);
152 } catch (Exception $e) {
153 $exceptionparam = new stdClass();
154 $exceptionparam->message = $e->getMessage();
155 $exceptionparam->courseid = $course->id;
156 throw new moodle_exception('errorcoursecontextnotvalid', 'webservice', '', $exceptionparam);
157 }
158
159 $canupdatecourse = has_capability('moodle/course:update', $context);
160
161 //create return value
162 $coursecontents = array();
163
164 if ($canupdatecourse or $course->visible
165 or has_capability('moodle/course:viewhiddencourses', $context)) {
166
167 //retrieve sections
168 $modinfo = get_fast_modinfo($course);
71a56e08 169 $sections = $modinfo->get_section_info_all();
89b909f6 170 $coursenumsections = course_get_format($course)->get_last_section_number();
10b88bf2 171 $stealthmodules = array(); // Array to keep all the modules available but not visible in a course section/topic.
ec0d6ea2 172
1de51367
JL
173 $completioninfo = new completion_info($course);
174
ec0d6ea2 175 //for each sections (first displayed to last displayed)
9f3cc17d 176 $modinfosections = $modinfo->get_sections();
ec0d6ea2
DC
177 foreach ($sections as $key => $section) {
178
08b66e86
JL
179 // This becomes true when we are filtering and we found the value to filter with.
180 $sectionfound = false;
181
182 // Filter by section id.
183 if (!empty($filters['sectionid'])) {
184 if ($section->id != $filters['sectionid']) {
185 continue;
186 } else {
187 $sectionfound = true;
188 }
189 }
190
191 // Filter by section number. Note that 0 is a valid section number.
192 if (isset($filters['sectionnumber'])) {
193 if ($key != $filters['sectionnumber']) {
194 continue;
195 } else {
196 $sectionfound = true;
197 }
198 }
199
ec0d6ea2
DC
200 // reset $sectioncontents
201 $sectionvalues = array();
202 $sectionvalues['id'] = $section->id;
203 $sectionvalues['name'] = get_section_name($course, $section);
ec0d6ea2 204 $sectionvalues['visible'] = $section->visible;
6a1131e2
JL
205
206 $options = (object) array('noclean' => true);
93ce0e82
JM
207 list($sectionvalues['summary'], $sectionvalues['summaryformat']) =
208 external_format_text($section->summary, $section->summaryformat,
6a1131e2 209 $context->id, 'course', 'section', $section->id, $options);
9df9f1f0 210 $sectionvalues['section'] = $section->section;
82f5802a 211 $sectionvalues['hiddenbynumsections'] = $section->section > $coursenumsections ? 1 : 0;
935429e2
JL
212 $sectionvalues['uservisible'] = $section->uservisible;
213 if (!empty($section->availableinfo)) {
214 $sectionvalues['availabilityinfo'] = \core_availability\info::format_info($section->availableinfo, $course);
215 }
216
ec0d6ea2
DC
217 $sectioncontents = array();
218
10b88bf2
JL
219 // For each module of the section.
220 if (empty($filters['excludemodules']) and !empty($modinfosections[$section->section])) {
9f3cc17d
JM
221 foreach ($modinfosections[$section->section] as $cmid) {
222 $cm = $modinfo->cms[$cmid];
ec0d6ea2 223
935429e2
JL
224 // Stop here if the module is not visible to the user on the course main page:
225 // The user can't access the module and the user can't view the module on the course page.
226 if (!$cm->uservisible && !$cm->is_visible_on_course_page()) {
9f3cc17d
JM
227 continue;
228 }
ec0d6ea2 229
08b66e86
JL
230 // This becomes true when we are filtering and we found the value to filter with.
231 $modfound = false;
232
233 // Filter by cmid.
234 if (!empty($filters['cmid'])) {
235 if ($cmid != $filters['cmid']) {
236 continue;
237 } else {
238 $modfound = true;
239 }
240 }
241
242 // Filter by module name and id.
243 if (!empty($filters['modname'])) {
244 if ($cm->modname != $filters['modname']) {
245 continue;
246 } else if (!empty($filters['modid'])) {
247 if ($cm->instance != $filters['modid']) {
248 continue;
249 } else {
250 // Note that if we are only filtering by modname we don't break the loop.
251 $modfound = true;
252 }
253 }
254 }
255
9f3cc17d 256 $module = array();
ec0d6ea2 257
9748791b
JL
258 $modcontext = context_module::instance($cm->id);
259
9f3cc17d
JM
260 //common info (for people being able to see the module or availability dates)
261 $module['id'] = $cm->id;
9748791b 262 $module['name'] = external_format_string($cm->name, $modcontext->id);
d3549931 263 $module['instance'] = $cm->instance;
d91aa1e7
MJ
264 $module['modname'] = (string) $cm->modname;
265 $module['modplural'] = (string) $cm->modplural;
9f3cc17d
JM
266 $module['modicon'] = $cm->get_icon_url()->out(false);
267 $module['indent'] = $cm->indent;
1206a487
JL
268 $module['onclick'] = $cm->onclick;
269 $module['afterlink'] = $cm->afterlink;
270 $module['customdata'] = json_encode($cm->customdata);
1de51367 271 $module['completion'] = $cm->completion;
62b40d27 272 $module['noviewlink'] = plugin_supports('mod', $cm->modname, FEATURE_NO_VIEW_LINK, false);
1de51367
JL
273
274 // Check module completion.
275 $completion = $completioninfo->is_enabled($cm);
276 if ($completion != COMPLETION_DISABLED) {
277 $completiondata = $completioninfo->get_data($cm, true);
278 $module['completiondata'] = array(
279 'state' => $completiondata->completionstate,
280 'timecompleted' => $completiondata->timemodified,
76724712
MJ
281 'overrideby' => $completiondata->overrideby,
282 'valueused' => core_availability\info::completion_value_used($course, $cm->id)
1de51367
JL
283 );
284 }
ec0d6ea2 285
62b40d27 286 if (!empty($cm->showdescription) or $module['noviewlink']) {
73ee2fda 287 // We want to use the external format. However from reading get_formatted_content(), $cm->content format is always FORMAT_HTML.
84346754 288 $options = array('noclean' => true);
73ee2fda 289 list($module['description'], $descriptionformat) = external_format_text($cm->content,
84346754 290 FORMAT_HTML, $modcontext->id, $cm->modname, 'intro', $cm->id, $options);
9f3cc17d 291 }
ec0d6ea2 292
9f3cc17d 293 //url of the module
73ee2fda 294 $url = $cm->url;
9f3cc17d 295 if ($url) { //labels don't have url
73ee2fda 296 $module['url'] = $url->out(false);
9f3cc17d 297 }
ec0d6ea2 298
9f3cc17d
JM
299 $canviewhidden = has_capability('moodle/course:viewhiddenactivities',
300 context_module::instance($cm->id));
301 //user that can view hidden module should know about the visibility
302 $module['visible'] = $cm->visible;
4529327a 303 $module['visibleoncoursepage'] = $cm->visibleoncoursepage;
935429e2
JL
304 $module['uservisible'] = $cm->uservisible;
305 if (!empty($cm->availableinfo)) {
306 $module['availabilityinfo'] = \core_availability\info::format_info($cm->availableinfo, $course);
307 }
ec0d6ea2 308
8d1f33e1 309 // Availability date (also send to user who can see hidden module).
310 if ($CFG->enableavailability && ($canviewhidden || $canupdatecourse)) {
311 $module['availability'] = $cm->availability;
9f3cc17d 312 }
ec0d6ea2 313
935429e2
JL
314 // Return contents only if the user can access to the module.
315 if ($cm->uservisible) {
316 $baseurl = 'webservice/pluginfile.php';
ec0d6ea2 317
935429e2
JL
318 // Call $modulename_export_contents (each module callback take care about checking the capabilities).
319 require_once($CFG->dirroot . '/mod/' . $cm->modname . '/lib.php');
320 $getcontentfunction = $cm->modname.'_export_contents';
321 if (function_exists($getcontentfunction)) {
9b8aed89
JL
322 $contents = $getcontentfunction($cm, $baseurl);
323 $module['contentsinfo'] = array(
324 'filescount' => count($contents),
325 'filessize' => 0,
326 'lastmodified' => 0,
327 'mimetypes' => array(),
328 );
329 foreach ($contents as $content) {
acfd5e83
JL
330 // Check repository file (only main file).
331 if (!isset($module['contentsinfo']['repositorytype'])) {
02b342bb
AG
332 $module['contentsinfo']['repositorytype'] =
333 isset($content['repositorytype']) ? $content['repositorytype'] : '';
acfd5e83 334 }
9b8aed89
JL
335 if (isset($content['filesize'])) {
336 $module['contentsinfo']['filessize'] += $content['filesize'];
337 }
338 if (isset($content['timemodified']) &&
339 ($content['timemodified'] > $module['contentsinfo']['lastmodified'])) {
340
341 $module['contentsinfo']['lastmodified'] = $content['timemodified'];
342 }
343 if (isset($content['mimetype'])) {
344 $module['contentsinfo']['mimetypes'][$content['mimetype']] = $content['mimetype'];
345 }
346 }
347
348 if (empty($filters['excludecontents']) and !empty($contents)) {
935429e2
JL
349 $module['contents'] = $contents;
350 } else {
351 $module['contents'] = array();
352 }
9f3cc17d 353 }
ec0d6ea2 354 }
ec0d6ea2 355
10b88bf2
JL
356 // Assign result to $sectioncontents, there is an exception,
357 // stealth activities in non-visible sections for students go to a special section.
358 if (!empty($filters['includestealthmodules']) && !$section->uservisible && $cm->is_stealth()) {
359 $stealthmodules[] = $module;
360 } else {
361 $sectioncontents[] = $module;
362 }
ec0d6ea2 363
08b66e86
JL
364 // If we just did a filtering, break the loop.
365 if ($modfound) {
366 break;
367 }
368
9f3cc17d 369 }
ec0d6ea2
DC
370 }
371 $sectionvalues['modules'] = $sectioncontents;
372
373 // assign result to $coursecontents
10b88bf2 374 $coursecontents[$key] = $sectionvalues;
08b66e86
JL
375
376 // Break the loop if we are filtering.
377 if ($sectionfound) {
378 break;
379 }
ec0d6ea2 380 }
10b88bf2
JL
381
382 // Now that we have iterated over all the sections and activities, check the visibility.
383 // We didn't this before to be able to retrieve stealth activities.
384 foreach ($coursecontents as $sectionnumber => $sectioncontents) {
385 $section = $sections[$sectionnumber];
386 // Show the section if the user is permitted to access it, OR if it's not available
387 // but there is some available info text which explains the reason & should display.
388 $showsection = $section->uservisible ||
389 ($section->visible && !$section->available &&
390 !empty($section->availableinfo));
391
392 if (!$showsection) {
393 unset($coursecontents[$sectionnumber]);
394 continue;
395 }
396
397 // Remove modules information if the section is not visible for the user.
398 if (!$section->uservisible) {
399 $coursecontents[$sectionnumber]['modules'] = array();
400 }
401 }
402
403 // Include stealth modules in special section (without any info).
404 if (!empty($stealthmodules)) {
405 $coursecontents[] = array(
406 'id' => -1,
407 'name' => '',
408 'summary' => '',
409 'summaryformat' => FORMAT_MOODLE,
410 'modules' => $stealthmodules
411 );
412 }
413
ec0d6ea2
DC
414 }
415 return $coursecontents;
416 }
417
418 /**
419 * Returns description of method result value
4615817d 420 *
ec0d6ea2 421 * @return external_description
4615817d 422 * @since Moodle 2.2
ec0d6ea2
DC
423 */
424 public static function get_course_contents_returns() {
425 return new external_multiple_structure(
426 new external_single_structure(
427 array(
428 'id' => new external_value(PARAM_INT, 'Section ID'),
429 'name' => new external_value(PARAM_TEXT, 'Section name'),
430 'visible' => new external_value(PARAM_INT, 'is the section visible', VALUE_OPTIONAL),
431 'summary' => new external_value(PARAM_RAW, 'Section description'),
93ce0e82 432 'summaryformat' => new external_format_value('summary'),
9df9f1f0 433 'section' => new external_value(PARAM_INT, 'Section number inside the course', VALUE_OPTIONAL),
82f5802a
JL
434 'hiddenbynumsections' => new external_value(PARAM_INT, 'Whether is a section hidden in the course format',
435 VALUE_OPTIONAL),
935429e2
JL
436 'uservisible' => new external_value(PARAM_BOOL, 'Is the section visible for the user?', VALUE_OPTIONAL),
437 'availabilityinfo' => new external_value(PARAM_RAW, 'Availability information.', VALUE_OPTIONAL),
ec0d6ea2
DC
438 'modules' => new external_multiple_structure(
439 new external_single_structure(
440 array(
441 'id' => new external_value(PARAM_INT, 'activity id'),
442 'url' => new external_value(PARAM_URL, 'activity url', VALUE_OPTIONAL),
11d81936 443 'name' => new external_value(PARAM_RAW, 'activity module name'),
ca4154ce 444 'instance' => new external_value(PARAM_INT, 'instance id', VALUE_OPTIONAL),
ec0d6ea2
DC
445 'description' => new external_value(PARAM_RAW, 'activity description', VALUE_OPTIONAL),
446 'visible' => new external_value(PARAM_INT, 'is the module visible', VALUE_OPTIONAL),
935429e2
JL
447 'uservisible' => new external_value(PARAM_BOOL, 'Is the module visible for the user?',
448 VALUE_OPTIONAL),
449 'availabilityinfo' => new external_value(PARAM_RAW, 'Availability information.',
450 VALUE_OPTIONAL),
4529327a
MG
451 'visibleoncoursepage' => new external_value(PARAM_INT, 'is the module visible on course page',
452 VALUE_OPTIONAL),
ec0d6ea2
DC
453 'modicon' => new external_value(PARAM_URL, 'activity icon url'),
454 'modname' => new external_value(PARAM_PLUGIN, 'activity module type'),
455 'modplural' => new external_value(PARAM_TEXT, 'activity module plural name'),
8d1f33e1 456 'availability' => new external_value(PARAM_RAW, 'module availability settings', VALUE_OPTIONAL),
ec0d6ea2 457 'indent' => new external_value(PARAM_INT, 'number of identation in the site'),
1206a487
JL
458 'onclick' => new external_value(PARAM_RAW, 'Onclick action.', VALUE_OPTIONAL),
459 'afterlink' => new external_value(PARAM_RAW, 'After link info to be displayed.',
460 VALUE_OPTIONAL),
461 'customdata' => new external_value(PARAM_RAW, 'Custom data (JSON encoded).', VALUE_OPTIONAL),
5872c4d4
AG
462 'noviewlink' => new external_value(PARAM_BOOL, 'Whether the module has no view page',
463 VALUE_OPTIONAL),
1de51367
JL
464 'completion' => new external_value(PARAM_INT, 'Type of completion tracking:
465 0 means none, 1 manual, 2 automatic.', VALUE_OPTIONAL),
466 'completiondata' => new external_single_structure(
467 array(
468 'state' => new external_value(PARAM_INT, 'Completion state value:
469 0 means incomplete, 1 complete, 2 complete pass, 3 complete fail'),
470 'timecompleted' => new external_value(PARAM_INT, 'Timestamp for completion status.'),
471 'overrideby' => new external_value(PARAM_INT, 'The user id who has overriden the
472 status.'),
76724712
MJ
473 'valueused' => new external_value(PARAM_BOOL, 'Whether the completion status affects
474 the availability of another activity.', VALUE_OPTIONAL),
1de51367
JL
475 ), 'Module completion data.', VALUE_OPTIONAL
476 ),
ec0d6ea2
DC
477 'contents' => new external_multiple_structure(
478 new external_single_structure(
479 array(
480 // content info
481 'type'=> new external_value(PARAM_TEXT, 'a file or a folder or external link'),
482 'filename'=> new external_value(PARAM_FILE, 'filename'),
483 'filepath'=> new external_value(PARAM_PATH, 'filepath'),
484 'filesize'=> new external_value(PARAM_INT, 'filesize'),
485 'fileurl' => new external_value(PARAM_URL, 'downloadable file url', VALUE_OPTIONAL),
486 'content' => new external_value(PARAM_RAW, 'Raw content, will be used when type is content', VALUE_OPTIONAL),
487 'timecreated' => new external_value(PARAM_INT, 'Time created'),
488 'timemodified' => new external_value(PARAM_INT, 'Time modified'),
489 'sortorder' => new external_value(PARAM_INT, 'Content sort order'),
1104a9fa
JL
490 'mimetype' => new external_value(PARAM_RAW, 'File mime type.', VALUE_OPTIONAL),
491 'isexternalfile' => new external_value(PARAM_BOOL, 'Whether is an external file.',
492 VALUE_OPTIONAL),
493 'repositorytype' => new external_value(PARAM_PLUGIN, 'The repository type for external files.',
494 VALUE_OPTIONAL),
ec0d6ea2
DC
495
496 // copyright related info
497 'userid' => new external_value(PARAM_INT, 'User who added this content to moodle'),
498 'author' => new external_value(PARAM_TEXT, 'Content owner'),
499 'license' => new external_value(PARAM_TEXT, 'Content license'),
6c344ff2
JL
500 'tags' => new external_multiple_structure(
501 \core_tag\external\tag_item_exporter::get_read_structure(), 'Tags',
502 VALUE_OPTIONAL
503 ),
ec0d6ea2
DC
504 )
505 ), VALUE_DEFAULT, array()
9b8aed89
JL
506 ),
507 'contentsinfo' => new external_single_structure(
508 array(
509 'filescount' => new external_value(PARAM_INT, 'Total number of files.'),
510 'filessize' => new external_value(PARAM_INT, 'Total files size.'),
511 'lastmodified' => new external_value(PARAM_INT, 'Last time files were modified.'),
512 'mimetypes' => new external_multiple_structure(
513 new external_value(PARAM_RAW, 'File mime type.'),
514 'Files mime types.'
515 ),
acfd5e83
JL
516 'repositorytype' => new external_value(PARAM_PLUGIN, 'The repository type for
517 the main file.', VALUE_OPTIONAL),
9b8aed89
JL
518 ), 'Contents summary information.', VALUE_OPTIONAL
519 ),
ec0d6ea2
DC
520 )
521 ), 'list of module'
522 )
523 )
524 )
525 );
526 }
527
6bb31e40 528 /**
529 * Returns description of method parameters
4615817d 530 *
6bb31e40 531 * @return external_function_parameters
754c2dea 532 * @since Moodle 2.3
6bb31e40 533 */
534 public static function get_courses_parameters() {
535 return new external_function_parameters(
536 array('options' => new external_single_structure(
537 array('ids' => new external_multiple_structure(
538 new external_value(PARAM_INT, 'Course id')
539 , 'List of course id. If empty return all courses
540 except front page course.',
541 VALUE_OPTIONAL)
542 ), 'options - operator OR is used', VALUE_DEFAULT, array())
543 )
544 );
545 }
546
547 /**
548 * Get courses
4615817d
JM
549 *
550 * @param array $options It contains an array (list of ids)
6bb31e40 551 * @return array
4615817d 552 * @since Moodle 2.2
6bb31e40 553 */
3297d575 554 public static function get_courses($options = array()) {
6bb31e40 555 global $CFG, $DB;
556 require_once($CFG->dirroot . "/course/lib.php");
557
558 //validate parameter
559 $params = self::validate_parameters(self::get_courses_parameters(),
560 array('options' => $options));
561
562 //retrieve courses
12fc8acf 563 if (!array_key_exists('ids', $params['options'])
6bb31e40 564 or empty($params['options']['ids'])) {
565 $courses = $DB->get_records('course');
566 } else {
567 $courses = $DB->get_records_list('course', 'id', $params['options']['ids']);
568 }
569
570 //create return value
571 $coursesinfo = array();
572 foreach ($courses as $course) {
573
574 // now security checks
1f364c87 575 $context = context_course::instance($course->id, IGNORE_MISSING);
0e984d98 576 $courseformatoptions = course_get_format($course)->get_format_options();
6bb31e40 577 try {
578 self::validate_context($context);
579 } catch (Exception $e) {
580 $exceptionparam = new stdClass();
581 $exceptionparam->message = $e->getMessage();
582 $exceptionparam->courseid = $course->id;
96d3b93b 583 throw new moodle_exception('errorcoursecontextnotvalid', 'webservice', '', $exceptionparam);
6bb31e40 584 }
a0cf7ee8
MG
585 if ($course->id != SITEID) {
586 require_capability('moodle/course:view', $context);
587 }
6bb31e40 588
589 $courseinfo = array();
590 $courseinfo['id'] = $course->id;
d889b587
JL
591 $courseinfo['fullname'] = external_format_string($course->fullname, $context->id);
592 $courseinfo['shortname'] = external_format_string($course->shortname, $context->id);
440b4c54 593 $courseinfo['displayname'] = external_format_string(get_course_display_name_for_list($course), $context->id);
6bb31e40 594 $courseinfo['categoryid'] = $course->category;
93ce0e82
JM
595 list($courseinfo['summary'], $courseinfo['summaryformat']) =
596 external_format_text($course->summary, $course->summaryformat, $context->id, 'course', 'summary', 0);
6bb31e40 597 $courseinfo['format'] = $course->format;
598 $courseinfo['startdate'] = $course->startdate;
fbcdb0d7 599 $courseinfo['enddate'] = $course->enddate;
0e984d98
MG
600 if (array_key_exists('numsections', $courseformatoptions)) {
601 // For backward-compartibility
602 $courseinfo['numsections'] = $courseformatoptions['numsections'];
603 }
6bb31e40 604
7a0162f1
DM
605 $handler = core_course\customfield\course_handler::create();
606 if ($customfields = $handler->export_instance_data($course->id)) {
607 $courseinfo['customfields'] = [];
608 foreach ($customfields as $data) {
609 $courseinfo['customfields'][] = [
610 'type' => $data->get_type(),
611 'value' => $data->get_value(),
612 'name' => $data->get_name(),
613 'shortname' => $data->get_shortname()
614 ];
615 }
616 }
617
6bb31e40 618 //some field should be returned only if the user has update permission
619 $courseadmin = has_capability('moodle/course:update', $context);
620 if ($courseadmin) {
621 $courseinfo['categorysortorder'] = $course->sortorder;
622 $courseinfo['idnumber'] = $course->idnumber;
623 $courseinfo['showgrades'] = $course->showgrades;
624 $courseinfo['showreports'] = $course->showreports;
625 $courseinfo['newsitems'] = $course->newsitems;
626 $courseinfo['visible'] = $course->visible;
627 $courseinfo['maxbytes'] = $course->maxbytes;
0e984d98
MG
628 if (array_key_exists('hiddensections', $courseformatoptions)) {
629 // For backward-compartibility
630 $courseinfo['hiddensections'] = $courseformatoptions['hiddensections'];
631 }
89b909f6
MG
632 // Return numsections for backward-compatibility with clients who expect it.
633 $courseinfo['numsections'] = course_get_format($course)->get_last_section_number();
6bb31e40 634 $courseinfo['groupmode'] = $course->groupmode;
635 $courseinfo['groupmodeforce'] = $course->groupmodeforce;
636 $courseinfo['defaultgroupingid'] = $course->defaultgroupingid;
6db24235 637 $courseinfo['lang'] = clean_param($course->lang, PARAM_LANG);
6bb31e40 638 $courseinfo['timecreated'] = $course->timecreated;
639 $courseinfo['timemodified'] = $course->timemodified;
6db24235 640 $courseinfo['forcetheme'] = clean_param($course->theme, PARAM_THEME);
6bb31e40 641 $courseinfo['enablecompletion'] = $course->enablecompletion;
6bb31e40 642 $courseinfo['completionnotify'] = $course->completionnotify;
8d8d4da4 643 $courseinfo['courseformatoptions'] = array();
0e984d98 644 foreach ($courseformatoptions as $key => $value) {
8d8d4da4
MG
645 $courseinfo['courseformatoptions'][] = array(
646 'name' => $key,
647 'value' => $value
0e984d98
MG
648 );
649 }
6bb31e40 650 }
651
652 if ($courseadmin or $course->visible
653 or has_capability('moodle/course:viewhiddencourses', $context)) {
654 $coursesinfo[] = $courseinfo;
655 }
656 }
657
658 return $coursesinfo;
659 }
660
661 /**
662 * Returns description of method result value
4615817d 663 *
6bb31e40 664 * @return external_description
4615817d 665 * @since Moodle 2.2
6bb31e40 666 */
667 public static function get_courses_returns() {
668 return new external_multiple_structure(
669 new external_single_structure(
670 array(
671 'id' => new external_value(PARAM_INT, 'course id'),
672 'shortname' => new external_value(PARAM_TEXT, 'course short name'),
673 'categoryid' => new external_value(PARAM_INT, 'category id'),
674 'categorysortorder' => new external_value(PARAM_INT,
675 'sort order into the category', VALUE_OPTIONAL),
676 'fullname' => new external_value(PARAM_TEXT, 'full name'),
440b4c54 677 'displayname' => new external_value(PARAM_TEXT, 'course display name'),
6bb31e40 678 'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
679 'summary' => new external_value(PARAM_RAW, 'summary'),
93ce0e82 680 'summaryformat' => new external_format_value('summary'),
aff24313 681 'format' => new external_value(PARAM_PLUGIN,
6bb31e40 682 'course format: weeks, topics, social, site,..'),
683 'showgrades' => new external_value(PARAM_INT,
684 '1 if grades are shown, otherwise 0', VALUE_OPTIONAL),
685 'newsitems' => new external_value(PARAM_INT,
686 'number of recent items appearing on the course page', VALUE_OPTIONAL),
687 'startdate' => new external_value(PARAM_INT,
688 'timestamp when the course start'),
fbcdb0d7
DNA
689 'enddate' => new external_value(PARAM_INT,
690 'timestamp when the course end'),
0e984d98 691 'numsections' => new external_value(PARAM_INT,
8d8d4da4 692 '(deprecated, use courseformatoptions) number of weeks/topics',
0e984d98 693 VALUE_OPTIONAL),
6bb31e40 694 'maxbytes' => new external_value(PARAM_INT,
695 'largest size of file that can be uploaded into the course',
696 VALUE_OPTIONAL),
697 'showreports' => new external_value(PARAM_INT,
698 'are activity report shown (yes = 1, no =0)', VALUE_OPTIONAL),
699 'visible' => new external_value(PARAM_INT,
700 '1: available to student, 0:not available', VALUE_OPTIONAL),
701 'hiddensections' => new external_value(PARAM_INT,
8d8d4da4 702 '(deprecated, use courseformatoptions) How the hidden sections in the course are displayed to students',
3ec163dd
EL
703 VALUE_OPTIONAL),
704 'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible',
705 VALUE_OPTIONAL),
706 'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no',
707 VALUE_OPTIONAL),
708 'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id',
709 VALUE_OPTIONAL),
710 'timecreated' => new external_value(PARAM_INT,
711 'timestamp when the course have been created', VALUE_OPTIONAL),
712 'timemodified' => new external_value(PARAM_INT,
713 'timestamp when the course have been modified', VALUE_OPTIONAL),
714 'enablecompletion' => new external_value(PARAM_INT,
715 'Enabled, control via completion and activity settings. Disbaled,
716 not shown in activity settings.',
717 VALUE_OPTIONAL),
3ec163dd
EL
718 'completionnotify' => new external_value(PARAM_INT,
719 '1: yes 0: no', VALUE_OPTIONAL),
720 'lang' => new external_value(PARAM_SAFEDIR,
721 'forced course language', VALUE_OPTIONAL),
722 'forcetheme' => new external_value(PARAM_PLUGIN,
723 'name of the force theme', VALUE_OPTIONAL),
8d8d4da4 724 'courseformatoptions' => new external_multiple_structure(
0e984d98 725 new external_single_structure(
8d8d4da4
MG
726 array('name' => new external_value(PARAM_ALPHANUMEXT, 'course format option name'),
727 'value' => new external_value(PARAM_RAW, 'course format option value')
7a0162f1 728 )), 'additional options for particular course format', VALUE_OPTIONAL
0e984d98 729 ),
7a0162f1
DM
730 'customfields' => new external_multiple_structure(
731 new external_single_structure(
732 ['name' => new external_value(PARAM_TEXT, 'The name of the custom field'),
733 'shortname' => new external_value(PARAM_ALPHANUMEXT, 'The shortname of the custom field'),
bbf60b14
MG
734 'type' => new external_value(PARAM_COMPONENT,
735 'The type of the custom field - text, checkbox...'),
7a0162f1
DM
736 'value' => new external_value(PARAM_RAW, 'The value of the custom field')]
737 ), 'Custom fields and associated values', VALUE_OPTIONAL),
3ec163dd 738 ), 'course'
479a5db1 739 )
479a5db1
FS
740 );
741 }
742
6bb31e40 743 /**
744 * Returns description of method parameters
4615817d 745 *
6bb31e40 746 * @return external_function_parameters
4615817d 747 * @since Moodle 2.2
6bb31e40 748 */
749 public static function create_courses_parameters() {
750 $courseconfig = get_config('moodlecourse'); //needed for many default values
751 return new external_function_parameters(
752 array(
753 'courses' => new external_multiple_structure(
754 new external_single_structure(
755 array(
756 'fullname' => new external_value(PARAM_TEXT, 'full name'),
757 'shortname' => new external_value(PARAM_TEXT, 'course short name'),
758 'categoryid' => new external_value(PARAM_INT, 'category id'),
759 'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
760 'summary' => new external_value(PARAM_RAW, 'summary', VALUE_OPTIONAL),
93ce0e82 761 'summaryformat' => new external_format_value('summary', VALUE_DEFAULT),
aff24313 762 'format' => new external_value(PARAM_PLUGIN,
6bb31e40 763 'course format: weeks, topics, social, site,..',
764 VALUE_DEFAULT, $courseconfig->format),
765 'showgrades' => new external_value(PARAM_INT,
766 '1 if grades are shown, otherwise 0', VALUE_DEFAULT,
767 $courseconfig->showgrades),
768 'newsitems' => new external_value(PARAM_INT,
769 'number of recent items appearing on the course page',
770 VALUE_DEFAULT, $courseconfig->newsitems),
771 'startdate' => new external_value(PARAM_INT,
772 'timestamp when the course start', VALUE_OPTIONAL),
fbcdb0d7
DNA
773 'enddate' => new external_value(PARAM_INT,
774 'timestamp when the course end', VALUE_OPTIONAL),
0e984d98 775 'numsections' => new external_value(PARAM_INT,
8d8d4da4 776 '(deprecated, use courseformatoptions) number of weeks/topics',
0e984d98 777 VALUE_OPTIONAL),
6bb31e40 778 'maxbytes' => new external_value(PARAM_INT,
779 'largest size of file that can be uploaded into the course',
780 VALUE_DEFAULT, $courseconfig->maxbytes),
781 'showreports' => new external_value(PARAM_INT,
782 'are activity report shown (yes = 1, no =0)', VALUE_DEFAULT,
783 $courseconfig->showreports),
784 'visible' => new external_value(PARAM_INT,
785 '1: available to student, 0:not available', VALUE_OPTIONAL),
786 'hiddensections' => new external_value(PARAM_INT,
8d8d4da4 787 '(deprecated, use courseformatoptions) How the hidden sections in the course are displayed to students',
0e984d98 788 VALUE_OPTIONAL),
6bb31e40 789 'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible',
790 VALUE_DEFAULT, $courseconfig->groupmode),
791 'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no',
792 VALUE_DEFAULT, $courseconfig->groupmodeforce),
793 'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id',
794 VALUE_DEFAULT, 0),
795 'enablecompletion' => new external_value(PARAM_INT,
8a6b1193 796 'Enabled, control via completion and activity settings. Disabled,
6bb31e40 797 not shown in activity settings.',
798 VALUE_OPTIONAL),
6bb31e40 799 'completionnotify' => new external_value(PARAM_INT,
800 '1: yes 0: no', VALUE_OPTIONAL),
aff24313 801 'lang' => new external_value(PARAM_SAFEDIR,
6bb31e40 802 'forced course language', VALUE_OPTIONAL),
aff24313 803 'forcetheme' => new external_value(PARAM_PLUGIN,
6bb31e40 804 'name of the force theme', VALUE_OPTIONAL),
8d8d4da4 805 'courseformatoptions' => new external_multiple_structure(
0e984d98 806 new external_single_structure(
8d8d4da4
MG
807 array('name' => new external_value(PARAM_ALPHANUMEXT, 'course format option name'),
808 'value' => new external_value(PARAM_RAW, 'course format option value')
0e984d98
MG
809 )),
810 'additional options for particular course format', VALUE_OPTIONAL),
7a0162f1
DM
811 'customfields' => new external_multiple_structure(
812 new external_single_structure(
813 array(
814 'shortname' => new external_value(PARAM_ALPHANUMEXT, 'The shortname of the custom field'),
815 'value' => new external_value(PARAM_RAW, 'The value of the custom field'),
816 )), 'custom fields for the course', VALUE_OPTIONAL
817 )
818 )), 'courses to create'
6bb31e40 819 )
820 )
821 );
822 }
823
824 /**
825 * Create courses
4615817d 826 *
6bb31e40 827 * @param array $courses
828 * @return array courses (id and shortname only)
4615817d 829 * @since Moodle 2.2
6bb31e40 830 */
831 public static function create_courses($courses) {
832 global $CFG, $DB;
833 require_once($CFG->dirroot . "/course/lib.php");
834 require_once($CFG->libdir . '/completionlib.php');
835
6bb31e40 836 $params = self::validate_parameters(self::create_courses_parameters(),
837 array('courses' => $courses));
838
bd3b3bba 839 $availablethemes = core_component::get_plugin_list('theme');
6bb31e40 840 $availablelangs = get_string_manager()->get_list_of_translations();
841
842 $transaction = $DB->start_delegated_transaction();
843
844 foreach ($params['courses'] as $course) {
845
846 // Ensure the current user is allowed to run this function
1f364c87 847 $context = context_coursecat::instance($course['categoryid'], IGNORE_MISSING);
6bb31e40 848 try {
849 self::validate_context($context);
850 } catch (Exception $e) {
851 $exceptionparam = new stdClass();
852 $exceptionparam->message = $e->getMessage();
853 $exceptionparam->catid = $course['categoryid'];
96d3b93b 854 throw new moodle_exception('errorcatcontextnotvalid', 'webservice', '', $exceptionparam);
6bb31e40 855 }
856 require_capability('moodle/course:create', $context);
857
858 // Make sure lang is valid
1433a078
DS
859 if (array_key_exists('lang', $course)) {
860 if (empty($availablelangs[$course['lang']])) {
861 throw new moodle_exception('errorinvalidparam', 'webservice', '', 'lang');
862 }
863 if (!has_capability('moodle/course:setforcedlanguage', $context)) {
864 unset($course['lang']);
865 }
6bb31e40 866 }
867
868 // Make sure theme is valid
12fc8acf 869 if (array_key_exists('forcetheme', $course)) {
6bb31e40 870 if (!empty($CFG->allowcoursethemes)) {
871 if (empty($availablethemes[$course['forcetheme']])) {
96d3b93b 872 throw new moodle_exception('errorinvalidparam', 'webservice', '', 'forcetheme');
6bb31e40 873 } else {
874 $course['theme'] = $course['forcetheme'];
875 }
876 }
877 }
878
879 //force visibility if ws user doesn't have the permission to set it
880 $category = $DB->get_record('course_categories', array('id' => $course['categoryid']));
881 if (!has_capability('moodle/course:visibility', $context)) {
882 $course['visible'] = $category->visible;
883 }
884
885 //set default value for completion
8a6b1193 886 $courseconfig = get_config('moodlecourse');
6bb31e40 887 if (completion_info::is_enabled_for_site()) {
12fc8acf 888 if (!array_key_exists('enablecompletion', $course)) {
8a6b1193 889 $course['enablecompletion'] = $courseconfig->enablecompletion;
6bb31e40 890 }
6bb31e40 891 } else {
892 $course['enablecompletion'] = 0;
6bb31e40 893 }
894
895 $course['category'] = $course['categoryid'];
896
93ce0e82
JM
897 // Summary format.
898 $course['summaryformat'] = external_validate_format($course['summaryformat']);
899
8d8d4da4
MG
900 if (!empty($course['courseformatoptions'])) {
901 foreach ($course['courseformatoptions'] as $option) {
902 $course[$option['name']] = $option['value'];
0e984d98
MG
903 }
904 }
905
7a0162f1
DM
906 // Custom fields.
907 if (!empty($course['customfields'])) {
908 foreach ($course['customfields'] as $field) {
909 $course['customfield_'.$field['shortname']] = $field['value'];
910 }
911 }
912
6bb31e40 913 //Note: create_course() core function check shortname, idnumber, category
914 $course['id'] = create_course((object) $course)->id;
915
916 $resultcourses[] = array('id' => $course['id'], 'shortname' => $course['shortname']);
917 }
918
919 $transaction->allow_commit();
920
921 return $resultcourses;
922 }
923
924 /**
925 * Returns description of method result value
4615817d 926 *
6bb31e40 927 * @return external_description
4615817d 928 * @since Moodle 2.2
6bb31e40 929 */
930 public static function create_courses_returns() {
931 return new external_multiple_structure(
932 new external_single_structure(
933 array(
934 'id' => new external_value(PARAM_INT, 'course id'),
935 'shortname' => new external_value(PARAM_TEXT, 'short name'),
936 )
937 )
938 );
939 }
940
791723c3
RT
941 /**
942 * Update courses
943 *
944 * @return external_function_parameters
945 * @since Moodle 2.5
946 */
947 public static function update_courses_parameters() {
948 return new external_function_parameters(
949 array(
950 'courses' => new external_multiple_structure(
951 new external_single_structure(
952 array(
953 'id' => new external_value(PARAM_INT, 'ID of the course'),
954 'fullname' => new external_value(PARAM_TEXT, 'full name', VALUE_OPTIONAL),
955 'shortname' => new external_value(PARAM_TEXT, 'course short name', VALUE_OPTIONAL),
956 'categoryid' => new external_value(PARAM_INT, 'category id', VALUE_OPTIONAL),
957 'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
958 'summary' => new external_value(PARAM_RAW, 'summary', VALUE_OPTIONAL),
959 'summaryformat' => new external_format_value('summary', VALUE_OPTIONAL),
960 'format' => new external_value(PARAM_PLUGIN,
961 'course format: weeks, topics, social, site,..', VALUE_OPTIONAL),
962 'showgrades' => new external_value(PARAM_INT,
963 '1 if grades are shown, otherwise 0', VALUE_OPTIONAL),
964 'newsitems' => new external_value(PARAM_INT,
965 'number of recent items appearing on the course page', VALUE_OPTIONAL),
966 'startdate' => new external_value(PARAM_INT,
967 'timestamp when the course start', VALUE_OPTIONAL),
fbcdb0d7
DNA
968 'enddate' => new external_value(PARAM_INT,
969 'timestamp when the course end', VALUE_OPTIONAL),
791723c3
RT
970 'numsections' => new external_value(PARAM_INT,
971 '(deprecated, use courseformatoptions) number of weeks/topics', VALUE_OPTIONAL),
972 'maxbytes' => new external_value(PARAM_INT,
973 'largest size of file that can be uploaded into the course', VALUE_OPTIONAL),
974 'showreports' => new external_value(PARAM_INT,
975 'are activity report shown (yes = 1, no =0)', VALUE_OPTIONAL),
976 'visible' => new external_value(PARAM_INT,
977 '1: available to student, 0:not available', VALUE_OPTIONAL),
978 'hiddensections' => new external_value(PARAM_INT,
e2adaaf7 979 '(deprecated, use courseformatoptions) How the hidden sections in the course are
791723c3
RT
980 displayed to students', VALUE_OPTIONAL),
981 'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible', VALUE_OPTIONAL),
982 'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no', VALUE_OPTIONAL),
983 'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id', VALUE_OPTIONAL),
984 'enablecompletion' => new external_value(PARAM_INT,
985 'Enabled, control via completion and activity settings. Disabled,
986 not shown in activity settings.', VALUE_OPTIONAL),
791723c3
RT
987 'completionnotify' => new external_value(PARAM_INT, '1: yes 0: no', VALUE_OPTIONAL),
988 'lang' => new external_value(PARAM_SAFEDIR, 'forced course language', VALUE_OPTIONAL),
989 'forcetheme' => new external_value(PARAM_PLUGIN, 'name of the force theme', VALUE_OPTIONAL),
990 'courseformatoptions' => new external_multiple_structure(
991 new external_single_structure(
992 array('name' => new external_value(PARAM_ALPHANUMEXT, 'course format option name'),
993 'value' => new external_value(PARAM_RAW, 'course format option value')
7a0162f1
DM
994 )), 'additional options for particular course format', VALUE_OPTIONAL),
995 'customfields' => new external_multiple_structure(
996 new external_single_structure(
997 [
998 'shortname' => new external_value(PARAM_ALPHANUMEXT, 'The shortname of the custom field'),
999 'value' => new external_value(PARAM_RAW, 'The value of the custom field')
1000 ]
1001 ), 'Custom fields', VALUE_OPTIONAL),
791723c3
RT
1002 )
1003 ), 'courses to update'
1004 )
1005 )
1006 );
1007 }
1008
1009 /**
1010 * Update courses
1011 *
1012 * @param array $courses
1013 * @since Moodle 2.5
1014 */
1015 public static function update_courses($courses) {
1016 global $CFG, $DB;
1017 require_once($CFG->dirroot . "/course/lib.php");
1018 $warnings = array();
1019
1020 $params = self::validate_parameters(self::update_courses_parameters(),
1021 array('courses' => $courses));
1022
bd3b3bba 1023 $availablethemes = core_component::get_plugin_list('theme');
791723c3
RT
1024 $availablelangs = get_string_manager()->get_list_of_translations();
1025
1026 foreach ($params['courses'] as $course) {
1027 // Catch any exception while updating course and return as warning to user.
1028 try {
1029 // Ensure the current user is allowed to run this function.
1030 $context = context_course::instance($course['id'], MUST_EXIST);
1031 self::validate_context($context);
1032
1033 $oldcourse = course_get_format($course['id'])->get_course();
1034
1035 require_capability('moodle/course:update', $context);
1036
1037 // Check if user can change category.
1038 if (array_key_exists('categoryid', $course) && ($oldcourse->category != $course['categoryid'])) {
1039 require_capability('moodle/course:changecategory', $context);
1040 $course['category'] = $course['categoryid'];
1041 }
1042
1043 // Check if the user can change fullname.
1044 if (array_key_exists('fullname', $course) && ($oldcourse->fullname != $course['fullname'])) {
1045 require_capability('moodle/course:changefullname', $context);
1046 }
1047
5536a561 1048 // Check if the user can change shortname.
791723c3
RT
1049 if (array_key_exists('shortname', $course) && ($oldcourse->shortname != $course['shortname'])) {
1050 require_capability('moodle/course:changeshortname', $context);
791723c3
RT
1051 }
1052
5536a561 1053 // Check if the user can change the idnumber.
791723c3
RT
1054 if (array_key_exists('idnumber', $course) && ($oldcourse->idnumber != $course['idnumber'])) {
1055 require_capability('moodle/course:changeidnumber', $context);
791723c3
RT
1056 }
1057
1058 // Check if user can change summary.
1059 if (array_key_exists('summary', $course) && ($oldcourse->summary != $course['summary'])) {
1060 require_capability('moodle/course:changesummary', $context);
1061 }
1062
1063 // Summary format.
1064 if (array_key_exists('summaryformat', $course) && ($oldcourse->summaryformat != $course['summaryformat'])) {
1065 require_capability('moodle/course:changesummary', $context);
1066 $course['summaryformat'] = external_validate_format($course['summaryformat']);
1067 }
1068
1069 // Check if user can change visibility.
1070 if (array_key_exists('visible', $course) && ($oldcourse->visible != $course['visible'])) {
1071 require_capability('moodle/course:visibility', $context);
1072 }
1073
1074 // Make sure lang is valid.
1433a078
DS
1075 if (array_key_exists('lang', $course) && ($oldcourse->lang != $course['lang'])) {
1076 require_capability('moodle/course:setforcedlanguage', $context);
1077 if (empty($availablelangs[$course['lang']])) {
1078 throw new moodle_exception('errorinvalidparam', 'webservice', '', 'lang');
1079 }
791723c3
RT
1080 }
1081
1082 // Make sure theme is valid.
1083 if (array_key_exists('forcetheme', $course)) {
1084 if (!empty($CFG->allowcoursethemes)) {
1085 if (empty($availablethemes[$course['forcetheme']])) {
1086 throw new moodle_exception('errorinvalidparam', 'webservice', '', 'forcetheme');
1087 } else {
1088 $course['theme'] = $course['forcetheme'];
1089 }
1090 }
1091 }
1092
1093 // Make sure completion is enabled before setting it.
8819a836 1094 if (array_key_exists('enabledcompletion', $course) && !completion_info::is_enabled_for_site()) {
791723c3 1095 $course['enabledcompletion'] = 0;
791723c3
RT
1096 }
1097
1098 // Make sure maxbytes are less then CFG->maxbytes.
1099 if (array_key_exists('maxbytes', $course)) {
bdd410a6
JD
1100 // We allow updates back to 0 max bytes, a special value denoting the course uses the site limit.
1101 // Otherwise, either use the size specified, or cap at the max size for the course.
1102 if ($course['maxbytes'] != 0) {
1103 $course['maxbytes'] = get_max_upload_file_size($CFG->maxbytes, $course['maxbytes']);
1104 }
791723c3
RT
1105 }
1106
1107 if (!empty($course['courseformatoptions'])) {
1108 foreach ($course['courseformatoptions'] as $option) {
1109 if (isset($option['name']) && isset($option['value'])) {
1110 $course[$option['name']] = $option['value'];
1111 }
1112 }
1113 }
1114
7a0162f1
DM
1115 // Prepare list of custom fields.
1116 if (isset($course['customfields'])) {
1117 foreach ($course['customfields'] as $field) {
1118 $course['customfield_' . $field['shortname']] = $field['value'];
1119 }
1120 }
1121
791723c3
RT
1122 // Update course if user has all required capabilities.
1123 update_course((object) $course);
1124 } catch (Exception $e) {
1125 $warning = array();
1126 $warning['item'] = 'course';
1127 $warning['itemid'] = $course['id'];
1128 if ($e instanceof moodle_exception) {
1129 $warning['warningcode'] = $e->errorcode;
1130 } else {
1131 $warning['warningcode'] = $e->getCode();
1132 }
1133 $warning['message'] = $e->getMessage();
1134 $warnings[] = $warning;
1135 }
1136 }
1137
1138 $result = array();
1139 $result['warnings'] = $warnings;
1140 return $result;
1141 }
1142
1143 /**
1144 * Returns description of method result value
1145 *
1146 * @return external_description
1147 * @since Moodle 2.5
1148 */
1149 public static function update_courses_returns() {
1150 return new external_single_structure(
1151 array(
1152 'warnings' => new external_warnings()
1153 )
1154 );
1155 }
1156
63a85dc7
JL
1157 /**
1158 * Returns description of method parameters
3ec163dd 1159 *
63a85dc7 1160 * @return external_function_parameters
3ec163dd 1161 * @since Moodle 2.2
63a85dc7
JL
1162 */
1163 public static function delete_courses_parameters() {
1164 return new external_function_parameters(
1165 array(
1166 'courseids' => new external_multiple_structure(new external_value(PARAM_INT, 'course ID')),
1167 )
1168 );
1169 }
1170
1171 /**
1172 * Delete courses
3ec163dd 1173 *
63a85dc7 1174 * @param array $courseids A list of course ids
3ec163dd 1175 * @since Moodle 2.2
63a85dc7
JL
1176 */
1177 public static function delete_courses($courseids) {
1178 global $CFG, $DB;
1179 require_once($CFG->dirroot."/course/lib.php");
1180
1181 // Parameter validation.
1182 $params = self::validate_parameters(self::delete_courses_parameters(), array('courseids'=>$courseids));
1183
70f37963 1184 $warnings = array();
63a85dc7
JL
1185
1186 foreach ($params['courseids'] as $courseid) {
70f37963
JH
1187 $course = $DB->get_record('course', array('id' => $courseid));
1188
1189 if ($course === false) {
1190 $warnings[] = array(
1191 'item' => 'course',
1192 'itemid' => $courseid,
1193 'warningcode' => 'unknowncourseidnumber',
1194 'message' => 'Unknown course ID ' . $courseid
1195 );
1196 continue;
1197 }
63a85dc7
JL
1198
1199 // Check if the context is valid.
1200 $coursecontext = context_course::instance($course->id);
1201 self::validate_context($coursecontext);
1202
70f37963 1203 // Check if the current user has permission.
63a85dc7 1204 if (!can_delete_course($courseid)) {
70f37963
JH
1205 $warnings[] = array(
1206 'item' => 'course',
1207 'itemid' => $courseid,
1208 'warningcode' => 'cannotdeletecourse',
1209 'message' => 'You do not have the permission to delete this course' . $courseid
1210 );
1211 continue;
63a85dc7
JL
1212 }
1213
70f37963
JH
1214 if (delete_course($course, false) === false) {
1215 $warnings[] = array(
1216 'item' => 'course',
1217 'itemid' => $courseid,
1218 'warningcode' => 'cannotdeletecategorycourse',
1219 'message' => 'Course ' . $courseid . ' failed to be deleted'
1220 );
1221 continue;
1222 }
63a85dc7
JL
1223 }
1224
70f37963 1225 fix_course_sortorder();
63a85dc7 1226
70f37963 1227 return array('warnings' => $warnings);
63a85dc7
JL
1228 }
1229
1230 /**
1231 * Returns description of method result value
3ec163dd 1232 *
63a85dc7 1233 * @return external_description
3ec163dd 1234 * @since Moodle 2.2
63a85dc7
JL
1235 */
1236 public static function delete_courses_returns() {
70f37963
JH
1237 return new external_single_structure(
1238 array(
1239 'warnings' => new external_warnings()
1240 )
1241 );
63a85dc7
JL
1242 }
1243
3dc1d76e
JL
1244 /**
1245 * Returns description of method parameters
1246 *
1247 * @return external_function_parameters
1248 * @since Moodle 2.3
1249 */
1250 public static function duplicate_course_parameters() {
1251 return new external_function_parameters(
1252 array(
1253 'courseid' => new external_value(PARAM_INT, 'course to duplicate id'),
1254 'fullname' => new external_value(PARAM_TEXT, 'duplicated course full name'),
1255 'shortname' => new external_value(PARAM_TEXT, 'duplicated course short name'),
1256 'categoryid' => new external_value(PARAM_INT, 'duplicated course category parent'),
1257 'visible' => new external_value(PARAM_INT, 'duplicated course visible, default to yes', VALUE_DEFAULT, 1),
1258 'options' => new external_multiple_structure(
1259 new external_single_structure(
1260 array(
3dfc29e1 1261 'name' => new external_value(PARAM_ALPHAEXT, 'The backup option name:
9aa84e91
JL
1262 "activities" (int) Include course activites (default to 1 that is equal to yes),
1263 "blocks" (int) Include course blocks (default to 1 that is equal to yes),
1264 "filters" (int) Include course filters (default to 1 that is equal to yes),
1265 "users" (int) Include users (default to 0 that is equal to no),
92253b15 1266 "enrolments" (int) Include enrolment methods (default to 1 - restore only with users),
9aa84e91 1267 "role_assignments" (int) Include role assignments (default to 0 that is equal to no),
9aa84e91 1268 "comments" (int) Include user comments (default to 0 that is equal to no),
7469c512 1269 "userscompletion" (int) Include user course completion information (default to 0 that is equal to no),
9aa84e91 1270 "logs" (int) Include course logs (default to 0 that is equal to no),
7469c512 1271 "grade_histories" (int) Include histories (default to 0 that is equal to no)'
9aa84e91
JL
1272 ),
1273 'value' => new external_value(PARAM_RAW, 'the value for the option 1 (yes) or 0 (no)'
1274 )
3dc1d76e
JL
1275 )
1276 ), VALUE_DEFAULT, array()
1277 ),
1278 )
1279 );
1280 }
1281
1282 /**
1283 * Duplicate a course
1284 *
1285 * @param int $courseid
1286 * @param string $fullname Duplicated course fullname
1287 * @param string $shortname Duplicated course shortname
1288 * @param int $categoryid Duplicated course parent category id
1289 * @param int $visible Duplicated course availability
1290 * @param array $options List of backup options
1291 * @return array New course info
1292 * @since Moodle 2.3
1293 */
3297d575 1294 public static function duplicate_course($courseid, $fullname, $shortname, $categoryid, $visible = 1, $options = array()) {
3dc1d76e
JL
1295 global $CFG, $USER, $DB;
1296 require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
1297 require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
1298
1299 // Parameter validation.
1300 $params = self::validate_parameters(
1301 self::duplicate_course_parameters(),
1302 array(
1303 'courseid' => $courseid,
1304 'fullname' => $fullname,
1305 'shortname' => $shortname,
1306 'categoryid' => $categoryid,
1307 'visible' => $visible,
1308 'options' => $options
1309 )
1310 );
1311
3ec163dd
EL
1312 // Context validation.
1313
1314 if (! ($course = $DB->get_record('course', array('id'=>$params['courseid'])))) {
19a86468 1315 throw new moodle_exception('invalidcourseid', 'error');
3ec163dd
EL
1316 }
1317
1318 // Category where duplicated course is going to be created.
1319 $categorycontext = context_coursecat::instance($params['categoryid']);
1320 self::validate_context($categorycontext);
1321
1322 // Course to be duplicated.
1323 $coursecontext = context_course::instance($course->id);
1324 self::validate_context($coursecontext);
1325
1326 $backupdefaults = array(
1327 'activities' => 1,
1328 'blocks' => 1,
1329 'filters' => 1,
1330 'users' => 0,
92253b15 1331 'enrolments' => backup::ENROL_WITHUSERS,
3ec163dd 1332 'role_assignments' => 0,
3ec163dd 1333 'comments' => 0,
7469c512 1334 'userscompletion' => 0,
3ec163dd 1335 'logs' => 0,
7469c512 1336 'grade_histories' => 0
3ec163dd
EL
1337 );
1338
1339 $backupsettings = array();
1340 // Check for backup and restore options.
1341 if (!empty($params['options'])) {
1342 foreach ($params['options'] as $option) {
1343
1344 // Strict check for a correct value (allways 1 or 0, true or false).
1345 $value = clean_param($option['value'], PARAM_INT);
1346
1347 if ($value !== 0 and $value !== 1) {
1348 throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
1349 }
1350
1351 if (!isset($backupdefaults[$option['name']])) {
1352 throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
1353 }
1354
1355 $backupsettings[$option['name']] = $value;
1356 }
1357 }
1358
1359 // Capability checking.
1360
1361 // The backup controller check for this currently, this may be redundant.
1362 require_capability('moodle/course:create', $categorycontext);
1363 require_capability('moodle/restore:restorecourse', $categorycontext);
1364 require_capability('moodle/backup:backupcourse', $coursecontext);
1365
1366 if (!empty($backupsettings['users'])) {
1367 require_capability('moodle/backup:userinfo', $coursecontext);
1368 require_capability('moodle/restore:userinfo', $categorycontext);
1369 }
1370
1371 // Check if the shortname is used.
1372 if ($foundcourses = $DB->get_records('course', array('shortname'=>$shortname))) {
1373 foreach ($foundcourses as $foundcourse) {
1374 $foundcoursenames[] = $foundcourse->fullname;
1375 }
1376
1377 $foundcoursenamestring = implode(',', $foundcoursenames);
1378 throw new moodle_exception('shortnametaken', '', '', $foundcoursenamestring);
1379 }
1380
1381 // Backup the course.
1382
1383 $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
1384 backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $USER->id);
1385
1386 foreach ($backupsettings as $name => $value) {
92253b15
MG
1387 if ($setting = $bc->get_plan()->get_setting($name)) {
1388 $bc->get_plan()->get_setting($name)->set_value($value);
1389 }
3ec163dd
EL
1390 }
1391
1392 $backupid = $bc->get_backupid();
1393 $backupbasepath = $bc->get_plan()->get_basepath();
1394
1395 $bc->execute_plan();
1396 $results = $bc->get_results();
1397 $file = $results['backup_destination'];
1398
1399 $bc->destroy();
1400
1401 // Restore the backup immediately.
1402
1403 // Check if we need to unzip the file because the backup temp dir does not contains backup files.
1404 if (!file_exists($backupbasepath . "/moodle_backup.xml")) {
00219425 1405 $file->extract_to_pathname(get_file_packer('application/vnd.moodle.backup'), $backupbasepath);
3ec163dd
EL
1406 }
1407
1408 // Create new course.
1409 $newcourseid = restore_dbops::create_new_course($params['fullname'], $params['shortname'], $params['categoryid']);
1410
1411 $rc = new restore_controller($backupid, $newcourseid,
1412 backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $USER->id, backup::TARGET_NEW_COURSE);
1413
1414 foreach ($backupsettings as $name => $value) {
1415 $setting = $rc->get_plan()->get_setting($name);
1416 if ($setting->get_status() == backup_setting::NOT_LOCKED) {
1417 $setting->set_value($value);
1418 }
1419 }
1420
1421 if (!$rc->execute_precheck()) {
1422 $precheckresults = $rc->get_precheck_results();
1423 if (is_array($precheckresults) && !empty($precheckresults['errors'])) {
1424 if (empty($CFG->keeptempdirectoriesonbackup)) {
1425 fulldelete($backupbasepath);
1426 }
1427
1428 $errorinfo = '';
1429
1430 foreach ($precheckresults['errors'] as $error) {
1431 $errorinfo .= $error;
1432 }
1433
1434 if (array_key_exists('warnings', $precheckresults)) {
1435 foreach ($precheckresults['warnings'] as $warning) {
1436 $errorinfo .= $warning;
1437 }
1438 }
1439
1440 throw new moodle_exception('backupprecheckerrors', 'webservice', '', $errorinfo);
1441 }
1442 }
1443
1444 $rc->execute_plan();
1445 $rc->destroy();
1446
1447 $course = $DB->get_record('course', array('id' => $newcourseid), '*', MUST_EXIST);
1448 $course->fullname = $params['fullname'];
1449 $course->shortname = $params['shortname'];
1450 $course->visible = $params['visible'];
1451
1452 // Set shortname and fullname back.
1453 $DB->update_record('course', $course);
1454
1455 if (empty($CFG->keeptempdirectoriesonbackup)) {
1456 fulldelete($backupbasepath);
1457 }
1458
1459 // Delete the course backup file created by this WebService. Originally located in the course backups area.
1460 $file->delete();
1461
1462 return array('id' => $course->id, 'shortname' => $course->shortname);
1463 }
1464
1465 /**
1466 * Returns description of method result value
1467 *
1468 * @return external_description
1469 * @since Moodle 2.3
1470 */
1471 public static function duplicate_course_returns() {
1472 return new external_single_structure(
1473 array(
1474 'id' => new external_value(PARAM_INT, 'course id'),
1475 'shortname' => new external_value(PARAM_TEXT, 'short name'),
1476 )
1477 );
1478 }
1479
8430d87b 1480 /**
c1483c9c 1481 * Returns description of method parameters for import_course
8430d87b
JL
1482 *
1483 * @return external_function_parameters
c1483c9c 1484 * @since Moodle 2.4
8430d87b
JL
1485 */
1486 public static function import_course_parameters() {
1487 return new external_function_parameters(
1488 array(
1489 'importfrom' => new external_value(PARAM_INT, 'the id of the course we are importing from'),
1490 'importto' => new external_value(PARAM_INT, 'the id of the course we are importing to'),
1491 'deletecontent' => new external_value(PARAM_INT, 'whether to delete the course content where we are importing to (default to 0 = No)', VALUE_DEFAULT, 0),
1492 'options' => new external_multiple_structure(
1493 new external_single_structure(
1494 array(
1495 'name' => new external_value(PARAM_ALPHA, 'The backup option name:
1496 "activities" (int) Include course activites (default to 1 that is equal to yes),
1497 "blocks" (int) Include course blocks (default to 1 that is equal to yes),
1498 "filters" (int) Include course filters (default to 1 that is equal to yes)'
1499 ),
1500 'value' => new external_value(PARAM_RAW, 'the value for the option 1 (yes) or 0 (no)'
1501 )
1502 )
1503 ), VALUE_DEFAULT, array()
1504 ),
1505 )
1506 );
1507 }
1508
1509 /**
1510 * Imports a course
1511 *
1512 * @param int $importfrom The id of the course we are importing from
1513 * @param int $importto The id of the course we are importing to
1514 * @param bool $deletecontent Whether to delete the course we are importing to content
1515 * @param array $options List of backup options
1516 * @return null
c1483c9c 1517 * @since Moodle 2.4
8430d87b 1518 */
b5bd42e8 1519 public static function import_course($importfrom, $importto, $deletecontent = 0, $options = array()) {
8430d87b
JL
1520 global $CFG, $USER, $DB;
1521 require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
1522 require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
1523
1524 // Parameter validation.
1525 $params = self::validate_parameters(
c1483c9c
SH
1526 self::import_course_parameters(),
1527 array(
1528 'importfrom' => $importfrom,
1529 'importto' => $importto,
1530 'deletecontent' => $deletecontent,
1531 'options' => $options
1532 )
8430d87b
JL
1533 );
1534
1535 if ($params['deletecontent'] !== 0 and $params['deletecontent'] !== 1) {
1b2f5493 1536 throw new moodle_exception('invalidextparam', 'webservice', '', $params['deletecontent']);
8430d87b
JL
1537 }
1538
1539 // Context validation.
1540
1541 if (! ($importfrom = $DB->get_record('course', array('id'=>$params['importfrom'])))) {
0b9a3d7a 1542 throw new moodle_exception('invalidcourseid', 'error');
8430d87b
JL
1543 }
1544
1545 if (! ($importto = $DB->get_record('course', array('id'=>$params['importto'])))) {
0b9a3d7a 1546 throw new moodle_exception('invalidcourseid', 'error');
8430d87b
JL
1547 }
1548
1549 $importfromcontext = context_course::instance($importfrom->id);
1550 self::validate_context($importfromcontext);
1551
1552 $importtocontext = context_course::instance($importto->id);
1553 self::validate_context($importtocontext);
1554
1555 $backupdefaults = array(
c1483c9c
SH
1556 'activities' => 1,
1557 'blocks' => 1,
1558 'filters' => 1
8430d87b
JL
1559 );
1560
1561 $backupsettings = array();
1562
1563 // Check for backup and restore options.
1564 if (!empty($params['options'])) {
1565 foreach ($params['options'] as $option) {
1566
1567 // Strict check for a correct value (allways 1 or 0, true or false).
1568 $value = clean_param($option['value'], PARAM_INT);
1569
1570 if ($value !== 0 and $value !== 1) {
1571 throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
1572 }
1573
1574 if (!isset($backupdefaults[$option['name']])) {
1575 throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
1576 }
1577
1578 $backupsettings[$option['name']] = $value;
1579 }
1580 }
1581
1582 // Capability checking.
1583
1584 require_capability('moodle/backup:backuptargetimport', $importfromcontext);
1585 require_capability('moodle/restore:restoretargetimport', $importtocontext);
1586
1587 $bc = new backup_controller(backup::TYPE_1COURSE, $importfrom->id, backup::FORMAT_MOODLE,
1588 backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id);
1589
1590 foreach ($backupsettings as $name => $value) {
1591 $bc->get_plan()->get_setting($name)->set_value($value);
1592 }
1593
1594 $backupid = $bc->get_backupid();
1595 $backupbasepath = $bc->get_plan()->get_basepath();
1596
1597 $bc->execute_plan();
1598 $bc->destroy();
1599
1600 // Restore the backup immediately.
1601
1602 // Check if we must delete the contents of the destination course.
1603 if ($params['deletecontent']) {
1604 $restoretarget = backup::TARGET_EXISTING_DELETING;
1605 } else {
1606 $restoretarget = backup::TARGET_EXISTING_ADDING;
1607 }
1608
1609 $rc = new restore_controller($backupid, $importto->id,
1610 backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id, $restoretarget);
1611
1612 foreach ($backupsettings as $name => $value) {
1613 $rc->get_plan()->get_setting($name)->set_value($value);
1614 }
1615
1616 if (!$rc->execute_precheck()) {
1617 $precheckresults = $rc->get_precheck_results();
1618 if (is_array($precheckresults) && !empty($precheckresults['errors'])) {
1619 if (empty($CFG->keeptempdirectoriesonbackup)) {
1620 fulldelete($backupbasepath);
1621 }
1622
1623 $errorinfo = '';
1624
1625 foreach ($precheckresults['errors'] as $error) {
1626 $errorinfo .= $error;
1627 }
1628
1629 if (array_key_exists('warnings', $precheckresults)) {
1630 foreach ($precheckresults['warnings'] as $warning) {
1631 $errorinfo .= $warning;
1632 }
1633 }
1634
1635 throw new moodle_exception('backupprecheckerrors', 'webservice', '', $errorinfo);
1636 }
1637 } else {
1638 if ($restoretarget == backup::TARGET_EXISTING_DELETING) {
1639 restore_dbops::delete_course_content($importto->id);
1640 }
1641 }
1642
1643 $rc->execute_plan();
1644 $rc->destroy();
1645
1646 if (empty($CFG->keeptempdirectoriesonbackup)) {
1647 fulldelete($backupbasepath);
1648 }
1649
1650 return null;
1651 }
1652
1653 /**
1654 * Returns description of method result value
1655 *
1656 * @return external_description
c1483c9c 1657 * @since Moodle 2.4
8430d87b
JL
1658 */
1659 public static function import_course_returns() {
1660 return null;
1661 }
1662
3ec163dd
EL
1663 /**
1664 * Returns description of method parameters
1665 *
1666 * @return external_function_parameters
1667 * @since Moodle 2.3
1668 */
1669 public static function get_categories_parameters() {
1670 return new external_function_parameters(
1671 array(
1672 'criteria' => new external_multiple_structure(
1673 new external_single_structure(
1674 array(
1675 'key' => new external_value(PARAM_ALPHA,
1676 'The category column to search, expected keys (value format) are:'.
1677 '"id" (int) the category id,'.
c1da311a 1678 '"ids" (string) category ids separated by commas,'.
3ec163dd
EL
1679 '"name" (string) the category name,'.
1680 '"parent" (int) the parent category id,'.
1681 '"idnumber" (string) category idnumber'.
1682 ' - user must have \'moodle/category:manage\' to search on idnumber,'.
e6d1218a
JM
1683 '"visible" (int) whether the returned categories must be visible or hidden. If the key is not passed,
1684 then the function return all categories that the user can see.'.
3ec163dd 1685 ' - user must have \'moodle/category:manage\' or \'moodle/category:viewhiddencategories\' to search on visible,'.
e6d1218a 1686 '"theme" (string) only return the categories having this theme'.
3ec163dd
EL
1687 ' - user must have \'moodle/category:manage\' to search on theme'),
1688 'value' => new external_value(PARAM_RAW, 'the value to match')
1689 )
7a384506 1690 ), 'criteria', VALUE_DEFAULT, array()
3ec163dd
EL
1691 ),
1692 'addsubcategories' => new external_value(PARAM_BOOL, 'return the sub categories infos
1693 (1 - default) otherwise only the category info (0)', VALUE_DEFAULT, 1)
1694 )
1695 );
1696 }
1697
1698 /**
1699 * Get categories
1700 *
1701 * @param array $criteria Criteria to match the results
1702 * @param booln $addsubcategories obtain only the category (false) or its subcategories (true - default)
1703 * @return array list of categories
1704 * @since Moodle 2.3
1705 */
1706 public static function get_categories($criteria = array(), $addsubcategories = true) {
1707 global $CFG, $DB;
1708 require_once($CFG->dirroot . "/course/lib.php");
1709
1710 // Validate parameters.
1711 $params = self::validate_parameters(self::get_categories_parameters(),
1712 array('criteria' => $criteria, 'addsubcategories' => $addsubcategories));
1713
1714 // Retrieve the categories.
1715 $categories = array();
1716 if (!empty($params['criteria'])) {
1717
1718 $conditions = array();
1719 $wheres = array();
1720 foreach ($params['criteria'] as $crit) {
1721 $key = trim($crit['key']);
1722
1723 // Trying to avoid duplicate keys.
1724 if (!isset($conditions[$key])) {
3dc1d76e 1725
3ec163dd
EL
1726 $context = context_system::instance();
1727 $value = null;
1728 switch ($key) {
1729 case 'id':
1730 $value = clean_param($crit['value'], PARAM_INT);
c1da311a
JL
1731 $conditions[$key] = $value;
1732 $wheres[] = $key . " = :" . $key;
1733 break;
1734
1735 case 'ids':
1736 $value = clean_param($crit['value'], PARAM_SEQUENCE);
1737 $ids = explode(',', $value);
1738 list($sqlids, $paramids) = $DB->get_in_or_equal($ids, SQL_PARAMS_NAMED);
1739 $conditions = array_merge($conditions, $paramids);
1740 $wheres[] = 'id ' . $sqlids;
3ec163dd 1741 break;
3dc1d76e 1742
3ec163dd
EL
1743 case 'idnumber':
1744 if (has_capability('moodle/category:manage', $context)) {
1745 $value = clean_param($crit['value'], PARAM_RAW);
c1da311a
JL
1746 $conditions[$key] = $value;
1747 $wheres[] = $key . " = :" . $key;
3ec163dd
EL
1748 } else {
1749 // We must throw an exception.
1750 // Otherwise the dev client would think no idnumber exists.
1751 throw new moodle_exception('criteriaerror',
1752 'webservice', '', null,
1753 'You don\'t have the permissions to search on the "idnumber" field.');
1754 }
1755 break;
3dc1d76e 1756
3ec163dd
EL
1757 case 'name':
1758 $value = clean_param($crit['value'], PARAM_TEXT);
c1da311a
JL
1759 $conditions[$key] = $value;
1760 $wheres[] = $key . " = :" . $key;
3ec163dd 1761 break;
3dc1d76e 1762
3ec163dd
EL
1763 case 'parent':
1764 $value = clean_param($crit['value'], PARAM_INT);
c1da311a
JL
1765 $conditions[$key] = $value;
1766 $wheres[] = $key . " = :" . $key;
3ec163dd 1767 break;
9aa84e91 1768
3ec163dd 1769 case 'visible':
d80533be 1770 if (has_capability('moodle/category:viewhiddencategories', $context)) {
3ec163dd 1771 $value = clean_param($crit['value'], PARAM_INT);
c1da311a
JL
1772 $conditions[$key] = $value;
1773 $wheres[] = $key . " = :" . $key;
3ec163dd
EL
1774 } else {
1775 throw new moodle_exception('criteriaerror',
1776 'webservice', '', null,
1777 'You don\'t have the permissions to search on the "visible" field.');
1778 }
1779 break;
9aa84e91 1780
3ec163dd
EL
1781 case 'theme':
1782 if (has_capability('moodle/category:manage', $context)) {
1783 $value = clean_param($crit['value'], PARAM_THEME);
c1da311a
JL
1784 $conditions[$key] = $value;
1785 $wheres[] = $key . " = :" . $key;
3ec163dd
EL
1786 } else {
1787 throw new moodle_exception('criteriaerror',
1788 'webservice', '', null,
1789 'You don\'t have the permissions to search on the "theme" field.');
1790 }
1791 break;
9aa84e91 1792
3ec163dd
EL
1793 default:
1794 throw new moodle_exception('criteriaerror',
1795 'webservice', '', null,
1796 'You can not search on this criteria: ' . $key);
1797 }
9aa84e91 1798 }
9aa84e91 1799 }
9aa84e91 1800
3ec163dd
EL
1801 if (!empty($wheres)) {
1802 $wheres = implode(" AND ", $wheres);
3dc1d76e 1803
3ec163dd 1804 $categories = $DB->get_records_select('course_categories', $wheres, $conditions);
3dc1d76e 1805
3ec163dd
EL
1806 // Retrieve its sub subcategories (all levels).
1807 if ($categories and !empty($params['addsubcategories'])) {
1808 $newcategories = array();
9aa84e91 1809
e6d1218a
JM
1810 // Check if we required visible/theme checks.
1811 $additionalselect = '';
1812 $additionalparams = array();
1813 if (isset($conditions['visible'])) {
1814 $additionalselect .= ' AND visible = :visible';
1815 $additionalparams['visible'] = $conditions['visible'];
1816 }
1817 if (isset($conditions['theme'])) {
1818 $additionalselect .= ' AND theme= :theme';
1819 $additionalparams['theme'] = $conditions['theme'];
1820 }
1821
3ec163dd 1822 foreach ($categories as $category) {
e6d1218a
JM
1823 $sqlselect = $DB->sql_like('path', ':path') . $additionalselect;
1824 $sqlparams = array('path' => $category->path.'/%') + $additionalparams; // It will NOT include the specified category.
1825 $subcategories = $DB->get_records_select('course_categories', $sqlselect, $sqlparams);
3ec163dd
EL
1826 $newcategories = $newcategories + $subcategories; // Both arrays have integer as keys.
1827 }
1828 $categories = $categories + $newcategories;
1829 }
3dc1d76e
JL
1830 }
1831
3ec163dd
EL
1832 } else {
1833 // Retrieve all categories in the database.
1834 $categories = $DB->get_records('course_categories');
3dc1d76e
JL
1835 }
1836
3ec163dd
EL
1837 // The not returned categories. key => category id, value => reason of exclusion.
1838 $excludedcats = array();
3dc1d76e 1839
3ec163dd
EL
1840 // The returned categories.
1841 $categoriesinfo = array();
6c7d3e31 1842
3ec163dd
EL
1843 // We need to sort the categories by path.
1844 // The parent cats need to be checked by the algo first.
1845 usort($categories, "core_course_external::compare_categories_by_path");
3dc1d76e 1846
3ec163dd 1847 foreach ($categories as $category) {
3dc1d76e 1848
3ec163dd
EL
1849 // Check if the category is a child of an excluded category, if yes exclude it too (excluded => do not return).
1850 $parents = explode('/', $category->path);
1851 unset($parents[0]); // First key is always empty because path start with / => /1/2/4.
1852 foreach ($parents as $parentid) {
1853 // Note: when the parent exclusion was due to the context,
1854 // the sub category could still be returned.
1855 if (isset($excludedcats[$parentid]) and $excludedcats[$parentid] != 'context') {
1856 $excludedcats[$category->id] = 'parent';
1857 }
1858 }
9aa84e91 1859
3ec163dd
EL
1860 // Check the user can use the category context.
1861 $context = context_coursecat::instance($category->id);
1862 try {
1863 self::validate_context($context);
1864 } catch (Exception $e) {
1865 $excludedcats[$category->id] = 'context';
3dc1d76e 1866
3ec163dd
EL
1867 // If it was the requested category then throw an exception.
1868 if (isset($params['categoryid']) && $category->id == $params['categoryid']) {
1869 $exceptionparam = new stdClass();
1870 $exceptionparam->message = $e->getMessage();
1871 $exceptionparam->catid = $category->id;
1872 throw new moodle_exception('errorcatcontextnotvalid', 'webservice', '', $exceptionparam);
1873 }
9aa84e91 1874 }
3dc1d76e 1875
3ec163dd
EL
1876 // Return the category information.
1877 if (!isset($excludedcats[$category->id])) {
3dc1d76e 1878
3ec163dd 1879 // Final check to see if the category is visible to the user.
beff3806 1880 if (core_course_category::can_view_category($category)) {
3dc1d76e 1881
3ec163dd
EL
1882 $categoryinfo = array();
1883 $categoryinfo['id'] = $category->id;
8c9a1964 1884 $categoryinfo['name'] = external_format_string($category->name, $context);
93ce0e82
JM
1885 list($categoryinfo['description'], $categoryinfo['descriptionformat']) =
1886 external_format_text($category->description, $category->descriptionformat,
1887 $context->id, 'coursecat', 'description', null);
3ec163dd
EL
1888 $categoryinfo['parent'] = $category->parent;
1889 $categoryinfo['sortorder'] = $category->sortorder;
1890 $categoryinfo['coursecount'] = $category->coursecount;
1891 $categoryinfo['depth'] = $category->depth;
1892 $categoryinfo['path'] = $category->path;
3dc1d76e 1893
3ec163dd
EL
1894 // Some fields only returned for admin.
1895 if (has_capability('moodle/category:manage', $context)) {
1896 $categoryinfo['idnumber'] = $category->idnumber;
1897 $categoryinfo['visible'] = $category->visible;
1898 $categoryinfo['visibleold'] = $category->visibleold;
1899 $categoryinfo['timemodified'] = $category->timemodified;
6db24235 1900 $categoryinfo['theme'] = clean_param($category->theme, PARAM_THEME);
3dc1d76e 1901 }
3dc1d76e 1902
3ec163dd
EL
1903 $categoriesinfo[] = $categoryinfo;
1904 } else {
1905 $excludedcats[$category->id] = 'visibility';
1906 }
3dc1d76e
JL
1907 }
1908 }
1909
3ec163dd
EL
1910 // Sorting the resulting array so it looks a bit better for the client developer.
1911 usort($categoriesinfo, "core_course_external::compare_categories_by_sortorder");
3dc1d76e 1912
3ec163dd
EL
1913 return $categoriesinfo;
1914 }
3dc1d76e 1915
3ec163dd
EL
1916 /**
1917 * Sort categories array by path
1918 * private function: only used by get_categories
1919 *
1920 * @param array $category1
1921 * @param array $category2
1922 * @return int result of strcmp
1923 * @since Moodle 2.3
1924 */
1925 private static function compare_categories_by_path($category1, $category2) {
1926 return strcmp($category1->path, $category2->path);
1927 }
6c7d3e31 1928
3ec163dd
EL
1929 /**
1930 * Sort categories array by sortorder
1931 * private function: only used by get_categories
1932 *
1933 * @param array $category1
1934 * @param array $category2
1935 * @return int result of strcmp
1936 * @since Moodle 2.3
1937 */
1938 private static function compare_categories_by_sortorder($category1, $category2) {
1939 return strcmp($category1['sortorder'], $category2['sortorder']);
3dc1d76e
JL
1940 }
1941
1942 /**
1943 * Returns description of method result value
1944 *
1945 * @return external_description
1946 * @since Moodle 2.3
1947 */
3ec163dd
EL
1948 public static function get_categories_returns() {
1949 return new external_multiple_structure(
1950 new external_single_structure(
1951 array(
1952 'id' => new external_value(PARAM_INT, 'category id'),
1953 'name' => new external_value(PARAM_TEXT, 'category name'),
1954 'idnumber' => new external_value(PARAM_RAW, 'category id number', VALUE_OPTIONAL),
1955 'description' => new external_value(PARAM_RAW, 'category description'),
93ce0e82 1956 'descriptionformat' => new external_format_value('description'),
3ec163dd
EL
1957 'parent' => new external_value(PARAM_INT, 'parent category id'),
1958 'sortorder' => new external_value(PARAM_INT, 'category sorting order'),
1959 'coursecount' => new external_value(PARAM_INT, 'number of courses in this category'),
1960 'visible' => new external_value(PARAM_INT, '1: available, 0:not available', VALUE_OPTIONAL),
1961 'visibleold' => new external_value(PARAM_INT, '1: available, 0:not available', VALUE_OPTIONAL),
1962 'timemodified' => new external_value(PARAM_INT, 'timestamp', VALUE_OPTIONAL),
1963 'depth' => new external_value(PARAM_INT, 'category depth'),
1964 'path' => new external_value(PARAM_TEXT, 'category path'),
1965 'theme' => new external_value(PARAM_THEME, 'category theme', VALUE_OPTIONAL),
1966 ), 'List of categories'
3dc1d76e
JL
1967 )
1968 );
1969 }
1970
2f951d86
FS
1971 /**
1972 * Returns description of method parameters
3ec163dd 1973 *
2f951d86
FS
1974 * @return external_function_parameters
1975 * @since Moodle 2.3
1976 */
3ec163dd 1977 public static function create_categories_parameters() {
2f951d86
FS
1978 return new external_function_parameters(
1979 array(
1980 'categories' => new external_multiple_structure(
3ec163dd
EL
1981 new external_single_structure(
1982 array(
1983 'name' => new external_value(PARAM_TEXT, 'new category name'),
1984 'parent' => new external_value(PARAM_INT,
9615b623
JM
1985 'the parent category id inside which the new category will be created
1986 - set to 0 for a root category',
1987 VALUE_DEFAULT, 0),
3ec163dd
EL
1988 'idnumber' => new external_value(PARAM_RAW,
1989 'the new category idnumber', VALUE_OPTIONAL),
1990 'description' => new external_value(PARAM_RAW,
1991 'the new category description', VALUE_OPTIONAL),
93ce0e82 1992 'descriptionformat' => new external_format_value('description', VALUE_DEFAULT),
3ec163dd
EL
1993 'theme' => new external_value(PARAM_THEME,
1994 'the new category theme. This option must be enabled on moodle',
1995 VALUE_OPTIONAL),
2f951d86
FS
1996 )
1997 )
1998 )
1999 )
2000 );
2001 }
2002
2003 /**
3ec163dd
EL
2004 * Create categories
2005 *
2006 * @param array $categories - see create_categories_parameters() for the array structure
2007 * @return array - see create_categories_returns() for the array structure
2f951d86
FS
2008 * @since Moodle 2.3
2009 */
3ec163dd 2010 public static function create_categories($categories) {
442f12f8 2011 global $DB;
2f951d86 2012
3ec163dd
EL
2013 $params = self::validate_parameters(self::create_categories_parameters(),
2014 array('categories' => $categories));
2f951d86 2015
3ec163dd
EL
2016 $transaction = $DB->start_delegated_transaction();
2017
2018 $createdcategories = array();
2f951d86 2019 foreach ($params['categories'] as $category) {
3ec163dd
EL
2020 if ($category['parent']) {
2021 if (!$DB->record_exists('course_categories', array('id' => $category['parent']))) {
2022 throw new moodle_exception('unknowcategory');
2023 }
2024 $context = context_coursecat::instance($category['parent']);
2025 } else {
2026 $context = context_system::instance();
2f951d86 2027 }
2f951d86 2028 self::validate_context($context);
3ec163dd 2029 require_capability('moodle/category:manage', $context);
2f951d86 2030
9bad61db
MG
2031 // this will validate format and throw an exception if there are errors
2032 external_validate_format($category['descriptionformat']);
3ec163dd 2033
442f12f8 2034 $newcategory = core_course_category::create($category);
8c9a1964 2035 $context = context_coursecat::instance($newcategory->id);
3ec163dd 2036
8c9a1964
JL
2037 $createdcategories[] = array(
2038 'id' => $newcategory->id,
2039 'name' => external_format_string($newcategory->name, $context),
2040 );
2f951d86
FS
2041 }
2042
3ec163dd
EL
2043 $transaction->allow_commit();
2044
2045 return $createdcategories;
2f951d86
FS
2046 }
2047
2048 /**
2049 * Returns description of method parameters
3ec163dd 2050 *
2f951d86
FS
2051 * @return external_function_parameters
2052 * @since Moodle 2.3
2053 */
3ec163dd
EL
2054 public static function create_categories_returns() {
2055 return new external_multiple_structure(
2056 new external_single_structure(
2057 array(
2058 'id' => new external_value(PARAM_INT, 'new category id'),
2059 'name' => new external_value(PARAM_TEXT, 'new category name'),
2060 )
2061 )
2062 );
2f951d86 2063 }
f2229c68
FS
2064
2065 /**
2066 * Returns description of method parameters
3ec163dd 2067 *
f2229c68
FS
2068 * @return external_function_parameters
2069 * @since Moodle 2.3
2070 */
2071 public static function update_categories_parameters() {
2072 return new external_function_parameters(
2073 array(
2074 'categories' => new external_multiple_structure(
2075 new external_single_structure(
2076 array(
2077 'id' => new external_value(PARAM_INT, 'course id'),
2078 'name' => new external_value(PARAM_TEXT, 'category name', VALUE_OPTIONAL),
2079 'idnumber' => new external_value(PARAM_RAW, 'category id number', VALUE_OPTIONAL),
2080 'parent' => new external_value(PARAM_INT, 'parent category id', VALUE_OPTIONAL),
2081 'description' => new external_value(PARAM_RAW, 'category description', VALUE_OPTIONAL),
93ce0e82 2082 'descriptionformat' => new external_format_value('description', VALUE_DEFAULT),
f2229c68
FS
2083 'theme' => new external_value(PARAM_THEME,
2084 'the category theme. This option must be enabled on moodle', VALUE_OPTIONAL),
2085 )
2086 )
2087 )
2088 )
2089 );
2090 }
2091
2092 /**
2093 * Update categories
3ec163dd 2094 *
f2229c68
FS
2095 * @param array $categories The list of categories to update
2096 * @return null
2097 * @since Moodle 2.3
2098 */
2099 public static function update_categories($categories) {
442f12f8 2100 global $DB;
f2229c68
FS
2101
2102 // Validate parameters.
2103 $params = self::validate_parameters(self::update_categories_parameters(), array('categories' => $categories));
2104
2105 $transaction = $DB->start_delegated_transaction();
2106
2107 foreach ($params['categories'] as $cat) {
442f12f8 2108 $category = core_course_category::get($cat['id']);
f2229c68
FS
2109
2110 $categorycontext = context_coursecat::instance($cat['id']);
2111 self::validate_context($categorycontext);
2112 require_capability('moodle/category:manage', $categorycontext);
2113
9bad61db
MG
2114 // this will throw an exception if descriptionformat is not valid
2115 external_validate_format($cat['descriptionformat']);
2116
2117 $category->update($cat);
f2229c68
FS
2118 }
2119
2120 $transaction->allow_commit();
2121 }
2122
2123 /**
2124 * Returns description of method result value
3ec163dd 2125 *
f2229c68 2126 * @return external_description
3ec163dd 2127 * @since Moodle 2.3
f2229c68
FS
2128 */
2129 public static function update_categories_returns() {
2130 return null;
2131 }
3ec163dd
EL
2132
2133 /**
2134 * Returns description of method parameters
2135 *
2136 * @return external_function_parameters
2137 * @since Moodle 2.3
2138 */
2139 public static function delete_categories_parameters() {
2140 return new external_function_parameters(
2141 array(
2142 'categories' => new external_multiple_structure(
2143 new external_single_structure(
2144 array(
2145 'id' => new external_value(PARAM_INT, 'category id to delete'),
2146 'newparent' => new external_value(PARAM_INT,
2147 'the parent category to move the contents to, if specified', VALUE_OPTIONAL),
2148 'recursive' => new external_value(PARAM_BOOL, '1: recursively delete all contents inside this
2149 category, 0 (default): move contents to newparent or current parent category (except if parent is root)', VALUE_DEFAULT, 0)
2150 )
2151 )
2152 )
2153 )
2154 );
2155 }
2156
2157 /**
2158 * Delete categories
2159 *
2160 * @param array $categories A list of category ids
2161 * @return array
2162 * @since Moodle 2.3
2163 */
2164 public static function delete_categories($categories) {
2165 global $CFG, $DB;
2166 require_once($CFG->dirroot . "/course/lib.php");
2167
2168 // Validate parameters.
2169 $params = self::validate_parameters(self::delete_categories_parameters(), array('categories' => $categories));
2170
f823158b
EL
2171 $transaction = $DB->start_delegated_transaction();
2172
3ec163dd 2173 foreach ($params['categories'] as $category) {
442f12f8 2174 $deletecat = core_course_category::get($category['id'], MUST_EXIST);
3ec163dd
EL
2175 $context = context_coursecat::instance($deletecat->id);
2176 require_capability('moodle/category:manage', $context);
2177 self::validate_context($context);
2178 self::validate_context(get_category_or_system_context($deletecat->parent));
2179
2180 if ($category['recursive']) {
2181 // If recursive was specified, then we recursively delete the category's contents.
deb65ced
MG
2182 if ($deletecat->can_delete_full()) {
2183 $deletecat->delete_full(false);
2184 } else {
2185 throw new moodle_exception('youcannotdeletecategory', '', '', $deletecat->get_formatted_name());
2186 }
3ec163dd
EL
2187 } else {
2188 // In this situation, we don't delete the category's contents, we either move it to newparent or parent.
2189 // If the parent is the root, moving is not supported (because a course must always be inside a category).
2190 // We must move to an existing category.
2191 if (!empty($category['newparent'])) {
442f12f8 2192 $newparentcat = core_course_category::get($category['newparent']);
3ec163dd 2193 } else {
442f12f8 2194 $newparentcat = core_course_category::get($deletecat->parent);
3ec163dd
EL
2195 }
2196
2197 // This operation is not allowed. We must move contents to an existing category.
deb65ced 2198 if (!$newparentcat->id) {
3ec163dd
EL
2199 throw new moodle_exception('movecatcontentstoroot');
2200 }
2201
deb65ced
MG
2202 self::validate_context(context_coursecat::instance($newparentcat->id));
2203 if ($deletecat->can_move_content_to($newparentcat->id)) {
2204 $deletecat->delete_move($newparentcat->id, false);
2205 } else {
2206 throw new moodle_exception('youcannotdeletecategory', '', '', $deletecat->get_formatted_name());
2207 }
3ec163dd
EL
2208 }
2209 }
2210
f823158b 2211 $transaction->allow_commit();
3ec163dd
EL
2212 }
2213
2214 /**
2215 * Returns description of method parameters
2216 *
2217 * @return external_function_parameters
2218 * @since Moodle 2.3
2219 */
2220 public static function delete_categories_returns() {
2221 return null;
2222 }
2223
79949c1b
MN
2224 /**
2225 * Describes the parameters for delete_modules.
2226 *
9db43c73 2227 * @return external_function_parameters
79949c1b
MN
2228 * @since Moodle 2.5
2229 */
2230 public static function delete_modules_parameters() {
2231 return new external_function_parameters (
2232 array(
2233 'cmids' => new external_multiple_structure(new external_value(PARAM_INT, 'course module ID',
2234 VALUE_REQUIRED, '', NULL_NOT_ALLOWED), 'Array of course module IDs'),
2235 )
2236 );
2237 }
2238
2239 /**
2240 * Deletes a list of provided module instances.
2241 *
2242 * @param array $cmids the course module ids
2243 * @since Moodle 2.5
2244 */
2245 public static function delete_modules($cmids) {
2246 global $CFG, $DB;
2247
2248 // Require course file containing the course delete module function.
2249 require_once($CFG->dirroot . "/course/lib.php");
2250
2251 // Clean the parameters.
2252 $params = self::validate_parameters(self::delete_modules_parameters(), array('cmids' => $cmids));
2253
2254 // Keep track of the course ids we have performed a capability check on to avoid repeating.
2255 $arrcourseschecked = array();
2256
2257 foreach ($params['cmids'] as $cmid) {
2258 // Get the course module.
2259 $cm = $DB->get_record('course_modules', array('id' => $cmid), '*', MUST_EXIST);
2260
2261 // Check if we have not yet confirmed they have permission in this course.
2262 if (!in_array($cm->course, $arrcourseschecked)) {
2263 // Ensure the current user has required permission in this course.
2264 $context = context_course::instance($cm->course);
2265 self::validate_context($context);
2266 // Add to the array.
2267 $arrcourseschecked[] = $cm->course;
2268 }
2269
2270 // Ensure they can delete this module.
2271 $modcontext = context_module::instance($cm->id);
2272 require_capability('moodle/course:manageactivities', $modcontext);
2273
2274 // Delete the module.
2275 course_delete_module($cm->id);
2276 }
2277 }
2278
2279 /**
2280 * Describes the delete_modules return value.
2281 *
2282 * @return external_single_structure
2283 * @since Moodle 2.5
2284 */
2285 public static function delete_modules_returns() {
2286 return null;
2287 }
7aed00d5
JL
2288
2289 /**
2290 * Returns description of method parameters
2291 *
2292 * @return external_function_parameters
2293 * @since Moodle 2.9
2294 */
2295 public static function view_course_parameters() {
2296 return new external_function_parameters(
2297 array(
2298 'courseid' => new external_value(PARAM_INT, 'id of the course'),
2299 'sectionnumber' => new external_value(PARAM_INT, 'section number', VALUE_DEFAULT, 0)
2300 )
2301 );
2302 }
2303
2304 /**
1c2b7882 2305 * Trigger the course viewed event.
7aed00d5
JL
2306 *
2307 * @param int $courseid id of course
2308 * @param int $sectionnumber sectionnumber (0, 1, 2...)
2309 * @return array of warnings and status result
2310 * @since Moodle 2.9
2311 * @throws moodle_exception
2312 */
2313 public static function view_course($courseid, $sectionnumber = 0) {
2314 global $CFG;
2315 require_once($CFG->dirroot . "/course/lib.php");
2316
2317 $params = self::validate_parameters(self::view_course_parameters(),
2318 array(
2319 'courseid' => $courseid,
2320 'sectionnumber' => $sectionnumber
2321 ));
2322
2323 $warnings = array();
2324
2325 $course = get_course($params['courseid']);
2326 $context = context_course::instance($course->id);
2327 self::validate_context($context);
2328
4dfef5e9
JL
2329 if (!empty($params['sectionnumber'])) {
2330
2331 // Get section details and check it exists.
2332 $modinfo = get_fast_modinfo($course);
2333 $coursesection = $modinfo->get_section_info($params['sectionnumber'], MUST_EXIST);
2334
2335 // Check user is allowed to see it.
2336 if (!$coursesection->uservisible) {
2337 require_capability('moodle/course:viewhiddensections', $context);
2338 }
2339 }
2340
7aed00d5
JL
2341 course_view($context, $params['sectionnumber']);
2342
2343 $result = array();
2344 $result['status'] = true;
2345 $result['warnings'] = $warnings;
2346 return $result;
2347 }
2348
2349 /**
2350 * Returns description of method result value
2351 *
2352 * @return external_description
2353 * @since Moodle 2.9
2354 */
2355 public static function view_course_returns() {
2356 return new external_single_structure(
2357 array(
2358 'status' => new external_value(PARAM_BOOL, 'status: true if success'),
2359 'warnings' => new external_warnings()
2360 )
2361 );
2362 }
2363
740c354f
JL
2364 /**
2365 * Returns description of method parameters
2366 *
2367 * @return external_function_parameters
2368 * @since Moodle 3.0
2369 */
2370 public static function search_courses_parameters() {
2371 return new external_function_parameters(
2372 array(
2373 'criterianame' => new external_value(PARAM_ALPHA, 'criteria name
2374 (search, modulelist (only admins), blocklist (only admins), tagid)'),
2375 'criteriavalue' => new external_value(PARAM_RAW, 'criteria value'),
2376 'page' => new external_value(PARAM_INT, 'page number (0 based)', VALUE_DEFAULT, 0),
235ef57a
DW
2377 'perpage' => new external_value(PARAM_INT, 'items per page', VALUE_DEFAULT, 0),
2378 'requiredcapabilities' => new external_multiple_structure(
2379 new external_value(PARAM_CAPABILITY, 'Capability string used to filter courses by permission'),
72ac2aac 2380 'Optional list of required capabilities (used to filter the list)', VALUE_DEFAULT, array()
427e3cbc
EM
2381 ),
2382 'limittoenrolled' => new external_value(PARAM_BOOL, 'limit to enrolled courses', VALUE_DEFAULT, 0),
76f2d894
MG
2383 'onlywithcompletion' => new external_value(PARAM_BOOL, 'limit to courses where completion is enabled',
2384 VALUE_DEFAULT, 0),
740c354f
JL
2385 )
2386 );
2387 }
2388
80adabef
JL
2389 /**
2390 * Return the course information that is public (visible by every one)
2391 *
442f12f8 2392 * @param core_course_list_element $course course in list object
80adabef
JL
2393 * @param stdClass $coursecontext course context object
2394 * @return array the course information
2395 * @since Moodle 3.2
2396 */
442f12f8 2397 protected static function get_course_public_information(core_course_list_element $course, $coursecontext) {
80adabef
JL
2398
2399 static $categoriescache = array();
2400
2401 // Category information.
2402 if (!array_key_exists($course->category, $categoriescache)) {
442f12f8 2403 $categoriescache[$course->category] = core_course_category::get($course->category, IGNORE_MISSING);
80adabef
JL
2404 }
2405 $category = $categoriescache[$course->category];
2406
2407 // Retrieve course overview used files.
2408 $files = array();
2409 foreach ($course->get_course_overviewfiles() as $file) {
2410 $fileurl = moodle_url::make_webservice_pluginfile_url($file->get_contextid(), $file->get_component(),
2411 $file->get_filearea(), null, $file->get_filepath(),
2412 $file->get_filename())->out(false);
2413 $files[] = array(
2414 'filename' => $file->get_filename(),
2415 'fileurl' => $fileurl,
2416 'filesize' => $file->get_filesize(),
2417 'filepath' => $file->get_filepath(),
2418 'mimetype' => $file->get_mimetype(),
2419 'timemodified' => $file->get_timemodified(),
2420 );
2421 }
2422
2423 // Retrieve the course contacts,
2424 // we need here the users fullname since if we are not enrolled can be difficult to obtain them via other Web Services.
2425 $coursecontacts = array();
2426 foreach ($course->get_course_contacts() as $contact) {
2427 $coursecontacts[] = array(
2428 'id' => $contact['user']->id,
a487a3ed
DK
2429 'fullname' => $contact['username'],
2430 'roles' => array_map(function($role){
2431 return array('id' => $role->id, 'name' => $role->displayname);
a1697454 2432 }, $contact['roles']),
a487a3ed
DK
2433 'role' => array('id' => $contact['role']->id, 'name' => $contact['role']->displayname),
2434 'rolename' => $contact['rolename']
2435 );
80adabef
JL
2436 }
2437
2438 // Allowed enrolment methods (maybe we can self-enrol).
2439 $enroltypes = array();
2440 $instances = enrol_get_instances($course->id, true);
2441 foreach ($instances as $instance) {
2442 $enroltypes[] = $instance->enrol;
2443 }
2444
2445 // Format summary.
2446 list($summary, $summaryformat) =
2447 external_format_text($course->summary, $course->summaryformat, $coursecontext->id, 'course', 'summary', null);
2448
8c9a1964
JL
2449 $categoryname = '';
2450 if (!empty($category)) {
2451 $categoryname = external_format_string($category->name, $category->get_context());
2452 }
2453
80adabef
JL
2454 $displayname = get_course_display_name_for_list($course);
2455 $coursereturns = array();
2456 $coursereturns['id'] = $course->id;
2457 $coursereturns['fullname'] = external_format_string($course->fullname, $coursecontext->id);
2458 $coursereturns['displayname'] = external_format_string($displayname, $coursecontext->id);
2459 $coursereturns['shortname'] = external_format_string($course->shortname, $coursecontext->id);
2460 $coursereturns['categoryid'] = $course->category;
8c9a1964 2461 $coursereturns['categoryname'] = $categoryname;
80adabef
JL
2462 $coursereturns['summary'] = $summary;
2463 $coursereturns['summaryformat'] = $summaryformat;
2464 $coursereturns['summaryfiles'] = external_util::get_area_files($coursecontext->id, 'course', 'summary', false, false);
2465 $coursereturns['overviewfiles'] = $files;
2466 $coursereturns['contacts'] = $coursecontacts;
2467 $coursereturns['enrollmentmethods'] = $enroltypes;
fb41d2f0 2468 $coursereturns['sortorder'] = $course->sortorder;
bfae6ca7
JL
2469
2470 $handler = core_course\customfield\course_handler::create();
2471 if ($customfields = $handler->export_instance_data($course->id)) {
2472 $coursereturns['customfields'] = [];
2473 foreach ($customfields as $data) {
2474 $coursereturns['customfields'][] = [
2475 'type' => $data->get_type(),
2476 'value' => $data->get_value(),
2477 'name' => $data->get_name(),
2478 'shortname' => $data->get_shortname()
2479 ];
2480 }
2481 }
2482
80adabef
JL
2483 return $coursereturns;
2484 }
2485
740c354f
JL
2486 /**
2487 * Search courses following the specified criteria.
2488 *
2489 * @param string $criterianame Criteria name (search, modulelist (only admins), blocklist (only admins), tagid)
2490 * @param string $criteriavalue Criteria value
2491 * @param int $page Page number (for pagination)
2492 * @param int $perpage Items per page
235ef57a 2493 * @param array $requiredcapabilities Optional list of required capabilities (used to filter the list).
427e3cbc 2494 * @param int $limittoenrolled Limit to only enrolled courses
76f2d894 2495 * @param int onlywithcompletion Limit to only courses where completion is enabled
740c354f
JL
2496 * @return array of course objects and warnings
2497 * @since Moodle 3.0
2498 * @throws moodle_exception
2499 */
235ef57a
DW
2500 public static function search_courses($criterianame,
2501 $criteriavalue,
2502 $page=0,
2503 $perpage=0,
427e3cbc 2504 $requiredcapabilities=array(),
76f2d894
MG
2505 $limittoenrolled=0,
2506 $onlywithcompletion=0) {
740c354f 2507 global $CFG;
740c354f
JL
2508
2509 $warnings = array();
2510
2511 $parameters = array(
2512 'criterianame' => $criterianame,
2513 'criteriavalue' => $criteriavalue,
2514 'page' => $page,
235ef57a 2515 'perpage' => $perpage,
76f2d894
MG
2516 'requiredcapabilities' => $requiredcapabilities,
2517 'limittoenrolled' => $limittoenrolled,
2518 'onlywithcompletion' => $onlywithcompletion
740c354f
JL
2519 );
2520 $params = self::validate_parameters(self::search_courses_parameters(), $parameters);
1d014075 2521 self::validate_context(context_system::instance());
740c354f
JL
2522
2523 $allowedcriterianames = array('search', 'modulelist', 'blocklist', 'tagid');
2524 if (!in_array($params['criterianame'], $allowedcriterianames)) {
2525 throw new invalid_parameter_exception('Invalid value for criterianame parameter (value: '.$params['criterianame'].'),' .
2526 'allowed values are: '.implode(',', $allowedcriterianames));
2527 }
2528
2529 if ($params['criterianame'] == 'modulelist' or $params['criterianame'] == 'blocklist') {
2530 require_capability('moodle/site:config', context_system::instance());
2531 }
2532
2533 $paramtype = array(
2534 'search' => PARAM_RAW,
2535 'modulelist' => PARAM_PLUGIN,
2536 'blocklist' => PARAM_INT,
2537 'tagid' => PARAM_INT
2538 );
2539 $params['criteriavalue'] = clean_param($params['criteriavalue'], $paramtype[$params['criterianame']]);
2540
2541 // Prepare the search API options.
2542 $searchcriteria = array();
2543 $searchcriteria[$params['criterianame']] = $params['criteriavalue'];
76f2d894
MG
2544 if ($params['onlywithcompletion']) {
2545 $searchcriteria['onlywithcompletion'] = true;
2546 }
740c354f
JL
2547
2548 $options = array();
2549 if ($params['perpage'] != 0) {
2550 $offset = $params['page'] * $params['perpage'];
2551 $options = array('offset' => $offset, 'limit' => $params['perpage']);
2552 }
2553
2554 // Search the courses.
442f12f8
MG
2555 $courses = core_course_category::search_courses($searchcriteria, $options, $params['requiredcapabilities']);
2556 $totalcount = core_course_category::search_courses_count($searchcriteria, $options, $params['requiredcapabilities']);
740c354f 2557
427e3cbc
EM
2558 if (!empty($limittoenrolled)) {
2559 // Get the courses where the current user has access.
2560 $enrolled = enrol_get_my_courses(array('id', 'cacherev'));
2561 }
2562
740c354f
JL
2563 $finalcourses = array();
2564 $categoriescache = array();
2565
2566 foreach ($courses as $course) {
427e3cbc
EM
2567 if (!empty($limittoenrolled)) {
2568 // Filter out not enrolled courses.
935ee1c6 2569 if (!isset($enrolled[$course->id])) {
427e3cbc
EM
2570 $totalcount--;
2571 continue;
2572 }
2573 }
740c354f
JL
2574
2575 $coursecontext = context_course::instance($course->id);
2576
80adabef 2577 $finalcourses[] = self::get_course_public_information($course, $coursecontext);
740c354f
JL
2578 }
2579
2580 return array(
2581 'total' => $totalcount,
2582 'courses' => $finalcourses,
2583 'warnings' => $warnings
2584 );
2585 }
2586
80adabef
JL
2587 /**
2588 * Returns a course structure definition
2589 *
2590 * @param boolean $onlypublicdata set to true, to retrieve only fields viewable by anyone when the course is visible
2591 * @return array the course structure
2592 * @since Moodle 3.2
2593 */
2594 protected static function get_course_structure($onlypublicdata = true) {
2595 $coursestructure = array(
2596 'id' => new external_value(PARAM_INT, 'course id'),
2597 'fullname' => new external_value(PARAM_TEXT, 'course full name'),
2598 'displayname' => new external_value(PARAM_TEXT, 'course display name'),
2599 'shortname' => new external_value(PARAM_TEXT, 'course short name'),
2600 'categoryid' => new external_value(PARAM_INT, 'category id'),
2601 'categoryname' => new external_value(PARAM_TEXT, 'category name'),
fb41d2f0 2602 'sortorder' => new external_value(PARAM_INT, 'Sort order in the category', VALUE_OPTIONAL),
80adabef
JL
2603 'summary' => new external_value(PARAM_RAW, 'summary'),
2604 'summaryformat' => new external_format_value('summary'),
2605 'summaryfiles' => new external_files('summary files in the summary field', VALUE_OPTIONAL),
2606 'overviewfiles' => new external_files('additional overview files attached to this course'),
2607 'contacts' => new external_multiple_structure(
2608 new external_single_structure(
2609 array(
2610 'id' => new external_value(PARAM_INT, 'contact user id'),
2611 'fullname' => new external_value(PARAM_NOTAGS, 'contact user fullname'),
2612 )
2613 ),
2614 'contact users'
2615 ),
2616 'enrollmentmethods' => new external_multiple_structure(
2617 new external_value(PARAM_PLUGIN, 'enrollment method'),
2618 'enrollment methods list'
2619 ),
7a0162f1
DM
2620 'customfields' => new external_multiple_structure(
2621 new external_single_structure(
2622 array(
2623 'name' => new external_value(PARAM_RAW, 'The name of the custom field'),
bbf60b14
MG
2624 'shortname' => new external_value(PARAM_RAW,
2625 'The shortname of the custom field - to be able to build the field class in the code'),
2626 'type' => new external_value(PARAM_ALPHANUMEXT,
2627 'The type of the custom field - text field, checkbox...'),
7a0162f1
DM
2628 'value' => new external_value(PARAM_RAW, 'The value of the custom field'),
2629 )
2630 ), 'Custom fields', VALUE_OPTIONAL),
80adabef
JL
2631 );
2632
2633 if (!$onlypublicdata) {
2634 $extra = array(
2635 'idnumber' => new external_value(PARAM_RAW, 'Id number', VALUE_OPTIONAL),
2636 'format' => new external_value(PARAM_PLUGIN, 'Course format: weeks, topics, social, site,..', VALUE_OPTIONAL),
2637 'showgrades' => new external_value(PARAM_INT, '1 if grades are shown, otherwise 0', VALUE_OPTIONAL),
2638 'newsitems' => new external_value(PARAM_INT, 'Number of recent items appearing on the course page', VALUE_OPTIONAL),
2639 'startdate' => new external_value(PARAM_INT, 'Timestamp when the course start', VALUE_OPTIONAL),
ef83fc2a 2640 'enddate' => new external_value(PARAM_INT, 'Timestamp when the course end', VALUE_OPTIONAL),
80adabef
JL
2641 'maxbytes' => new external_value(PARAM_INT, 'Largest size of file that can be uploaded into', VALUE_OPTIONAL),
2642 'showreports' => new external_value(PARAM_INT, 'Are activity report shown (yes = 1, no =0)', VALUE_OPTIONAL),
2643 'visible' => new external_value(PARAM_INT, '1: available to student, 0:not available', VALUE_OPTIONAL),
2644 'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible', VALUE_OPTIONAL),
2645 'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no', VALUE_OPTIONAL),
2646 'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id', VALUE_OPTIONAL),
2647 'enablecompletion' => new external_value(PARAM_INT, 'Completion enabled? 1: yes 0: no', VALUE_OPTIONAL),
2648 'completionnotify' => new external_value(PARAM_INT, '1: yes 0: no', VALUE_OPTIONAL),
2649 'lang' => new external_value(PARAM_SAFEDIR, 'Forced course language', VALUE_OPTIONAL),
2650 'theme' => new external_value(PARAM_PLUGIN, 'Fame of the forced theme', VALUE_OPTIONAL),
80adabef
JL
2651 'marker' => new external_value(PARAM_INT, 'Current course marker', VALUE_OPTIONAL),
2652 'legacyfiles' => new external_value(PARAM_INT, 'If legacy files are enabled', VALUE_OPTIONAL),
2653 'calendartype' => new external_value(PARAM_PLUGIN, 'Calendar type', VALUE_OPTIONAL),
2654 'timecreated' => new external_value(PARAM_INT, 'Time when the course was created', VALUE_OPTIONAL),
2655 'timemodified' => new external_value(PARAM_INT, 'Last time the course was updated', VALUE_OPTIONAL),
2656 'requested' => new external_value(PARAM_INT, 'If is a requested course', VALUE_OPTIONAL),
2657 'cacherev' => new external_value(PARAM_INT, 'Cache revision number', VALUE_OPTIONAL),
e45fc71e
JL
2658 'filters' => new external_multiple_structure(
2659 new external_single_structure(
2660 array(
2661 'filter' => new external_value(PARAM_PLUGIN, 'Filter plugin name'),
2662 'localstate' => new external_value(PARAM_INT, 'Filter state: 1 for on, -1 for off, 0 if inherit'),
2663 'inheritedstate' => new external_value(PARAM_INT, '1 or 0 to use when localstate is set to inherit'),
2664 )
2665 ),
2666 'Course filters', VALUE_OPTIONAL
2667 ),
cf58a2d5
JL
2668 'courseformatoptions' => new external_multiple_structure(
2669 new external_single_structure(
2670 array(
2671 'name' => new external_value(PARAM_RAW, 'Course format option name.'),
2672 'value' => new external_value(PARAM_RAW, 'Course format option value.'),
2673 )
2674 ),
2675 'Additional options for particular course format.', VALUE_OPTIONAL
2676 ),
80adabef
JL
2677 );
2678 $coursestructure = array_merge($coursestructure, $extra);
2679 }
2680 return new external_single_structure($coursestructure);
2681 }
2682
740c354f
JL
2683 /**
2684 * Returns description of method result value
2685 *
2686 * @return external_description
2687 * @since Moodle 3.0
2688 */
2689 public static function search_courses_returns() {
740c354f
JL
2690 return new external_single_structure(
2691 array(
2692 'total' => new external_value(PARAM_INT, 'total course count'),
80adabef 2693 'courses' => new external_multiple_structure(self::get_course_structure(), 'course'),
740c354f
JL
2694 'warnings' => new external_warnings()
2695 )
2696 );
2697 }
c5158499
JL
2698
2699 /**
2700 * Returns description of method parameters
2701 *
2702 * @return external_function_parameters
2703 * @since Moodle 3.0
2704 */
2705 public static function get_course_module_parameters() {
2706 return new external_function_parameters(
2707 array(
2708 'cmid' => new external_value(PARAM_INT, 'The course module id')
2709 )
2710 );
2711 }
2712
2713 /**
2714 * Return information about a course module.
2715 *
2716 * @param int $cmid the course module id
2717 * @return array of warnings and the course module
2718 * @since Moodle 3.0
2719 * @throws moodle_exception
2720 */
2721 public static function get_course_module($cmid) {
796876b0 2722 global $CFG, $DB;
c5158499 2723
796876b0 2724 $params = self::validate_parameters(self::get_course_module_parameters(), array('cmid' => $cmid));
c5158499
JL
2725 $warnings = array();
2726
2727 $cm = get_coursemodule_from_id(null, $params['cmid'], 0, true, MUST_EXIST);
2728 $context = context_module::instance($cm->id);
2729 self::validate_context($context);
2730
2731 // If the user has permissions to manage the activity, return all the information.
2732 if (has_capability('moodle/course:manageactivities', $context)) {
796876b0
JL
2733 require_once($CFG->dirroot . '/course/modlib.php');
2734 require_once($CFG->libdir . '/gradelib.php');
2735
c5158499 2736 $info = $cm;
796876b0
JL
2737 // Get the extra information: grade, advanced grading and outcomes data.
2738 $course = get_course($cm->course);
2739 list($newcm, $newcontext, $module, $extrainfo, $cw) = get_moduleinfo_data($cm, $course);
2740 // Grades.
2741 $gradeinfo = array('grade', 'gradepass', 'gradecat');
2742 foreach ($gradeinfo as $gfield) {
2743 if (isset($extrainfo->{$gfield})) {
2744 $info->{$gfield} = $extrainfo->{$gfield};
2745 }
2746 }
2747 if (isset($extrainfo->grade) and $extrainfo->grade < 0) {
2748 $info->scale = $DB->get_field('scale', 'scale', array('id' => abs($extrainfo->grade)));
2749 }
2750 // Advanced grading.
2751 if (isset($extrainfo->_advancedgradingdata)) {
2752 $info->advancedgrading = array();
2753 foreach ($extrainfo as $key => $val) {
2754 if (strpos($key, 'advancedgradingmethod_') === 0) {
2755 $info->advancedgrading[] = array(
2756 'area' => str_replace('advancedgradingmethod_', '', $key),
2757 'method' => $val
2758 );
2759 }
2760 }
2761 }
2762 // Outcomes.
2763 foreach ($extrainfo as $key => $val) {
2764 if (strpos($key, 'outcome_') === 0) {
2765 if (!isset($info->outcomes)) {
2766 $info->outcomes = array();
2767 }
2768 $id = str_replace('outcome_', '', $key);
28ff87be
PFO
2769 $outcome = grade_outcome::fetch(array('id' => $id));
2770 $scaleitems = $outcome->load_scale();
796876b0
JL
2771 $info->outcomes[] = array(
2772 'id' => $id,
28ff87be
PFO
2773 'name' => external_format_string($outcome->get_name(), $context->id),
2774 'scale' => $scaleitems->scale
796876b0
JL
2775 );
2776 }
2777 }
c5158499
JL
2778 } else {
2779 // Return information is safe to show to any user.
2780 $info = new stdClass();
2781 $info->id = $cm->id;
2782 $info->course = $cm->course;
2783 $info->module = $cm->module;
2784 $info->modname = $cm->modname;
2785 $info->instance = $cm->instance;
2786 $info->section = $cm->section;
2787 $info->sectionnum = $cm->sectionnum;
2788 $info->groupmode = $cm->groupmode;
2789 $info->groupingid = $cm->groupingid;
2790 $info->completion = $cm->completion;
2791 }
2792 // Format name.
9748791b 2793 $info->name = external_format_string($cm->name, $context->id);
c5158499
JL
2794 $result = array();
2795 $result['cm'] = $info;
2796 $result['warnings'] = $warnings;
2797 return $result;
2798 }
2799
2800 /**
2801 * Returns description of method result value
2802 *
2803 * @return external_description
2804 * @since Moodle 3.0
2805 */
2806 public static function get_course_module_returns() {
2807 return new external_single_structure(
2808 array(
2809 'cm' => new external_single_structure(
2810 array(
2811 'id' => new external_value(PARAM_INT, 'The course module id'),
2812 'course' => new external_value(PARAM_INT, 'The course id'),
2813 'module' => new external_value(PARAM_INT, 'The module type id'),
9748791b 2814 'name' => new external_value(PARAM_RAW, 'The activity name'),
c5158499
JL
2815 'modname' => new external_value(PARAM_COMPONENT, 'The module component name (forum, assign, etc..)'),
2816 'instance' => new external_value(PARAM_INT, 'The activity instance id'),
2817 'section' => new external_value(PARAM_INT, 'The module section id'),
2818 'sectionnum' => new external_value(PARAM_INT, 'The module section number'),
2819 'groupmode' => new external_value(PARAM_INT, 'Group mode'),
2820 'groupingid' => new external_value(PARAM_INT, 'Grouping id'),
2821 'completion' => new external_value(PARAM_INT, 'If completion is enabled'),
2822 'idnumber' => new external_value(PARAM_RAW, 'Module id number', VALUE_OPTIONAL),
2823 'added' => new external_value(PARAM_INT, 'Time added', VALUE_OPTIONAL),
2824 'score' => new external_value(PARAM_INT, 'Score', VALUE_OPTIONAL),
2825 'indent' => new external_value(PARAM_INT, 'Indentation', VALUE_OPTIONAL),
2826 'visible' => new external_value(PARAM_INT, 'If visible', VALUE_OPTIONAL),
4529327a 2827 'visibleoncoursepage' => new external_value(PARAM_INT, 'If visible on course page', VALUE_OPTIONAL),
c5158499
JL
2828 'visibleold' => new external_value(PARAM_INT, 'Visible old', VALUE_OPTIONAL),
2829 'completiongradeitemnumber' => new external_value(PARAM_INT, 'Completion grade item', VALUE_OPTIONAL),
2830 'completionview' => new external_value(PARAM_INT, 'Completion view setting', VALUE_OPTIONAL),
2831 'completionexpected' => new external_value(PARAM_INT, 'Completion time expected', VALUE_OPTIONAL),
2832 'showdescription' => new external_value(PARAM_INT, 'If the description is showed', VALUE_OPTIONAL),
2833 'availability' => new external_value(PARAM_RAW, 'Availability settings', VALUE_OPTIONAL),
7ddb5f25 2834 'grade' => new external_value(PARAM_FLOAT, 'Grade (max value or scale id)', VALUE_OPTIONAL),
796876b0
JL
2835 'scale' => new external_value(PARAM_TEXT, 'Scale items (if used)', VALUE_OPTIONAL),
2836 'gradepass' => new external_value(PARAM_RAW, 'Grade to pass (float)', VALUE_OPTIONAL),
2837 'gradecat' => new external_value(PARAM_INT, 'Grade category', VALUE_OPTIONAL),
2838 'advancedgrading' => new external_multiple_structure(
2839 new external_single_structure(
2840 array(
2841 'area' => new external_value(PARAM_AREA, 'Gradable area name'),
2842 'method' => new external_value(PARAM_COMPONENT, 'Grading method'),
2843 )
2844 ),
2845 'Advanced grading settings', VALUE_OPTIONAL
2846 ),
2847 'outcomes' => new external_multiple_structure(
2848 new external_single_structure(
2849 array(
2850 'id' => new external_value(PARAM_ALPHANUMEXT, 'Outcome id'),
2851 'name' => new external_value(PARAM_TEXT, 'Outcome full name'),
28ff87be 2852 'scale' => new external_value(PARAM_TEXT, 'Scale items')
796876b0
JL
2853 )
2854 ),
2855 'Outcomes information', VALUE_OPTIONAL
2856 ),
c5158499
JL
2857 )
2858 ),
2859 'warnings' => new external_warnings()
2860 )
2861 );
2862 }
2863
13bb6819
JL
2864 /**
2865 * Returns description of method parameters
2866 *
2867 * @return external_function_parameters
2868 * @since Moodle 3.0
2869 */
2870 public static function get_course_module_by_instance_parameters() {
2871 return new external_function_parameters(
2872 array(
2873 'module' => new external_value(PARAM_COMPONENT, 'The module name'),
2874 'instance' => new external_value(PARAM_INT, 'The module instance id')
2875 )
2876 );
2877 }
2878
2879 /**
2880 * Return information about a course module.
2881 *
d30255a0
DM
2882 * @param string $module the module name
2883 * @param int $instance the activity instance id
13bb6819
JL
2884 * @return array of warnings and the course module
2885 * @since Moodle 3.0
2886 * @throws moodle_exception
2887 */
2888 public static function get_course_module_by_instance($module, $instance) {
2889
2890 $params = self::validate_parameters(self::get_course_module_by_instance_parameters(),
2891 array(
2892 'module' => $module,
2893 'instance' => $instance,
2894 ));
2895
2896 $warnings = array();
2897 $cm = get_coursemodule_from_instance($params['module'], $params['instance'], 0, false, MUST_EXIST);
2898
2899 return self::get_course_module($cm->id);
2900 }
2901
2902 /**
2903 * Returns description of method result value
2904 *
2905 * @return external_description
2906 * @since Moodle 3.0
2907 */
2908 public static function get_course_module_by_instance_returns() {
2909 return self::get_course_module_returns();
2910 }
2911
c115ff6a
JL
2912 /**
2913 * Returns description of method parameters
2914 *
2915 * @return external_function_parameters
2916 * @since Moodle 3.2
2917 */
2918 public static function get_user_navigation_options_parameters() {
2919 return new external_function_parameters(
2920 array(
2921 'courseids' => new external_multiple_structure(new external_value(PARAM_INT, 'Course id.')),
2922 )
2923 );
2924 }
2925
2926 /**
2927 * Return a list of navigation options in a set of courses that are avaialable or not for the current user.
2928 *
2929 * @param array $courseids a list of course ids
2930 * @return array of warnings and the options availability
2931 * @since Moodle 3.2
2932 * @throws moodle_exception
2933 */
2934 public static function get_user_navigation_options($courseids) {
2935 global $CFG;
2936 require_once($CFG->dirroot . '/course/lib.php');
2937
2938 // Parameter validation.
2939 $params = self::validate_parameters(self::get_user_navigation_options_parameters(), array('courseids' => $courseids));
2940 $courseoptions = array();
2941
2942 list($courses, $warnings) = external_util::validate_courses($params['courseids'], array(), true);
2943
2944 if (!empty($courses)) {
2945 foreach ($courses as $course) {
2946 // Fix the context for the frontpage.
2947 if ($course->id == SITEID) {
2948 $course->context = context_system::instance();
2949 }
2950 $navoptions = course_get_user_navigation_options($course->context, $course);
2951 $options = array();
2952 foreach ($navoptions as $name => $available) {
2953 $options[] = array(
2954 'name' => $name,
2955 'available' => $available,
2956 );
2957 }
2958
2959 $courseoptions[] = array(
2960 'id' => $course->id,
2961 'options' => $options
2962 );
2963 }
2964 }
2965
2966 $result = array(
2967 'courses' => $courseoptions,
2968 'warnings' => $warnings
2969 );
2970 return $result;
2971 }
2972
2973 /**
2974 * Returns description of method result value
2975 *
2976 * @return external_description
2977 * @since Moodle 3.2
2978 */
2979 public static function get_user_navigation_options_returns() {
2980 return new external_single_structure(
2981 array(
2982 'courses' => new external_multiple_structure(
2983 new external_single_structure(
2984 array(
2985 'id' => new external_value(PARAM_INT, 'Course id'),
2986 'options' => new external_multiple_structure(
2987 new external_single_structure(
2988 array(
2989 'name' => new external_value(PARAM_ALPHANUMEXT, 'Option name'),
2990 'available' => new external_value(PARAM_BOOL, 'Whether the option is available or not'),
2991 )
2992 )
2993 )
2994 )
2995 ), 'List of courses'
2996 ),
2997 'warnings' => new external_warnings()
2998 )
2999 );
3000 }
3001
b9050b10
JL
3002 /**
3003 * Returns description of method parameters
3004 *
3005 * @return external_function_parameters
3006 * @since Moodle 3.2
3007 */
3008 public static function get_user_administration_options_parameters() {
3009 return new external_function_parameters(
3010 array(
3011 'courseids' => new external_multiple_structure(new external_value(PARAM_INT, 'Course id.')),
3012 )
3013 );
3014 }
3015
3016 /**
3017 * Return a list of administration options in a set of courses that are available or not for the current user.
3018 *
3019 * @param array $courseids a list of course ids
3020 * @return array of warnings and the options availability
3021 * @since Moodle 3.2
3022 * @throws moodle_exception
3023 */
3024 public static function get_user_administration_options($courseids) {
3025 global $CFG;
3026 require_once($CFG->dirroot . '/course/lib.php');
3027
3028 // Parameter validation.
3029 $params = self::validate_parameters(self::get_user_administration_options_parameters(), array('courseids' => $courseids));
3030 $courseoptions = array();
3031
3032 list($courses, $warnings) = external_util::validate_courses($params['courseids'], array(), true);
3033
3034 if (!empty($courses)) {
3035 foreach ($courses as $course) {
3036 $adminoptions = course_get_user_administration_options($course, $course->context);
3037 $options = array();
3038 foreach ($adminoptions as $name => $available) {
3039 $options[] = array(
3040 'name' => $name,
3041 'available' => $available,
3042 );
3043 }
3044
3045 $courseoptions[] = array(
3046 'id' => $course->id,
3047 'options' => $options
3048 );
3049 }
3050 }
3051
3052 $result = array(
3053 'courses' => $courseoptions,
3054 'warnings' => $warnings
3055 );
3056 return $result;
3057 }
3058
3059 /**
3060 * Returns description of method result value
3061 *
3062 * @return external_description
3063 * @since Moodle 3.2
3064 */
3065 public static function get_user_administration_options_returns() {
3066 return self::get_user_navigation_options_returns();
3067 }
80adabef
JL
3068
3069 /**
3070 * Returns description of method parameters
3071 *
3072 * @return external_function_parameters
3073 * @since Moodle 3.2
3074 */
3075 public static function get_courses_by_field_parameters() {
3076 return new external_function_parameters(
3077 array(
3078 'field' => new external_value(PARAM_ALPHA, 'The field to search can be left empty for all courses or:
3079 id: course id
3080 ids: comma separated course ids
3081 shortname: course short name
3082 idnumber: course id number
3083 category: category id the course belongs to
3084 ', VALUE_DEFAULT, ''),
3085 'value' => new external_value(PARAM_RAW, 'The value to match', VALUE_DEFAULT, '')
3086 )
3087 );
3088 }
3089
3090
3091 /**
3092 * Get courses matching a specific field (id/s, shortname, idnumber, category)
3093 *
3094 * @param string $field field name to search, or empty for all courses
3095 * @param string $value value to search
3096 * @return array list of courses and warnings
3097 * @throws invalid_parameter_exception
3098 * @since Moodle 3.2
3099 */
3100 public static function get_courses_by_field($field = '', $value = '') {
3101 global $DB, $CFG;
bd4a6a70 3102 require_once($CFG->dirroot . '/course/lib.php');
e45fc71e 3103 require_once($CFG->libdir . '/filterlib.php');
80adabef
JL
3104
3105 $params = self::validate_parameters(self::get_courses_by_field_parameters(),
3106 array(
3107 'field' => $field,
3108 'value' => $value,
3109 )
3110 );
3111 $warnings = array();
3112
3113 if (empty($params['field'])) {
3114 $courses = $DB->get_records('course', null, 'id ASC');
3115 } else {
3116 switch ($params['field']) {
3117 case 'id':
3118 case 'category':
3119 $value = clean_param($params['value'], PARAM_INT);
3120 break;
3121 case 'ids':
3122 $value = clean_param($params['value'], PARAM_SEQUENCE);
3123 break;
3124 case 'shortname':
3125 $value = clean_param($params['value'], PARAM_TEXT);
3126 break;
3127 case 'idnumber':
3128 $value = clean_param($params['value'], PARAM_RAW);
3129 break;
3130 default:
3131 throw new invalid_parameter_exception('Invalid field name');
3132 }
3133
3134 if ($params['field'] === 'ids') {
107ab4b9 3135 // Preload categories to avoid loading one at a time.
3136 $courseids = explode(',', $value);
3137 list ($listsql, $listparams) = $DB->get_in_or_equal($courseids);
3138 $categoryids = $DB->get_fieldset_sql("
3139 SELECT DISTINCT cc.id
3140 FROM {course} c
3141 JOIN {course_categories} cc ON cc.id = c.category
3142 WHERE c.id $listsql", $listparams);
3143 core_course_category::get_many($categoryids);
3144
3145 // Load and validate all courses. This is called because it loads the courses
3146 // more efficiently.
3147 list ($courses, $warnings) = external_util::validate_courses($courseids, [],
3148 false, true);
80adabef
JL
3149 } else {
3150 $courses = $DB->get_records('course', array($params['field'] => $value), 'id ASC');
3151 }
3152 }
3153
3154 $coursesdata = array();
3155 foreach ($courses as $course) {
3156 $context = context_course::instance($course->id);
3157 $canupdatecourse = has_capability('moodle/course:update', $context);
3158 $canviewhiddencourses = has_capability('moodle/course:viewhiddencourses', $context);
3159
3160 // Check if the course is visible in the site for the user.
3161 if (!$course->visible and !$canviewhiddencourses and !$canupdatecourse) {
3162 continue;
3163 }
3164 // Get the public course information, even if we are not enrolled.
442f12f8 3165 $courseinlist = new core_course_list_element($course);
80adabef 3166
107ab4b9 3167 // Now, check if we have access to the course, unless it was already checked.
80adabef 3168 try {
107ab4b9 3169 if (empty($course->contextvalidated)) {
3170 self::validate_context($context);
3171 }
80adabef 3172 } catch (Exception $e) {
beff3806
MG
3173 // User can not access the course, check if they can see the public information about the course and return it.
3174 if (core_course_category::can_view_course_info($course)) {
3175 $coursesdata[$course->id] = self::get_course_public_information($courseinlist, $context);
3176 }
80adabef
JL
3177 continue;
3178 }
beff3806 3179 $coursesdata[$course->id] = self::get_course_public_information($courseinlist, $context);
80adabef 3180 // Return information for any user that can access the course.
ef83fc2a 3181 $coursefields = array('format', 'showgrades', 'newsitems', 'startdate', 'enddate', 'maxbytes', 'showreports', 'visible',
80adabef 3182 'groupmode', 'groupmodeforce', 'defaultgroupingid', 'enablecompletion', 'completionnotify', 'lang', 'theme',
fb41d2f0 3183 'marker');
80adabef 3184
e45fc71e
JL
3185 // Course filters.
3186 $coursesdata[$course->id]['filters'] = filter_get_available_in_context($context);
3187
80adabef
JL
3188 // Information for managers only.
3189 if ($canupdatecourse) {
3190 $managerfields = array('idnumber', 'legacyfiles', 'calendartype', 'timecreated', 'timemodified', 'requested',
3191 'cacherev');
3192 $coursefields = array_merge($coursefields, $managerfields);
3193 }
3194
3195 // Populate fields.
3196 foreach ($coursefields as $field) {
3197 $coursesdata[$course->id][$field] = $course->{$field};
3198 }
6db24235
JL
3199
3200 // Clean lang and auth fields for external functions (it may content uninstalled themes or language packs).
3201 if (isset($coursesdata[$course->id]['theme'])) {
3202 $coursesdata[$course->id]['theme'] = clean_param($coursesdata[$course->id]['theme'], PARAM_THEME);
3203 }
3204 if (isset($coursesdata[$course->id]['lang'])) {
3205 $coursesdata[$course->id]['lang'] = clean_param($coursesdata[$course->id]['lang'], PARAM_LANG);
3206 }
cf58a2d5
JL
3207
3208 $courseformatoptions = course_get_format($course)->get_config_for_external();
3209 foreach ($courseformatoptions as $key => $value) {
3210 $coursesdata[$course->id]['courseformatoptions'][] = array(
3211 'name' => $key,
3212 'value' => $value
3213 );
3214 }
80adabef
JL
3215 }
3216
3217 return array(
3218 'courses' => $coursesdata,
3219 'warnings' => $warnings
3220 );
3221 }
3222
3223 /**
3224 * Returns description of method result value
3225 *
3226 * @return external_description
3227 * @since Moodle 3.2
3228 */
3229 public static function get_courses_by_field_returns() {
3230 // Course structure, including not only public viewable fields.
3231 return new external_single_structure(
3232 array(
3233 'courses' => new external_multiple_structure(self::get_course_structure(false), 'Course'),
3234 'warnings' => new external_warnings()
3235 )
3236 );
3237 }
26659f62
JL
3238
3239 /**
3240 * Returns description of method parameters
3241 *
3242 * @return external_function_parameters
3243 * @since Moodle 3.2
3244 */
3245 public static function check_updates_parameters() {
3246 return new external_function_parameters(
3247 array(
3248 'courseid' => new external_value(PARAM_INT, 'Course id to check'),
3249 'tocheck' => new external_multiple_structure(
3250 new external_single_structure(
3251 array(
3252 'contextlevel' => new external_value(PARAM_ALPHA, 'The context level for the file location.
3253 Only module supported right now.'),
3254 'id' => new external_value(PARAM_INT, 'Context instance id'),
3255 'since' => new external_value(PARAM_INT, 'Check updates since this time stamp'),
3256 )
3257 ),
3258 'Instances to check'
3259 ),
3260 'filter' => new external_multiple_structure(
25adfbaa 3261 new external_value(PARAM_ALPHANUM, 'Area name: configuration, fileareas, completion, ratings, comments,
26659f62
JL
3262 gradeitems, outcomes'),
3263 'Check only for updates in these areas', VALUE_DEFAULT, array()
3264 )
3265 )
3266 );
3267 }
3268
3269 /**
3270 * Check if there is updates affecting the user for the given course and contexts.
3271 * Right now only modules are supported.
3272 * This WS calls mod_check_updates_since for each module to check if there is any update the user should we aware of.
26659f62
JL
3273 *
3274 * @param int $courseid the list of modules to check
3275 * @param array $tocheck the list of modules to check
3276 * @param array $filter check only for updates in these areas
3277 * @return array list of updates and warnings
3278 * @throws moodle_exception
3279 * @since Moodle 3.2
3280 */
3281 public static function check_updates($courseid, $tocheck, $filter = array()) {
3282 global $CFG, $DB;
cd2115fa 3283 require_once($CFG->dirroot . "/course/lib.php");
26659f62
JL
3284
3285 $params = self::validate_parameters(
3286 self::check_updates_parameters(),
3287 array(
3288 'courseid' => $courseid,
3289 'tocheck' => $tocheck,
3290 'filter' => $filter,
3291 )
3292 );
3293
3294 $course = get_course($params['courseid']);
3295 $context = context_course::instance($course->id);
3296 self::validate_context($context);
3297
3298 list($instances, $warnings) = course_check_updates($course, $params['tocheck'], $filter);
3299
3300 $instancesformatted = array();
3301 foreach ($instances as $instance) {
3302 $updates = array();
25adfbaa
JL
3303 foreach ($instance['updates'] as $name => $data) {
3304 if (empty($data->updated)) {
3305 continue;
3306 }
3307 $updatedata = array(
26659f62 3308 'name' => $name,
25adfbaa
JL
3309 );
3310 if (!empty($data->timeupdated)) {
3311 $updatedata['timeupdated'] = $data->timeupdated;
3312 }
3313 if (!empty($data->itemids)) {
3314 $updatedata['itemids'] = $data->itemids;
3315 }
3316 $updates[] = $updatedata;
3317 }
3318 if (!empty($updates)) {
3319 $instancesformatted[] = array(
3320 'contextlevel' => $instance['contextlevel'],
3321 'id' => $instance['id'],
3322 'updates' => $updates
26659f62
JL
3323 );
3324 }
26659f62
JL
3325 }
3326
3327 return array(
3328 'instances' => $instancesformatted,
3329 'warnings' => $warnings
3330 );
3331 }
3332
3333 /**
3334 * Returns description of method result value
3335 *
3336 * @return external_description
3337 * @since Moodle 3.2
3338 */
3339 public static function check_updates_returns() {
3340 return new external_single_structure(
3341 array(
3342 'instances' => new external_multiple_structure(
3343 new external_single_structure(
3344 array(
3345 'contextlevel' => new external_value(PARAM_ALPHA, 'The context level'),
3346 'id' => new external_value(PARAM_INT, 'Instance id'),
3347 'updates' => new external_multiple_structure(
3348 new external_single_structure(
3349 array(
3350 'name' => new external_value(PARAM_ALPHANUMEXT, 'Name of the area updated.'),
25adfbaa
JL
3351 'timeupdated' => new external_value(PARAM_INT, 'Last time was updated', VALUE_OPTIONAL),
3352 'itemids' => new external_multiple_structure(
3353 new external_value(PARAM_INT, 'Instance id'),
3354 'The ids of the items updated',
3355 VALUE_OPTIONAL
3356 )
26659f62
JL
3357 )
3358 )
3359 )
3360 )
3361 )
3362 ),
3363 'warnings' => new external_warnings()
3364 )
3365 );
3366 }
879a8f56
JL
3367
3368 /**
3369 * Returns description of method parameters
3370 *
3371 * @return external_function_parameters
3372 * @since Moodle 3.3
3373 */
3374 public static function get_updates_since_parameters() {
3375 return new external_function_parameters(
3376 array(
3377 'courseid' => new external_value(PARAM_INT, 'Course id to check'),
3378 'since' => new external_value(PARAM_INT, 'Check updates since this time stamp'),
3379 'filter' => new external_multiple_structure(
3380 new external_value(PARAM_ALPHANUM, 'Area name: configuration, fileareas, completion, ratings, comments,
3381 gradeitems, outcomes'),
3382 'Check only for updates in these areas', VALUE_DEFAULT, array()
3383 )
3384 )
3385 );
3386 }
3387
3388 /**
3389 * Check if there are updates affecting the user for the given course since the given time stamp.
3390 *
3391 * This function is a wrapper of self::check_updates for retrieving all the updates since a given time for all the activities.
3392 *
3393 * @param int $courseid the list of modules to check
3394 * @param int $since check updates since this time stamp
3395 * @param array $filter check only for updates in these areas
3396 * @return array list of updates and warnings
3397 * @throws moodle_exception
3398 * @since Moodle 3.3
3399 */
3400 public static function get_updates_since($courseid, $since, $filter = array()) {
3401 global $CFG, $DB;
3402
3403 $params = self::validate_parameters(
3404 self::get_updates_since_parameters(),
3405 array(
3406 'courseid' => $courseid,
3407 'since' => $since,
3408 'filter' => $filter,