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