MDL-69625 course: return raw custom field value in external method.
[moodle.git] / course / externallib.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
18 /**
19  * External course API
20  *
21  * @package    core_course
22  * @category   external
23  * @copyright  2009 Petr Skodak
24  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25  */
27 defined('MOODLE_INTERNAL') || die;
29 use core_course\external\course_summary_exporter;
31 require_once("$CFG->libdir/externallib.php");
32 require_once("lib.php");
34 /**
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
42  */
43 class core_course_external extends external_api {
45     /**
46      * Returns description of method parameters
47      *
48      * @return external_function_parameters
49      * @since Moodle 2.9 Options available
50      * @since Moodle 2.2
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(
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)
62                                                 includestealthmodules (bool) Return stealth modules for students in a special
63                                                     section (with id -1)
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.')
71                               )
72                       ), 'Options, used since Moodle 2.9', VALUE_DEFAULT, array())
73                 )
74         );
75     }
77     /**
78      * Get course contents
79      *
80      * @param int $courseid course id
81      * @param array $options Options for filtering the results, used since Moodle 2.9
82      * @return array
83      * @since Moodle 2.9 Options available
84      * @since Moodle 2.2
85      */
86     public static function get_course_contents($courseid, $options = array()) {
87         global $CFG, $DB;
88         require_once($CFG->dirroot . "/course/lib.php");
89         require_once($CFG->libdir . '/completionlib.php');
91         //validate parameter
92         $params = self::validate_parameters(self::get_course_contents_parameters(),
93                         array('courseid' => $courseid, 'options' => $options));
95         $filters = array();
96         if (!empty($params['options'])) {
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':
105                         case 'includestealthmodules':
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         }
135         //retrieve the course
136         $course = $DB->get_record('course', array('id' => $params['courseid']), '*', MUST_EXIST);
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             }
146         }
148         // now security checks
149         $context = context_course::instance($course->id, IGNORE_MISSING);
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         }
159         $canupdatecourse = has_capability('moodle/course:update', $context);
161         //create return value
162         $coursecontents = array();
164         if ($canupdatecourse or $course->visible
165                 or has_capability('moodle/course:viewhiddencourses', $context)) {
167             //retrieve sections
168             $modinfo = get_fast_modinfo($course);
169             $sections = $modinfo->get_section_info_all();
170             $coursenumsections = course_get_format($course)->get_last_section_number();
171             $stealthmodules = array();   // Array to keep all the modules available but not visible in a course section/topic.
173             $completioninfo = new completion_info($course);
175             //for each sections (first displayed to last displayed)
176             $modinfosections = $modinfo->get_sections();
177             foreach ($sections as $key => $section) {
179                 // This becomes true when we are filtering and we found the value to filter with.
180                 $sectionfound = false;
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                 }
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                 }
200                 // reset $sectioncontents
201                 $sectionvalues = array();
202                 $sectionvalues['id'] = $section->id;
203                 $sectionvalues['name'] = get_section_name($course, $section);
204                 $sectionvalues['visible'] = $section->visible;
206                 $options = (object) array('noclean' => true);
207                 list($sectionvalues['summary'], $sectionvalues['summaryformat']) =
208                         external_format_text($section->summary, $section->summaryformat,
209                                 $context->id, 'course', 'section', $section->id, $options);
210                 $sectionvalues['section'] = $section->section;
211                 $sectionvalues['hiddenbynumsections'] = $section->section > $coursenumsections ? 1 : 0;
212                 $sectionvalues['uservisible'] = $section->uservisible;
213                 if (!empty($section->availableinfo)) {
214                     $sectionvalues['availabilityinfo'] = \core_availability\info::format_info($section->availableinfo, $course);
215                 }
217                 $sectioncontents = array();
219                 // For each module of the section.
220                 if (empty($filters['excludemodules']) and !empty($modinfosections[$section->section])) {
221                     foreach ($modinfosections[$section->section] as $cmid) {
222                         $cm = $modinfo->cms[$cmid];
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()) {
227                             continue;
228                         }
230                         // This becomes true when we are filtering and we found the value to filter with.
231                         $modfound = false;
233                         // Filter by cmid.
234                         if (!empty($filters['cmid'])) {
235                             if ($cmid != $filters['cmid']) {
236                                 continue;
237                             } else {
238                                 $modfound = true;
239                             }
240                         }
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                         }
256                         $module = array();
258                         $modcontext = context_module::instance($cm->id);
260                         //common info (for people being able to see the module or availability dates)
261                         $module['id'] = $cm->id;
262                         $module['name'] = external_format_string($cm->name, $modcontext->id);
263                         $module['instance'] = $cm->instance;
264                         $module['contextid'] = $modcontext->id;
265                         $module['modname'] = (string) $cm->modname;
266                         $module['modplural'] = (string) $cm->modplural;
267                         $module['modicon'] = $cm->get_icon_url()->out(false);
268                         $module['indent'] = $cm->indent;
269                         $module['onclick'] = $cm->onclick;
270                         $module['afterlink'] = $cm->afterlink;
271                         $module['customdata'] = json_encode($cm->customdata);
272                         $module['completion'] = $cm->completion;
273                         $module['noviewlink'] = plugin_supports('mod', $cm->modname, FEATURE_NO_VIEW_LINK, false);
275                         // Check module completion.
276                         $completion = $completioninfo->is_enabled($cm);
277                         if ($completion != COMPLETION_DISABLED) {
278                             $completiondata = $completioninfo->get_data($cm, true);
279                             $module['completiondata'] = array(
280                                 'state'         => $completiondata->completionstate,
281                                 'timecompleted' => $completiondata->timemodified,
282                                 'overrideby'    => $completiondata->overrideby,
283                                 'valueused'     => core_availability\info::completion_value_used($course, $cm->id)
284                             );
285                         }
287                         if (!empty($cm->showdescription) or $module['noviewlink']) {
288                             // We want to use the external format. However from reading get_formatted_content(), $cm->content format is always FORMAT_HTML.
289                             $options = array('noclean' => true);
290                             list($module['description'], $descriptionformat) = external_format_text($cm->content,
291                                 FORMAT_HTML, $modcontext->id, $cm->modname, 'intro', $cm->id, $options);
292                         }
294                         //url of the module
295                         $url = $cm->url;
296                         if ($url) { //labels don't have url
297                             $module['url'] = $url->out(false);
298                         }
300                         $canviewhidden = has_capability('moodle/course:viewhiddenactivities',
301                                             context_module::instance($cm->id));
302                         //user that can view hidden module should know about the visibility
303                         $module['visible'] = $cm->visible;
304                         $module['visibleoncoursepage'] = $cm->visibleoncoursepage;
305                         $module['uservisible'] = $cm->uservisible;
306                         if (!empty($cm->availableinfo)) {
307                             $module['availabilityinfo'] = \core_availability\info::format_info($cm->availableinfo, $course);
308                         }
310                         // Availability date (also send to user who can see hidden module).
311                         if ($CFG->enableavailability && ($canviewhidden || $canupdatecourse)) {
312                             $module['availability'] = $cm->availability;
313                         }
315                         // Return contents only if the user can access to the module.
316                         if ($cm->uservisible) {
317                             $baseurl = 'webservice/pluginfile.php';
319                             // Call $modulename_export_contents (each module callback take care about checking the capabilities).
320                             require_once($CFG->dirroot . '/mod/' . $cm->modname . '/lib.php');
321                             $getcontentfunction = $cm->modname.'_export_contents';
322                             if (function_exists($getcontentfunction)) {
323                                 $contents = $getcontentfunction($cm, $baseurl);
324                                 $module['contentsinfo'] = array(
325                                     'filescount' => count($contents),
326                                     'filessize' => 0,
327                                     'lastmodified' => 0,
328                                     'mimetypes' => array(),
329                                 );
330                                 foreach ($contents as $content) {
331                                     // Check repository file (only main file).
332                                     if (!isset($module['contentsinfo']['repositorytype'])) {
333                                         $module['contentsinfo']['repositorytype'] =
334                                             isset($content['repositorytype']) ? $content['repositorytype'] : '';
335                                     }
336                                     if (isset($content['filesize'])) {
337                                         $module['contentsinfo']['filessize'] += $content['filesize'];
338                                     }
339                                     if (isset($content['timemodified']) &&
340                                             ($content['timemodified'] > $module['contentsinfo']['lastmodified'])) {
342                                         $module['contentsinfo']['lastmodified'] = $content['timemodified'];
343                                     }
344                                     if (isset($content['mimetype'])) {
345                                         $module['contentsinfo']['mimetypes'][$content['mimetype']] = $content['mimetype'];
346                                     }
347                                 }
349                                 if (empty($filters['excludecontents']) and !empty($contents)) {
350                                     $module['contents'] = $contents;
351                                 } else {
352                                     $module['contents'] = array();
353                                 }
354                             }
355                         }
357                         // Assign result to $sectioncontents, there is an exception,
358                         // stealth activities in non-visible sections for students go to a special section.
359                         if (!empty($filters['includestealthmodules']) && !$section->uservisible && $cm->is_stealth()) {
360                             $stealthmodules[] = $module;
361                         } else {
362                             $sectioncontents[] = $module;
363                         }
365                         // If we just did a filtering, break the loop.
366                         if ($modfound) {
367                             break;
368                         }
370                     }
371                 }
372                 $sectionvalues['modules'] = $sectioncontents;
374                 // assign result to $coursecontents
375                 $coursecontents[$key] = $sectionvalues;
377                 // Break the loop if we are filtering.
378                 if ($sectionfound) {
379                     break;
380                 }
381             }
383             // Now that we have iterated over all the sections and activities, check the visibility.
384             // We didn't this before to be able to retrieve stealth activities.
385             foreach ($coursecontents as $sectionnumber => $sectioncontents) {
386                 $section = $sections[$sectionnumber];
387                 // Show the section if the user is permitted to access it, OR if it's not available
388                 // but there is some available info text which explains the reason & should display.
389                 $showsection = $section->uservisible ||
390                     ($section->visible && !$section->available &&
391                     !empty($section->availableinfo));
393                 if (!$showsection) {
394                     unset($coursecontents[$sectionnumber]);
395                     continue;
396                 }
398                 // Remove modules information if the section is not visible for the user.
399                 if (!$section->uservisible) {
400                     $coursecontents[$sectionnumber]['modules'] = array();
401                 }
402             }
404             // Include stealth modules in special section (without any info).
405             if (!empty($stealthmodules)) {
406                 $coursecontents[] = array(
407                     'id' => -1,
408                     'name' => '',
409                     'summary' => '',
410                     'summaryformat' => FORMAT_MOODLE,
411                     'modules' => $stealthmodules
412                 );
413             }
415         }
416         return $coursecontents;
417     }
419     /**
420      * Returns description of method result value
421      *
422      * @return external_description
423      * @since Moodle 2.2
424      */
425     public static function get_course_contents_returns() {
426         return new external_multiple_structure(
427             new external_single_structure(
428                 array(
429                     'id' => new external_value(PARAM_INT, 'Section ID'),
430                     'name' => new external_value(PARAM_RAW, 'Section name'),
431                     'visible' => new external_value(PARAM_INT, 'is the section visible', VALUE_OPTIONAL),
432                     'summary' => new external_value(PARAM_RAW, 'Section description'),
433                     'summaryformat' => new external_format_value('summary'),
434                     'section' => new external_value(PARAM_INT, 'Section number inside the course', VALUE_OPTIONAL),
435                     'hiddenbynumsections' => new external_value(PARAM_INT, 'Whether is a section hidden in the course format',
436                                                                 VALUE_OPTIONAL),
437                     'uservisible' => new external_value(PARAM_BOOL, 'Is the section visible for the user?', VALUE_OPTIONAL),
438                     'availabilityinfo' => new external_value(PARAM_RAW, 'Availability information.', VALUE_OPTIONAL),
439                     'modules' => new external_multiple_structure(
440                             new external_single_structure(
441                                 array(
442                                     'id' => new external_value(PARAM_INT, 'activity id'),
443                                     'url' => new external_value(PARAM_URL, 'activity url', VALUE_OPTIONAL),
444                                     'name' => new external_value(PARAM_RAW, 'activity module name'),
445                                     'instance' => new external_value(PARAM_INT, 'instance id', VALUE_OPTIONAL),
446                                     'contextid' => new external_value(PARAM_INT, 'Activity context id.', VALUE_OPTIONAL),
447                                     'description' => new external_value(PARAM_RAW, 'activity description', VALUE_OPTIONAL),
448                                     'visible' => new external_value(PARAM_INT, 'is the module visible', VALUE_OPTIONAL),
449                                     'uservisible' => new external_value(PARAM_BOOL, 'Is the module visible for the user?',
450                                         VALUE_OPTIONAL),
451                                     'availabilityinfo' => new external_value(PARAM_RAW, 'Availability information.',
452                                         VALUE_OPTIONAL),
453                                     'visibleoncoursepage' => new external_value(PARAM_INT, 'is the module visible on course page',
454                                         VALUE_OPTIONAL),
455                                     'modicon' => new external_value(PARAM_URL, 'activity icon url'),
456                                     'modname' => new external_value(PARAM_PLUGIN, 'activity module type'),
457                                     'modplural' => new external_value(PARAM_TEXT, 'activity module plural name'),
458                                     'availability' => new external_value(PARAM_RAW, 'module availability settings', VALUE_OPTIONAL),
459                                     'indent' => new external_value(PARAM_INT, 'number of identation in the site'),
460                                     'onclick' => new external_value(PARAM_RAW, 'Onclick action.', VALUE_OPTIONAL),
461                                     'afterlink' => new external_value(PARAM_RAW, 'After link info to be displayed.',
462                                         VALUE_OPTIONAL),
463                                     'customdata' => new external_value(PARAM_RAW, 'Custom data (JSON encoded).', VALUE_OPTIONAL),
464                                     'noviewlink' => new external_value(PARAM_BOOL, 'Whether the module has no view page',
465                                         VALUE_OPTIONAL),
466                                     'completion' => new external_value(PARAM_INT, 'Type of completion tracking:
467                                         0 means none, 1 manual, 2 automatic.', VALUE_OPTIONAL),
468                                     'completiondata' => new external_single_structure(
469                                         array(
470                                             'state' => new external_value(PARAM_INT, 'Completion state value:
471                                                 0 means incomplete, 1 complete, 2 complete pass, 3 complete fail'),
472                                             'timecompleted' => new external_value(PARAM_INT, 'Timestamp for completion status.'),
473                                             'overrideby' => new external_value(PARAM_INT, 'The user id who has overriden the
474                                                 status.'),
475                                             'valueused' => new external_value(PARAM_BOOL, 'Whether the completion status affects
476                                                 the availability of another activity.', VALUE_OPTIONAL),
477                                         ), 'Module completion data.', VALUE_OPTIONAL
478                                     ),
479                                     'contents' => new external_multiple_structure(
480                                           new external_single_structure(
481                                               array(
482                                                   // content info
483                                                   'type'=> new external_value(PARAM_TEXT, 'a file or a folder or external link'),
484                                                   'filename'=> new external_value(PARAM_FILE, 'filename'),
485                                                   'filepath'=> new external_value(PARAM_PATH, 'filepath'),
486                                                   'filesize'=> new external_value(PARAM_INT, 'filesize'),
487                                                   'fileurl' => new external_value(PARAM_URL, 'downloadable file url', VALUE_OPTIONAL),
488                                                   'content' => new external_value(PARAM_RAW, 'Raw content, will be used when type is content', VALUE_OPTIONAL),
489                                                   'timecreated' => new external_value(PARAM_INT, 'Time created'),
490                                                   'timemodified' => new external_value(PARAM_INT, 'Time modified'),
491                                                   'sortorder' => new external_value(PARAM_INT, 'Content sort order'),
492                                                   'mimetype' => new external_value(PARAM_RAW, 'File mime type.', VALUE_OPTIONAL),
493                                                   'isexternalfile' => new external_value(PARAM_BOOL, 'Whether is an external file.',
494                                                     VALUE_OPTIONAL),
495                                                   'repositorytype' => new external_value(PARAM_PLUGIN, 'The repository type for external files.',
496                                                     VALUE_OPTIONAL),
498                                                   // copyright related info
499                                                   'userid' => new external_value(PARAM_INT, 'User who added this content to moodle'),
500                                                   'author' => new external_value(PARAM_TEXT, 'Content owner'),
501                                                   'license' => new external_value(PARAM_TEXT, 'Content license'),
502                                                   'tags' => new external_multiple_structure(
503                                                        \core_tag\external\tag_item_exporter::get_read_structure(), 'Tags',
504                                                             VALUE_OPTIONAL
505                                                    ),
506                                               )
507                                           ), VALUE_DEFAULT, array()
508                                       ),
509                                     'contentsinfo' => new external_single_structure(
510                                         array(
511                                             'filescount' => new external_value(PARAM_INT, 'Total number of files.'),
512                                             'filessize' => new external_value(PARAM_INT, 'Total files size.'),
513                                             'lastmodified' => new external_value(PARAM_INT, 'Last time files were modified.'),
514                                             'mimetypes' => new external_multiple_structure(
515                                                 new external_value(PARAM_RAW, 'File mime type.'),
516                                                 'Files mime types.'
517                                             ),
518                                             'repositorytype' => new external_value(PARAM_PLUGIN, 'The repository type for
519                                                 the main file.', VALUE_OPTIONAL),
520                                         ), 'Contents summary information.', VALUE_OPTIONAL
521                                     ),
522                                 )
523                             ), 'list of module'
524                     )
525                 )
526             )
527         );
528     }
530     /**
531      * Returns description of method parameters
532      *
533      * @return external_function_parameters
534      * @since Moodle 2.3
535      */
536     public static function get_courses_parameters() {
537         return new external_function_parameters(
538                 array('options' => new external_single_structure(
539                             array('ids' => new external_multiple_structure(
540                                         new external_value(PARAM_INT, 'Course id')
541                                         , 'List of course id. If empty return all courses
542                                             except front page course.',
543                                         VALUE_OPTIONAL)
544                             ), 'options - operator OR is used', VALUE_DEFAULT, array())
545                 )
546         );
547     }
549     /**
550      * Get courses
551      *
552      * @param array $options It contains an array (list of ids)
553      * @return array
554      * @since Moodle 2.2
555      */
556     public static function get_courses($options = array()) {
557         global $CFG, $DB;
558         require_once($CFG->dirroot . "/course/lib.php");
560         //validate parameter
561         $params = self::validate_parameters(self::get_courses_parameters(),
562                         array('options' => $options));
564         //retrieve courses
565         if (!array_key_exists('ids', $params['options'])
566                 or empty($params['options']['ids'])) {
567             $courses = $DB->get_records('course');
568         } else {
569             $courses = $DB->get_records_list('course', 'id', $params['options']['ids']);
570         }
572         //create return value
573         $coursesinfo = array();
574         foreach ($courses as $course) {
576             // now security checks
577             $context = context_course::instance($course->id, IGNORE_MISSING);
578             $courseformatoptions = course_get_format($course)->get_format_options();
579             try {
580                 self::validate_context($context);
581             } catch (Exception $e) {
582                 $exceptionparam = new stdClass();
583                 $exceptionparam->message = $e->getMessage();
584                 $exceptionparam->courseid = $course->id;
585                 throw new moodle_exception('errorcoursecontextnotvalid', 'webservice', '', $exceptionparam);
586             }
587             if ($course->id != SITEID) {
588                 require_capability('moodle/course:view', $context);
589             }
591             $courseinfo = array();
592             $courseinfo['id'] = $course->id;
593             $courseinfo['fullname'] = external_format_string($course->fullname, $context->id);
594             $courseinfo['shortname'] = external_format_string($course->shortname, $context->id);
595             $courseinfo['displayname'] = external_format_string(get_course_display_name_for_list($course), $context->id);
596             $courseinfo['categoryid'] = $course->category;
597             list($courseinfo['summary'], $courseinfo['summaryformat']) =
598                 external_format_text($course->summary, $course->summaryformat, $context->id, 'course', 'summary', 0);
599             $courseinfo['format'] = $course->format;
600             $courseinfo['startdate'] = $course->startdate;
601             $courseinfo['enddate'] = $course->enddate;
602             if (array_key_exists('numsections', $courseformatoptions)) {
603                 // For backward-compartibility
604                 $courseinfo['numsections'] = $courseformatoptions['numsections'];
605             }
607             $handler = core_course\customfield\course_handler::create();
608             if ($customfields = $handler->export_instance_data($course->id)) {
609                 $courseinfo['customfields'] = [];
610                 foreach ($customfields as $data) {
611                     $courseinfo['customfields'][] = [
612                         'type' => $data->get_type(),
613                         'value' => $data->get_value(),
614                         'valueraw' => $data->get_data_controller()->get_value(),
615                         'name' => $data->get_name(),
616                         'shortname' => $data->get_shortname()
617                     ];
618                 }
619             }
621             //some field should be returned only if the user has update permission
622             $courseadmin = has_capability('moodle/course:update', $context);
623             if ($courseadmin) {
624                 $courseinfo['categorysortorder'] = $course->sortorder;
625                 $courseinfo['idnumber'] = $course->idnumber;
626                 $courseinfo['showgrades'] = $course->showgrades;
627                 $courseinfo['showreports'] = $course->showreports;
628                 $courseinfo['newsitems'] = $course->newsitems;
629                 $courseinfo['visible'] = $course->visible;
630                 $courseinfo['maxbytes'] = $course->maxbytes;
631                 if (array_key_exists('hiddensections', $courseformatoptions)) {
632                     // For backward-compartibility
633                     $courseinfo['hiddensections'] = $courseformatoptions['hiddensections'];
634                 }
635                 // Return numsections for backward-compatibility with clients who expect it.
636                 $courseinfo['numsections'] = course_get_format($course)->get_last_section_number();
637                 $courseinfo['groupmode'] = $course->groupmode;
638                 $courseinfo['groupmodeforce'] = $course->groupmodeforce;
639                 $courseinfo['defaultgroupingid'] = $course->defaultgroupingid;
640                 $courseinfo['lang'] = clean_param($course->lang, PARAM_LANG);
641                 $courseinfo['timecreated'] = $course->timecreated;
642                 $courseinfo['timemodified'] = $course->timemodified;
643                 $courseinfo['forcetheme'] = clean_param($course->theme, PARAM_THEME);
644                 $courseinfo['enablecompletion'] = $course->enablecompletion;
645                 $courseinfo['completionnotify'] = $course->completionnotify;
646                 $courseinfo['courseformatoptions'] = array();
647                 foreach ($courseformatoptions as $key => $value) {
648                     $courseinfo['courseformatoptions'][] = array(
649                         'name' => $key,
650                         'value' => $value
651                     );
652                 }
653             }
655             if ($courseadmin or $course->visible
656                     or has_capability('moodle/course:viewhiddencourses', $context)) {
657                 $coursesinfo[] = $courseinfo;
658             }
659         }
661         return $coursesinfo;
662     }
664     /**
665      * Returns description of method result value
666      *
667      * @return external_description
668      * @since Moodle 2.2
669      */
670     public static function get_courses_returns() {
671         return new external_multiple_structure(
672                 new external_single_structure(
673                         array(
674                             'id' => new external_value(PARAM_INT, 'course id'),
675                             'shortname' => new external_value(PARAM_RAW, 'course short name'),
676                             'categoryid' => new external_value(PARAM_INT, 'category id'),
677                             'categorysortorder' => new external_value(PARAM_INT,
678                                     'sort order into the category', VALUE_OPTIONAL),
679                             'fullname' => new external_value(PARAM_RAW, 'full name'),
680                             'displayname' => new external_value(PARAM_RAW, 'course display name'),
681                             'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
682                             'summary' => new external_value(PARAM_RAW, 'summary'),
683                             'summaryformat' => new external_format_value('summary'),
684                             'format' => new external_value(PARAM_PLUGIN,
685                                     'course format: weeks, topics, social, site,..'),
686                             'showgrades' => new external_value(PARAM_INT,
687                                     '1 if grades are shown, otherwise 0', VALUE_OPTIONAL),
688                             'newsitems' => new external_value(PARAM_INT,
689                                     'number of recent items appearing on the course page', VALUE_OPTIONAL),
690                             'startdate' => new external_value(PARAM_INT,
691                                     'timestamp when the course start'),
692                             'enddate' => new external_value(PARAM_INT,
693                                     'timestamp when the course end'),
694                             'numsections' => new external_value(PARAM_INT,
695                                     '(deprecated, use courseformatoptions) number of weeks/topics',
696                                     VALUE_OPTIONAL),
697                             'maxbytes' => new external_value(PARAM_INT,
698                                     'largest size of file that can be uploaded into the course',
699                                     VALUE_OPTIONAL),
700                             'showreports' => new external_value(PARAM_INT,
701                                     'are activity report shown (yes = 1, no =0)', VALUE_OPTIONAL),
702                             'visible' => new external_value(PARAM_INT,
703                                     '1: available to student, 0:not available', VALUE_OPTIONAL),
704                             'hiddensections' => new external_value(PARAM_INT,
705                                     '(deprecated, use courseformatoptions) How the hidden sections in the course are displayed to students',
706                                     VALUE_OPTIONAL),
707                             'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible',
708                                     VALUE_OPTIONAL),
709                             'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no',
710                                     VALUE_OPTIONAL),
711                             'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id',
712                                     VALUE_OPTIONAL),
713                             'timecreated' => new external_value(PARAM_INT,
714                                     'timestamp when the course have been created', VALUE_OPTIONAL),
715                             'timemodified' => new external_value(PARAM_INT,
716                                     'timestamp when the course have been modified', VALUE_OPTIONAL),
717                             'enablecompletion' => new external_value(PARAM_INT,
718                                     'Enabled, control via completion and activity settings. Disbaled,
719                                         not shown in activity settings.',
720                                     VALUE_OPTIONAL),
721                             'completionnotify' => new external_value(PARAM_INT,
722                                     '1: yes 0: no', VALUE_OPTIONAL),
723                             'lang' => new external_value(PARAM_SAFEDIR,
724                                     'forced course language', VALUE_OPTIONAL),
725                             'forcetheme' => new external_value(PARAM_PLUGIN,
726                                     'name of the force theme', VALUE_OPTIONAL),
727                             'courseformatoptions' => new external_multiple_structure(
728                                 new external_single_structure(
729                                     array('name' => new external_value(PARAM_ALPHANUMEXT, 'course format option name'),
730                                         'value' => new external_value(PARAM_RAW, 'course format option value')
731                                 )), 'additional options for particular course format', VALUE_OPTIONAL
732                              ),
733                             'customfields' => new external_multiple_structure(
734                                 new external_single_structure(
735                                     ['name' => new external_value(PARAM_RAW, 'The name of the custom field'),
736                                      'shortname' => new external_value(PARAM_ALPHANUMEXT, 'The shortname of the custom field'),
737                                      'type'  => new external_value(PARAM_COMPONENT,
738                                          'The type of the custom field - text, checkbox...'),
739                                      'valueraw' => new external_value(PARAM_RAW, 'The raw value of the custom field'),
740                                      'value' => new external_value(PARAM_RAW, 'The value of the custom field')]
741                                 ), 'Custom fields and associated values', VALUE_OPTIONAL),
742                         ), 'course'
743                 )
744         );
745     }
747     /**
748      * Returns description of method parameters
749      *
750      * @return external_function_parameters
751      * @since Moodle 2.2
752      */
753     public static function create_courses_parameters() {
754         $courseconfig = get_config('moodlecourse'); //needed for many default values
755         return new external_function_parameters(
756             array(
757                 'courses' => new external_multiple_structure(
758                     new external_single_structure(
759                         array(
760                             'fullname' => new external_value(PARAM_TEXT, 'full name'),
761                             'shortname' => new external_value(PARAM_TEXT, 'course short name'),
762                             'categoryid' => new external_value(PARAM_INT, 'category id'),
763                             'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
764                             'summary' => new external_value(PARAM_RAW, 'summary', VALUE_OPTIONAL),
765                             'summaryformat' => new external_format_value('summary', VALUE_DEFAULT),
766                             'format' => new external_value(PARAM_PLUGIN,
767                                     'course format: weeks, topics, social, site,..',
768                                     VALUE_DEFAULT, $courseconfig->format),
769                             'showgrades' => new external_value(PARAM_INT,
770                                     '1 if grades are shown, otherwise 0', VALUE_DEFAULT,
771                                     $courseconfig->showgrades),
772                             'newsitems' => new external_value(PARAM_INT,
773                                     'number of recent items appearing on the course page',
774                                     VALUE_DEFAULT, $courseconfig->newsitems),
775                             'startdate' => new external_value(PARAM_INT,
776                                     'timestamp when the course start', VALUE_OPTIONAL),
777                             'enddate' => new external_value(PARAM_INT,
778                                     'timestamp when the course end', VALUE_OPTIONAL),
779                             'numsections' => new external_value(PARAM_INT,
780                                     '(deprecated, use courseformatoptions) number of weeks/topics',
781                                     VALUE_OPTIONAL),
782                             'maxbytes' => new external_value(PARAM_INT,
783                                     'largest size of file that can be uploaded into the course',
784                                     VALUE_DEFAULT, $courseconfig->maxbytes),
785                             'showreports' => new external_value(PARAM_INT,
786                                     'are activity report shown (yes = 1, no =0)', VALUE_DEFAULT,
787                                     $courseconfig->showreports),
788                             'visible' => new external_value(PARAM_INT,
789                                     '1: available to student, 0:not available', VALUE_OPTIONAL),
790                             'hiddensections' => new external_value(PARAM_INT,
791                                     '(deprecated, use courseformatoptions) How the hidden sections in the course are displayed to students',
792                                     VALUE_OPTIONAL),
793                             'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible',
794                                     VALUE_DEFAULT, $courseconfig->groupmode),
795                             'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no',
796                                     VALUE_DEFAULT, $courseconfig->groupmodeforce),
797                             'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id',
798                                     VALUE_DEFAULT, 0),
799                             'enablecompletion' => new external_value(PARAM_INT,
800                                     'Enabled, control via completion and activity settings. Disabled,
801                                         not shown in activity settings.',
802                                     VALUE_OPTIONAL),
803                             'completionnotify' => new external_value(PARAM_INT,
804                                     '1: yes 0: no', VALUE_OPTIONAL),
805                             'lang' => new external_value(PARAM_SAFEDIR,
806                                     'forced course language', VALUE_OPTIONAL),
807                             'forcetheme' => new external_value(PARAM_PLUGIN,
808                                     'name of the force theme', VALUE_OPTIONAL),
809                             'courseformatoptions' => new external_multiple_structure(
810                                 new external_single_structure(
811                                     array('name' => new external_value(PARAM_ALPHANUMEXT, 'course format option name'),
812                                         'value' => new external_value(PARAM_RAW, 'course format option value')
813                                 )),
814                                     'additional options for particular course format', VALUE_OPTIONAL),
815                             'customfields' => new external_multiple_structure(
816                                 new external_single_structure(
817                                     array(
818                                         'shortname'  => new external_value(PARAM_ALPHANUMEXT, 'The shortname of the custom field'),
819                                         'value' => new external_value(PARAM_RAW, 'The value of the custom field'),
820                                 )), 'custom fields for the course', VALUE_OPTIONAL
821                             )
822                     )), 'courses to create'
823                 )
824             )
825         );
826     }
828     /**
829      * Create  courses
830      *
831      * @param array $courses
832      * @return array courses (id and shortname only)
833      * @since Moodle 2.2
834      */
835     public static function create_courses($courses) {
836         global $CFG, $DB;
837         require_once($CFG->dirroot . "/course/lib.php");
838         require_once($CFG->libdir . '/completionlib.php');
840         $params = self::validate_parameters(self::create_courses_parameters(),
841                         array('courses' => $courses));
843         $availablethemes = core_component::get_plugin_list('theme');
844         $availablelangs = get_string_manager()->get_list_of_translations();
846         $transaction = $DB->start_delegated_transaction();
848         foreach ($params['courses'] as $course) {
850             // Ensure the current user is allowed to run this function
851             $context = context_coursecat::instance($course['categoryid'], IGNORE_MISSING);
852             try {
853                 self::validate_context($context);
854             } catch (Exception $e) {
855                 $exceptionparam = new stdClass();
856                 $exceptionparam->message = $e->getMessage();
857                 $exceptionparam->catid = $course['categoryid'];
858                 throw new moodle_exception('errorcatcontextnotvalid', 'webservice', '', $exceptionparam);
859             }
860             require_capability('moodle/course:create', $context);
862             // Fullname and short name are required to be non-empty.
863             if (trim($course['fullname']) === '') {
864                 throw new moodle_exception('errorinvalidparam', 'webservice', '', 'fullname');
865             } else if (trim($course['shortname']) === '') {
866                 throw new moodle_exception('errorinvalidparam', 'webservice', '', 'shortname');
867             }
869             // Make sure lang is valid
870             if (array_key_exists('lang', $course)) {
871                 if (empty($availablelangs[$course['lang']])) {
872                     throw new moodle_exception('errorinvalidparam', 'webservice', '', 'lang');
873                 }
874                 if (!has_capability('moodle/course:setforcedlanguage', $context)) {
875                     unset($course['lang']);
876                 }
877             }
879             // Make sure theme is valid
880             if (array_key_exists('forcetheme', $course)) {
881                 if (!empty($CFG->allowcoursethemes)) {
882                     if (empty($availablethemes[$course['forcetheme']])) {
883                         throw new moodle_exception('errorinvalidparam', 'webservice', '', 'forcetheme');
884                     } else {
885                         $course['theme'] = $course['forcetheme'];
886                     }
887                 }
888             }
890             //force visibility if ws user doesn't have the permission to set it
891             $category = $DB->get_record('course_categories', array('id' => $course['categoryid']));
892             if (!has_capability('moodle/course:visibility', $context)) {
893                 $course['visible'] = $category->visible;
894             }
896             //set default value for completion
897             $courseconfig = get_config('moodlecourse');
898             if (completion_info::is_enabled_for_site()) {
899                 if (!array_key_exists('enablecompletion', $course)) {
900                     $course['enablecompletion'] = $courseconfig->enablecompletion;
901                 }
902             } else {
903                 $course['enablecompletion'] = 0;
904             }
906             $course['category'] = $course['categoryid'];
908             // Summary format.
909             $course['summaryformat'] = external_validate_format($course['summaryformat']);
911             if (!empty($course['courseformatoptions'])) {
912                 foreach ($course['courseformatoptions'] as $option) {
913                     $course[$option['name']] = $option['value'];
914                 }
915             }
917             // Custom fields.
918             if (!empty($course['customfields'])) {
919                 foreach ($course['customfields'] as $field) {
920                     $course['customfield_'.$field['shortname']] = $field['value'];
921                 }
922             }
924             //Note: create_course() core function check shortname, idnumber, category
925             $course['id'] = create_course((object) $course)->id;
927             $resultcourses[] = array('id' => $course['id'], 'shortname' => $course['shortname']);
928         }
930         $transaction->allow_commit();
932         return $resultcourses;
933     }
935     /**
936      * Returns description of method result value
937      *
938      * @return external_description
939      * @since Moodle 2.2
940      */
941     public static function create_courses_returns() {
942         return new external_multiple_structure(
943             new external_single_structure(
944                 array(
945                     'id'       => new external_value(PARAM_INT, 'course id'),
946                     'shortname' => new external_value(PARAM_RAW, 'short name'),
947                 )
948             )
949         );
950     }
952     /**
953      * Update courses
954      *
955      * @return external_function_parameters
956      * @since Moodle 2.5
957      */
958     public static function update_courses_parameters() {
959         return new external_function_parameters(
960             array(
961                 'courses' => new external_multiple_structure(
962                     new external_single_structure(
963                         array(
964                             'id' => new external_value(PARAM_INT, 'ID of the course'),
965                             'fullname' => new external_value(PARAM_TEXT, 'full name', VALUE_OPTIONAL),
966                             'shortname' => new external_value(PARAM_TEXT, 'course short name', VALUE_OPTIONAL),
967                             'categoryid' => new external_value(PARAM_INT, 'category id', VALUE_OPTIONAL),
968                             'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
969                             'summary' => new external_value(PARAM_RAW, 'summary', VALUE_OPTIONAL),
970                             'summaryformat' => new external_format_value('summary', VALUE_OPTIONAL),
971                             'format' => new external_value(PARAM_PLUGIN,
972                                     'course format: weeks, topics, social, site,..', VALUE_OPTIONAL),
973                             'showgrades' => new external_value(PARAM_INT,
974                                     '1 if grades are shown, otherwise 0', VALUE_OPTIONAL),
975                             'newsitems' => new external_value(PARAM_INT,
976                                     'number of recent items appearing on the course page', VALUE_OPTIONAL),
977                             'startdate' => new external_value(PARAM_INT,
978                                     'timestamp when the course start', VALUE_OPTIONAL),
979                             'enddate' => new external_value(PARAM_INT,
980                                     'timestamp when the course end', VALUE_OPTIONAL),
981                             'numsections' => new external_value(PARAM_INT,
982                                     '(deprecated, use courseformatoptions) number of weeks/topics', VALUE_OPTIONAL),
983                             'maxbytes' => new external_value(PARAM_INT,
984                                     'largest size of file that can be uploaded into the course', VALUE_OPTIONAL),
985                             'showreports' => new external_value(PARAM_INT,
986                                     'are activity report shown (yes = 1, no =0)', VALUE_OPTIONAL),
987                             'visible' => new external_value(PARAM_INT,
988                                     '1: available to student, 0:not available', VALUE_OPTIONAL),
989                             'hiddensections' => new external_value(PARAM_INT,
990                                     '(deprecated, use courseformatoptions) How the hidden sections in the course are
991                                         displayed to students', VALUE_OPTIONAL),
992                             'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible', VALUE_OPTIONAL),
993                             'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no', VALUE_OPTIONAL),
994                             'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id', VALUE_OPTIONAL),
995                             'enablecompletion' => new external_value(PARAM_INT,
996                                     'Enabled, control via completion and activity settings. Disabled,
997                                         not shown in activity settings.', VALUE_OPTIONAL),
998                             'completionnotify' => new external_value(PARAM_INT, '1: yes 0: no', VALUE_OPTIONAL),
999                             'lang' => new external_value(PARAM_SAFEDIR, 'forced course language', VALUE_OPTIONAL),
1000                             'forcetheme' => new external_value(PARAM_PLUGIN, 'name of the force theme', VALUE_OPTIONAL),
1001                             'courseformatoptions' => new external_multiple_structure(
1002                                 new external_single_structure(
1003                                     array('name' => new external_value(PARAM_ALPHANUMEXT, 'course format option name'),
1004                                         'value' => new external_value(PARAM_RAW, 'course format option value')
1005                                 )), 'additional options for particular course format', VALUE_OPTIONAL),
1006                             'customfields' => new external_multiple_structure(
1007                                 new external_single_structure(
1008                                     [
1009                                         'shortname'  => new external_value(PARAM_ALPHANUMEXT, 'The shortname of the custom field'),
1010                                         'value' => new external_value(PARAM_RAW, 'The value of the custom field')
1011                                     ]
1012                                 ), 'Custom fields', VALUE_OPTIONAL),
1013                         )
1014                     ), 'courses to update'
1015                 )
1016             )
1017         );
1018     }
1020     /**
1021      * Update courses
1022      *
1023      * @param array $courses
1024      * @since Moodle 2.5
1025      */
1026     public static function update_courses($courses) {
1027         global $CFG, $DB;
1028         require_once($CFG->dirroot . "/course/lib.php");
1029         $warnings = array();
1031         $params = self::validate_parameters(self::update_courses_parameters(),
1032                         array('courses' => $courses));
1034         $availablethemes = core_component::get_plugin_list('theme');
1035         $availablelangs = get_string_manager()->get_list_of_translations();
1037         foreach ($params['courses'] as $course) {
1038             // Catch any exception while updating course and return as warning to user.
1039             try {
1040                 // Ensure the current user is allowed to run this function.
1041                 $context = context_course::instance($course['id'], MUST_EXIST);
1042                 self::validate_context($context);
1044                 $oldcourse = course_get_format($course['id'])->get_course();
1046                 require_capability('moodle/course:update', $context);
1048                 // Check if user can change category.
1049                 if (array_key_exists('categoryid', $course) && ($oldcourse->category != $course['categoryid'])) {
1050                     require_capability('moodle/course:changecategory', $context);
1051                     $course['category'] = $course['categoryid'];
1052                 }
1054                 // Check if the user can change fullname, and the new value is non-empty.
1055                 if (array_key_exists('fullname', $course) && ($oldcourse->fullname != $course['fullname'])) {
1056                     require_capability('moodle/course:changefullname', $context);
1057                     if (trim($course['fullname']) === '') {
1058                         throw new moodle_exception('errorinvalidparam', 'webservice', '', 'fullname');
1059                     }
1060                 }
1062                 // Check if the user can change shortname, and the new value is non-empty.
1063                 if (array_key_exists('shortname', $course) && ($oldcourse->shortname != $course['shortname'])) {
1064                     require_capability('moodle/course:changeshortname', $context);
1065                     if (trim($course['shortname']) === '') {
1066                         throw new moodle_exception('errorinvalidparam', 'webservice', '', 'shortname');
1067                     }
1068                 }
1070                 // Check if the user can change the idnumber.
1071                 if (array_key_exists('idnumber', $course) && ($oldcourse->idnumber != $course['idnumber'])) {
1072                     require_capability('moodle/course:changeidnumber', $context);
1073                 }
1075                 // Check if user can change summary.
1076                 if (array_key_exists('summary', $course) && ($oldcourse->summary != $course['summary'])) {
1077                     require_capability('moodle/course:changesummary', $context);
1078                 }
1080                 // Summary format.
1081                 if (array_key_exists('summaryformat', $course) && ($oldcourse->summaryformat != $course['summaryformat'])) {
1082                     require_capability('moodle/course:changesummary', $context);
1083                     $course['summaryformat'] = external_validate_format($course['summaryformat']);
1084                 }
1086                 // Check if user can change visibility.
1087                 if (array_key_exists('visible', $course) && ($oldcourse->visible != $course['visible'])) {
1088                     require_capability('moodle/course:visibility', $context);
1089                 }
1091                 // Make sure lang is valid.
1092                 if (array_key_exists('lang', $course) && ($oldcourse->lang != $course['lang'])) {
1093                     require_capability('moodle/course:setforcedlanguage', $context);
1094                     if (empty($availablelangs[$course['lang']])) {
1095                         throw new moodle_exception('errorinvalidparam', 'webservice', '', 'lang');
1096                     }
1097                 }
1099                 // Make sure theme is valid.
1100                 if (array_key_exists('forcetheme', $course)) {
1101                     if (!empty($CFG->allowcoursethemes)) {
1102                         if (empty($availablethemes[$course['forcetheme']])) {
1103                             throw new moodle_exception('errorinvalidparam', 'webservice', '', 'forcetheme');
1104                         } else {
1105                             $course['theme'] = $course['forcetheme'];
1106                         }
1107                     }
1108                 }
1110                 // Make sure completion is enabled before setting it.
1111                 if (array_key_exists('enabledcompletion', $course) && !completion_info::is_enabled_for_site()) {
1112                     $course['enabledcompletion'] = 0;
1113                 }
1115                 // Make sure maxbytes are less then CFG->maxbytes.
1116                 if (array_key_exists('maxbytes', $course)) {
1117                     // We allow updates back to 0 max bytes, a special value denoting the course uses the site limit.
1118                     // Otherwise, either use the size specified, or cap at the max size for the course.
1119                     if ($course['maxbytes'] != 0) {
1120                         $course['maxbytes'] = get_max_upload_file_size($CFG->maxbytes, $course['maxbytes']);
1121                     }
1122                 }
1124                 if (!empty($course['courseformatoptions'])) {
1125                     foreach ($course['courseformatoptions'] as $option) {
1126                         if (isset($option['name']) && isset($option['value'])) {
1127                             $course[$option['name']] = $option['value'];
1128                         }
1129                     }
1130                 }
1132                 // Prepare list of custom fields.
1133                 if (isset($course['customfields'])) {
1134                     foreach ($course['customfields'] as $field) {
1135                         $course['customfield_' . $field['shortname']] = $field['value'];
1136                     }
1137                 }
1139                 // Update course if user has all required capabilities.
1140                 update_course((object) $course);
1141             } catch (Exception $e) {
1142                 $warning = array();
1143                 $warning['item'] = 'course';
1144                 $warning['itemid'] = $course['id'];
1145                 if ($e instanceof moodle_exception) {
1146                     $warning['warningcode'] = $e->errorcode;
1147                 } else {
1148                     $warning['warningcode'] = $e->getCode();
1149                 }
1150                 $warning['message'] = $e->getMessage();
1151                 $warnings[] = $warning;
1152             }
1153         }
1155         $result = array();
1156         $result['warnings'] = $warnings;
1157         return $result;
1158     }
1160     /**
1161      * Returns description of method result value
1162      *
1163      * @return external_description
1164      * @since Moodle 2.5
1165      */
1166     public static function update_courses_returns() {
1167         return new external_single_structure(
1168             array(
1169                 'warnings' => new external_warnings()
1170             )
1171         );
1172     }
1174     /**
1175      * Returns description of method parameters
1176      *
1177      * @return external_function_parameters
1178      * @since Moodle 2.2
1179      */
1180     public static function delete_courses_parameters() {
1181         return new external_function_parameters(
1182             array(
1183                 'courseids' => new external_multiple_structure(new external_value(PARAM_INT, 'course ID')),
1184             )
1185         );
1186     }
1188     /**
1189      * Delete courses
1190      *
1191      * @param array $courseids A list of course ids
1192      * @since Moodle 2.2
1193      */
1194     public static function delete_courses($courseids) {
1195         global $CFG, $DB;
1196         require_once($CFG->dirroot."/course/lib.php");
1198         // Parameter validation.
1199         $params = self::validate_parameters(self::delete_courses_parameters(), array('courseids'=>$courseids));
1201         $warnings = array();
1203         foreach ($params['courseids'] as $courseid) {
1204             $course = $DB->get_record('course', array('id' => $courseid));
1206             if ($course === false) {
1207                 $warnings[] = array(
1208                                 'item' => 'course',
1209                                 'itemid' => $courseid,
1210                                 'warningcode' => 'unknowncourseidnumber',
1211                                 'message' => 'Unknown course ID ' . $courseid
1212                             );
1213                 continue;
1214             }
1216             // Check if the context is valid.
1217             $coursecontext = context_course::instance($course->id);
1218             self::validate_context($coursecontext);
1220             // Check if the current user has permission.
1221             if (!can_delete_course($courseid)) {
1222                 $warnings[] = array(
1223                                 'item' => 'course',
1224                                 'itemid' => $courseid,
1225                                 'warningcode' => 'cannotdeletecourse',
1226                                 'message' => 'You do not have the permission to delete this course' . $courseid
1227                             );
1228                 continue;
1229             }
1231             if (delete_course($course, false) === false) {
1232                 $warnings[] = array(
1233                                 'item' => 'course',
1234                                 'itemid' => $courseid,
1235                                 'warningcode' => 'cannotdeletecategorycourse',
1236                                 'message' => 'Course ' . $courseid . ' failed to be deleted'
1237                             );
1238                 continue;
1239             }
1240         }
1242         fix_course_sortorder();
1244         return array('warnings' => $warnings);
1245     }
1247     /**
1248      * Returns description of method result value
1249      *
1250      * @return external_description
1251      * @since Moodle 2.2
1252      */
1253     public static function delete_courses_returns() {
1254         return new external_single_structure(
1255             array(
1256                 'warnings' => new external_warnings()
1257             )
1258         );
1259     }
1261     /**
1262      * Returns description of method parameters
1263      *
1264      * @return external_function_parameters
1265      * @since Moodle 2.3
1266      */
1267     public static function duplicate_course_parameters() {
1268         return new external_function_parameters(
1269             array(
1270                 'courseid' => new external_value(PARAM_INT, 'course to duplicate id'),
1271                 'fullname' => new external_value(PARAM_TEXT, 'duplicated course full name'),
1272                 'shortname' => new external_value(PARAM_TEXT, 'duplicated course short name'),
1273                 'categoryid' => new external_value(PARAM_INT, 'duplicated course category parent'),
1274                 'visible' => new external_value(PARAM_INT, 'duplicated course visible, default to yes', VALUE_DEFAULT, 1),
1275                 'options' => new external_multiple_structure(
1276                     new external_single_structure(
1277                         array(
1278                                 'name' => new external_value(PARAM_ALPHAEXT, 'The backup option name:
1279                                             "activities" (int) Include course activites (default to 1 that is equal to yes),
1280                                             "blocks" (int) Include course blocks (default to 1 that is equal to yes),
1281                                             "filters" (int) Include course filters  (default to 1 that is equal to yes),
1282                                             "users" (int) Include users (default to 0 that is equal to no),
1283                                             "enrolments" (int) Include enrolment methods (default to 1 - restore only with users),
1284                                             "role_assignments" (int) Include role assignments  (default to 0 that is equal to no),
1285                                             "comments" (int) Include user comments  (default to 0 that is equal to no),
1286                                             "userscompletion" (int) Include user course completion information  (default to 0 that is equal to no),
1287                                             "logs" (int) Include course logs  (default to 0 that is equal to no),
1288                                             "grade_histories" (int) Include histories  (default to 0 that is equal to no)'
1289                                             ),
1290                                 'value' => new external_value(PARAM_RAW, 'the value for the option 1 (yes) or 0 (no)'
1291                             )
1292                         )
1293                     ), VALUE_DEFAULT, array()
1294                 ),
1295             )
1296         );
1297     }
1299     /**
1300      * Duplicate a course
1301      *
1302      * @param int $courseid
1303      * @param string $fullname Duplicated course fullname
1304      * @param string $shortname Duplicated course shortname
1305      * @param int $categoryid Duplicated course parent category id
1306      * @param int $visible Duplicated course availability
1307      * @param array $options List of backup options
1308      * @return array New course info
1309      * @since Moodle 2.3
1310      */
1311     public static function duplicate_course($courseid, $fullname, $shortname, $categoryid, $visible = 1, $options = array()) {
1312         global $CFG, $USER, $DB;
1313         require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
1314         require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
1316         // Parameter validation.
1317         $params = self::validate_parameters(
1318                 self::duplicate_course_parameters(),
1319                 array(
1320                       'courseid' => $courseid,
1321                       'fullname' => $fullname,
1322                       'shortname' => $shortname,
1323                       'categoryid' => $categoryid,
1324                       'visible' => $visible,
1325                       'options' => $options
1326                 )
1327         );
1329         // Context validation.
1331         if (! ($course = $DB->get_record('course', array('id'=>$params['courseid'])))) {
1332             throw new moodle_exception('invalidcourseid', 'error');
1333         }
1335         // Category where duplicated course is going to be created.
1336         $categorycontext = context_coursecat::instance($params['categoryid']);
1337         self::validate_context($categorycontext);
1339         // Course to be duplicated.
1340         $coursecontext = context_course::instance($course->id);
1341         self::validate_context($coursecontext);
1343         $backupdefaults = array(
1344             'activities' => 1,
1345             'blocks' => 1,
1346             'filters' => 1,
1347             'users' => 0,
1348             'enrolments' => backup::ENROL_WITHUSERS,
1349             'role_assignments' => 0,
1350             'comments' => 0,
1351             'userscompletion' => 0,
1352             'logs' => 0,
1353             'grade_histories' => 0
1354         );
1356         $backupsettings = array();
1357         // Check for backup and restore options.
1358         if (!empty($params['options'])) {
1359             foreach ($params['options'] as $option) {
1361                 // Strict check for a correct value (allways 1 or 0, true or false).
1362                 $value = clean_param($option['value'], PARAM_INT);
1364                 if ($value !== 0 and $value !== 1) {
1365                     throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
1366                 }
1368                 if (!isset($backupdefaults[$option['name']])) {
1369                     throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
1370                 }
1372                 $backupsettings[$option['name']] = $value;
1373             }
1374         }
1376         // Capability checking.
1378         // The backup controller check for this currently, this may be redundant.
1379         require_capability('moodle/course:create', $categorycontext);
1380         require_capability('moodle/restore:restorecourse', $categorycontext);
1381         require_capability('moodle/backup:backupcourse', $coursecontext);
1383         if (!empty($backupsettings['users'])) {
1384             require_capability('moodle/backup:userinfo', $coursecontext);
1385             require_capability('moodle/restore:userinfo', $categorycontext);
1386         }
1388         // Check if the shortname is used.
1389         if ($foundcourses = $DB->get_records('course', array('shortname'=>$shortname))) {
1390             foreach ($foundcourses as $foundcourse) {
1391                 $foundcoursenames[] = $foundcourse->fullname;
1392             }
1394             $foundcoursenamestring = implode(',', $foundcoursenames);
1395             throw new moodle_exception('shortnametaken', '', '', $foundcoursenamestring);
1396         }
1398         // Backup the course.
1400         $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
1401         backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $USER->id);
1403         foreach ($backupsettings as $name => $value) {
1404             if ($setting = $bc->get_plan()->get_setting($name)) {
1405                 $bc->get_plan()->get_setting($name)->set_value($value);
1406             }
1407         }
1409         $backupid       = $bc->get_backupid();
1410         $backupbasepath = $bc->get_plan()->get_basepath();
1412         $bc->execute_plan();
1413         $results = $bc->get_results();
1414         $file = $results['backup_destination'];
1416         $bc->destroy();
1418         // Restore the backup immediately.
1420         // Check if we need to unzip the file because the backup temp dir does not contains backup files.
1421         if (!file_exists($backupbasepath . "/moodle_backup.xml")) {
1422             $file->extract_to_pathname(get_file_packer('application/vnd.moodle.backup'), $backupbasepath);
1423         }
1425         // Create new course.
1426         $newcourseid = restore_dbops::create_new_course($params['fullname'], $params['shortname'], $params['categoryid']);
1428         $rc = new restore_controller($backupid, $newcourseid,
1429                 backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $USER->id, backup::TARGET_NEW_COURSE);
1431         foreach ($backupsettings as $name => $value) {
1432             $setting = $rc->get_plan()->get_setting($name);
1433             if ($setting->get_status() == backup_setting::NOT_LOCKED) {
1434                 $setting->set_value($value);
1435             }
1436         }
1438         if (!$rc->execute_precheck()) {
1439             $precheckresults = $rc->get_precheck_results();
1440             if (is_array($precheckresults) && !empty($precheckresults['errors'])) {
1441                 if (empty($CFG->keeptempdirectoriesonbackup)) {
1442                     fulldelete($backupbasepath);
1443                 }
1445                 $errorinfo = '';
1447                 foreach ($precheckresults['errors'] as $error) {
1448                     $errorinfo .= $error;
1449                 }
1451                 if (array_key_exists('warnings', $precheckresults)) {
1452                     foreach ($precheckresults['warnings'] as $warning) {
1453                         $errorinfo .= $warning;
1454                     }
1455                 }
1457                 throw new moodle_exception('backupprecheckerrors', 'webservice', '', $errorinfo);
1458             }
1459         }
1461         $rc->execute_plan();
1462         $rc->destroy();
1464         $course = $DB->get_record('course', array('id' => $newcourseid), '*', MUST_EXIST);
1465         $course->fullname = $params['fullname'];
1466         $course->shortname = $params['shortname'];
1467         $course->visible = $params['visible'];
1469         // Set shortname and fullname back.
1470         $DB->update_record('course', $course);
1472         if (empty($CFG->keeptempdirectoriesonbackup)) {
1473             fulldelete($backupbasepath);
1474         }
1476         // Delete the course backup file created by this WebService. Originally located in the course backups area.
1477         $file->delete();
1479         return array('id' => $course->id, 'shortname' => $course->shortname);
1480     }
1482     /**
1483      * Returns description of method result value
1484      *
1485      * @return external_description
1486      * @since Moodle 2.3
1487      */
1488     public static function duplicate_course_returns() {
1489         return new external_single_structure(
1490             array(
1491                 'id'       => new external_value(PARAM_INT, 'course id'),
1492                 'shortname' => new external_value(PARAM_RAW, 'short name'),
1493             )
1494         );
1495     }
1497     /**
1498      * Returns description of method parameters for import_course
1499      *
1500      * @return external_function_parameters
1501      * @since Moodle 2.4
1502      */
1503     public static function import_course_parameters() {
1504         return new external_function_parameters(
1505             array(
1506                 'importfrom' => new external_value(PARAM_INT, 'the id of the course we are importing from'),
1507                 'importto' => new external_value(PARAM_INT, 'the id of the course we are importing to'),
1508                 'deletecontent' => new external_value(PARAM_INT, 'whether to delete the course content where we are importing to (default to 0 = No)', VALUE_DEFAULT, 0),
1509                 'options' => new external_multiple_structure(
1510                     new external_single_structure(
1511                         array(
1512                                 'name' => new external_value(PARAM_ALPHA, 'The backup option name:
1513                                             "activities" (int) Include course activites (default to 1 that is equal to yes),
1514                                             "blocks" (int) Include course blocks (default to 1 that is equal to yes),
1515                                             "filters" (int) Include course filters  (default to 1 that is equal to yes)'
1516                                             ),
1517                                 'value' => new external_value(PARAM_RAW, 'the value for the option 1 (yes) or 0 (no)'
1518                             )
1519                         )
1520                     ), VALUE_DEFAULT, array()
1521                 ),
1522             )
1523         );
1524     }
1526     /**
1527      * Imports a course
1528      *
1529      * @param int $importfrom The id of the course we are importing from
1530      * @param int $importto The id of the course we are importing to
1531      * @param bool $deletecontent Whether to delete the course we are importing to content
1532      * @param array $options List of backup options
1533      * @return null
1534      * @since Moodle 2.4
1535      */
1536     public static function import_course($importfrom, $importto, $deletecontent = 0, $options = array()) {
1537         global $CFG, $USER, $DB;
1538         require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
1539         require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
1541         // Parameter validation.
1542         $params = self::validate_parameters(
1543             self::import_course_parameters(),
1544             array(
1545                 'importfrom' => $importfrom,
1546                 'importto' => $importto,
1547                 'deletecontent' => $deletecontent,
1548                 'options' => $options
1549             )
1550         );
1552         if ($params['deletecontent'] !== 0 and $params['deletecontent'] !== 1) {
1553             throw new moodle_exception('invalidextparam', 'webservice', '', $params['deletecontent']);
1554         }
1556         // Context validation.
1558         if (! ($importfrom = $DB->get_record('course', array('id'=>$params['importfrom'])))) {
1559             throw new moodle_exception('invalidcourseid', 'error');
1560         }
1562         if (! ($importto = $DB->get_record('course', array('id'=>$params['importto'])))) {
1563             throw new moodle_exception('invalidcourseid', 'error');
1564         }
1566         $importfromcontext = context_course::instance($importfrom->id);
1567         self::validate_context($importfromcontext);
1569         $importtocontext = context_course::instance($importto->id);
1570         self::validate_context($importtocontext);
1572         $backupdefaults = array(
1573             'activities' => 1,
1574             'blocks' => 1,
1575             'filters' => 1
1576         );
1578         $backupsettings = array();
1580         // Check for backup and restore options.
1581         if (!empty($params['options'])) {
1582             foreach ($params['options'] as $option) {
1584                 // Strict check for a correct value (allways 1 or 0, true or false).
1585                 $value = clean_param($option['value'], PARAM_INT);
1587                 if ($value !== 0 and $value !== 1) {
1588                     throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
1589                 }
1591                 if (!isset($backupdefaults[$option['name']])) {
1592                     throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
1593                 }
1595                 $backupsettings[$option['name']] = $value;
1596             }
1597         }
1599         // Capability checking.
1601         require_capability('moodle/backup:backuptargetimport', $importfromcontext);
1602         require_capability('moodle/restore:restoretargetimport', $importtocontext);
1604         $bc = new backup_controller(backup::TYPE_1COURSE, $importfrom->id, backup::FORMAT_MOODLE,
1605                 backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id);
1607         foreach ($backupsettings as $name => $value) {
1608             $bc->get_plan()->get_setting($name)->set_value($value);
1609         }
1611         $backupid       = $bc->get_backupid();
1612         $backupbasepath = $bc->get_plan()->get_basepath();
1614         $bc->execute_plan();
1615         $bc->destroy();
1617         // Restore the backup immediately.
1619         // Check if we must delete the contents of the destination course.
1620         if ($params['deletecontent']) {
1621             $restoretarget = backup::TARGET_EXISTING_DELETING;
1622         } else {
1623             $restoretarget = backup::TARGET_EXISTING_ADDING;
1624         }
1626         $rc = new restore_controller($backupid, $importto->id,
1627                 backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id, $restoretarget);
1629         foreach ($backupsettings as $name => $value) {
1630             $rc->get_plan()->get_setting($name)->set_value($value);
1631         }
1633         if (!$rc->execute_precheck()) {
1634             $precheckresults = $rc->get_precheck_results();
1635             if (is_array($precheckresults) && !empty($precheckresults['errors'])) {
1636                 if (empty($CFG->keeptempdirectoriesonbackup)) {
1637                     fulldelete($backupbasepath);
1638                 }
1640                 $errorinfo = '';
1642                 foreach ($precheckresults['errors'] as $error) {
1643                     $errorinfo .= $error;
1644                 }
1646                 if (array_key_exists('warnings', $precheckresults)) {
1647                     foreach ($precheckresults['warnings'] as $warning) {
1648                         $errorinfo .= $warning;
1649                     }
1650                 }
1652                 throw new moodle_exception('backupprecheckerrors', 'webservice', '', $errorinfo);
1653             }
1654         } else {
1655             if ($restoretarget == backup::TARGET_EXISTING_DELETING) {
1656                 restore_dbops::delete_course_content($importto->id);
1657             }
1658         }
1660         $rc->execute_plan();
1661         $rc->destroy();
1663         if (empty($CFG->keeptempdirectoriesonbackup)) {
1664             fulldelete($backupbasepath);
1665         }
1667         return null;
1668     }
1670     /**
1671      * Returns description of method result value
1672      *
1673      * @return external_description
1674      * @since Moodle 2.4
1675      */
1676     public static function import_course_returns() {
1677         return null;
1678     }
1680     /**
1681      * Returns description of method parameters
1682      *
1683      * @return external_function_parameters
1684      * @since Moodle 2.3
1685      */
1686     public static function get_categories_parameters() {
1687         return new external_function_parameters(
1688             array(
1689                 'criteria' => new external_multiple_structure(
1690                     new external_single_structure(
1691                         array(
1692                             'key' => new external_value(PARAM_ALPHA,
1693                                          'The category column to search, expected keys (value format) are:'.
1694                                          '"id" (int) the category id,'.
1695                                          '"ids" (string) category ids separated by commas,'.
1696                                          '"name" (string) the category name,'.
1697                                          '"parent" (int) the parent category id,'.
1698                                          '"idnumber" (string) category idnumber'.
1699                                          ' - user must have \'moodle/category:manage\' to search on idnumber,'.
1700                                          '"visible" (int) whether the returned categories must be visible or hidden. If the key is not passed,
1701                                              then the function return all categories that the user can see.'.
1702                                          ' - user must have \'moodle/category:manage\' or \'moodle/category:viewhiddencategories\' to search on visible,'.
1703                                          '"theme" (string) only return the categories having this theme'.
1704                                          ' - user must have \'moodle/category:manage\' to search on theme'),
1705                             'value' => new external_value(PARAM_RAW, 'the value to match')
1706                         )
1707                     ), 'criteria', VALUE_DEFAULT, array()
1708                 ),
1709                 'addsubcategories' => new external_value(PARAM_BOOL, 'return the sub categories infos
1710                                           (1 - default) otherwise only the category info (0)', VALUE_DEFAULT, 1)
1711             )
1712         );
1713     }
1715     /**
1716      * Get categories
1717      *
1718      * @param array $criteria Criteria to match the results
1719      * @param booln $addsubcategories obtain only the category (false) or its subcategories (true - default)
1720      * @return array list of categories
1721      * @since Moodle 2.3
1722      */
1723     public static function get_categories($criteria = array(), $addsubcategories = true) {
1724         global $CFG, $DB;
1725         require_once($CFG->dirroot . "/course/lib.php");
1727         // Validate parameters.
1728         $params = self::validate_parameters(self::get_categories_parameters(),
1729                 array('criteria' => $criteria, 'addsubcategories' => $addsubcategories));
1731         // Retrieve the categories.
1732         $categories = array();
1733         if (!empty($params['criteria'])) {
1735             $conditions = array();
1736             $wheres = array();
1737             foreach ($params['criteria'] as $crit) {
1738                 $key = trim($crit['key']);
1740                 // Trying to avoid duplicate keys.
1741                 if (!isset($conditions[$key])) {
1743                     $context = context_system::instance();
1744                     $value = null;
1745                     switch ($key) {
1746                         case 'id':
1747                             $value = clean_param($crit['value'], PARAM_INT);
1748                             $conditions[$key] = $value;
1749                             $wheres[] = $key . " = :" . $key;
1750                             break;
1752                         case 'ids':
1753                             $value = clean_param($crit['value'], PARAM_SEQUENCE);
1754                             $ids = explode(',', $value);
1755                             list($sqlids, $paramids) = $DB->get_in_or_equal($ids, SQL_PARAMS_NAMED);
1756                             $conditions = array_merge($conditions, $paramids);
1757                             $wheres[] = 'id ' . $sqlids;
1758                             break;
1760                         case 'idnumber':
1761                             if (has_capability('moodle/category:manage', $context)) {
1762                                 $value = clean_param($crit['value'], PARAM_RAW);
1763                                 $conditions[$key] = $value;
1764                                 $wheres[] = $key . " = :" . $key;
1765                             } else {
1766                                 // We must throw an exception.
1767                                 // Otherwise the dev client would think no idnumber exists.
1768                                 throw new moodle_exception('criteriaerror',
1769                                         'webservice', '', null,
1770                                         'You don\'t have the permissions to search on the "idnumber" field.');
1771                             }
1772                             break;
1774                         case 'name':
1775                             $value = clean_param($crit['value'], PARAM_TEXT);
1776                             $conditions[$key] = $value;
1777                             $wheres[] = $key . " = :" . $key;
1778                             break;
1780                         case 'parent':
1781                             $value = clean_param($crit['value'], PARAM_INT);
1782                             $conditions[$key] = $value;
1783                             $wheres[] = $key . " = :" . $key;
1784                             break;
1786                         case 'visible':
1787                             if (has_capability('moodle/category:viewhiddencategories', $context)) {
1788                                 $value = clean_param($crit['value'], PARAM_INT);
1789                                 $conditions[$key] = $value;
1790                                 $wheres[] = $key . " = :" . $key;
1791                             } else {
1792                                 throw new moodle_exception('criteriaerror',
1793                                         'webservice', '', null,
1794                                         'You don\'t have the permissions to search on the "visible" field.');
1795                             }
1796                             break;
1798                         case 'theme':
1799                             if (has_capability('moodle/category:manage', $context)) {
1800                                 $value = clean_param($crit['value'], PARAM_THEME);
1801                                 $conditions[$key] = $value;
1802                                 $wheres[] = $key . " = :" . $key;
1803                             } else {
1804                                 throw new moodle_exception('criteriaerror',
1805                                         'webservice', '', null,
1806                                         'You don\'t have the permissions to search on the "theme" field.');
1807                             }
1808                             break;
1810                         default:
1811                             throw new moodle_exception('criteriaerror',
1812                                     'webservice', '', null,
1813                                     'You can not search on this criteria: ' . $key);
1814                     }
1815                 }
1816             }
1818             if (!empty($wheres)) {
1819                 $wheres = implode(" AND ", $wheres);
1821                 $categories = $DB->get_records_select('course_categories', $wheres, $conditions);
1823                 // Retrieve its sub subcategories (all levels).
1824                 if ($categories and !empty($params['addsubcategories'])) {
1825                     $newcategories = array();
1827                     // Check if we required visible/theme checks.
1828                     $additionalselect = '';
1829                     $additionalparams = array();
1830                     if (isset($conditions['visible'])) {
1831                         $additionalselect .= ' AND visible = :visible';
1832                         $additionalparams['visible'] = $conditions['visible'];
1833                     }
1834                     if (isset($conditions['theme'])) {
1835                         $additionalselect .= ' AND theme= :theme';
1836                         $additionalparams['theme'] = $conditions['theme'];
1837                     }
1839                     foreach ($categories as $category) {
1840                         $sqlselect = $DB->sql_like('path', ':path') . $additionalselect;
1841                         $sqlparams = array('path' => $category->path.'/%') + $additionalparams; // It will NOT include the specified category.
1842                         $subcategories = $DB->get_records_select('course_categories', $sqlselect, $sqlparams);
1843                         $newcategories = $newcategories + $subcategories;   // Both arrays have integer as keys.
1844                     }
1845                     $categories = $categories + $newcategories;
1846                 }
1847             }
1849         } else {
1850             // Retrieve all categories in the database.
1851             $categories = $DB->get_records('course_categories');
1852         }
1854         // The not returned categories. key => category id, value => reason of exclusion.
1855         $excludedcats = array();
1857         // The returned categories.
1858         $categoriesinfo = array();
1860         // We need to sort the categories by path.
1861         // The parent cats need to be checked by the algo first.
1862         usort($categories, "core_course_external::compare_categories_by_path");
1864         foreach ($categories as $category) {
1866             // Check if the category is a child of an excluded category, if yes exclude it too (excluded => do not return).
1867             $parents = explode('/', $category->path);
1868             unset($parents[0]); // First key is always empty because path start with / => /1/2/4.
1869             foreach ($parents as $parentid) {
1870                 // Note: when the parent exclusion was due to the context,
1871                 // the sub category could still be returned.
1872                 if (isset($excludedcats[$parentid]) and $excludedcats[$parentid] != 'context') {
1873                     $excludedcats[$category->id] = 'parent';
1874                 }
1875             }
1877             // Check the user can use the category context.
1878             $context = context_coursecat::instance($category->id);
1879             try {
1880                 self::validate_context($context);
1881             } catch (Exception $e) {
1882                 $excludedcats[$category->id] = 'context';
1884                 // If it was the requested category then throw an exception.
1885                 if (isset($params['categoryid']) && $category->id == $params['categoryid']) {
1886                     $exceptionparam = new stdClass();
1887                     $exceptionparam->message = $e->getMessage();
1888                     $exceptionparam->catid = $category->id;
1889                     throw new moodle_exception('errorcatcontextnotvalid', 'webservice', '', $exceptionparam);
1890                 }
1891             }
1893             // Return the category information.
1894             if (!isset($excludedcats[$category->id])) {
1896                 // Final check to see if the category is visible to the user.
1897                 if (core_course_category::can_view_category($category)) {
1899                     $categoryinfo = array();
1900                     $categoryinfo['id'] = $category->id;
1901                     $categoryinfo['name'] = external_format_string($category->name, $context);
1902                     list($categoryinfo['description'], $categoryinfo['descriptionformat']) =
1903                         external_format_text($category->description, $category->descriptionformat,
1904                                 $context->id, 'coursecat', 'description', null);
1905                     $categoryinfo['parent'] = $category->parent;
1906                     $categoryinfo['sortorder'] = $category->sortorder;
1907                     $categoryinfo['coursecount'] = $category->coursecount;
1908                     $categoryinfo['depth'] = $category->depth;
1909                     $categoryinfo['path'] = $category->path;
1911                     // Some fields only returned for admin.
1912                     if (has_capability('moodle/category:manage', $context)) {
1913                         $categoryinfo['idnumber'] = $category->idnumber;
1914                         $categoryinfo['visible'] = $category->visible;
1915                         $categoryinfo['visibleold'] = $category->visibleold;
1916                         $categoryinfo['timemodified'] = $category->timemodified;
1917                         $categoryinfo['theme'] = clean_param($category->theme, PARAM_THEME);
1918                     }
1920                     $categoriesinfo[] = $categoryinfo;
1921                 } else {
1922                     $excludedcats[$category->id] = 'visibility';
1923                 }
1924             }
1925         }
1927         // Sorting the resulting array so it looks a bit better for the client developer.
1928         usort($categoriesinfo, "core_course_external::compare_categories_by_sortorder");
1930         return $categoriesinfo;
1931     }
1933     /**
1934      * Sort categories array by path
1935      * private function: only used by get_categories
1936      *
1937      * @param array $category1
1938      * @param array $category2
1939      * @return int result of strcmp
1940      * @since Moodle 2.3
1941      */
1942     private static function compare_categories_by_path($category1, $category2) {
1943         return strcmp($category1->path, $category2->path);
1944     }
1946     /**
1947      * Sort categories array by sortorder
1948      * private function: only used by get_categories
1949      *
1950      * @param array $category1
1951      * @param array $category2
1952      * @return int result of strcmp
1953      * @since Moodle 2.3
1954      */
1955     private static function compare_categories_by_sortorder($category1, $category2) {
1956         return strcmp($category1['sortorder'], $category2['sortorder']);
1957     }
1959     /**
1960      * Returns description of method result value
1961      *
1962      * @return external_description
1963      * @since Moodle 2.3
1964      */
1965     public static function get_categories_returns() {
1966         return new external_multiple_structure(
1967             new external_single_structure(
1968                 array(
1969                     'id' => new external_value(PARAM_INT, 'category id'),
1970                     'name' => new external_value(PARAM_RAW, 'category name'),
1971                     'idnumber' => new external_value(PARAM_RAW, 'category id number', VALUE_OPTIONAL),
1972                     'description' => new external_value(PARAM_RAW, 'category description'),
1973                     'descriptionformat' => new external_format_value('description'),
1974                     'parent' => new external_value(PARAM_INT, 'parent category id'),
1975                     'sortorder' => new external_value(PARAM_INT, 'category sorting order'),
1976                     'coursecount' => new external_value(PARAM_INT, 'number of courses in this category'),
1977                     'visible' => new external_value(PARAM_INT, '1: available, 0:not available', VALUE_OPTIONAL),
1978                     'visibleold' => new external_value(PARAM_INT, '1: available, 0:not available', VALUE_OPTIONAL),
1979                     'timemodified' => new external_value(PARAM_INT, 'timestamp', VALUE_OPTIONAL),
1980                     'depth' => new external_value(PARAM_INT, 'category depth'),
1981                     'path' => new external_value(PARAM_TEXT, 'category path'),
1982                     'theme' => new external_value(PARAM_THEME, 'category theme', VALUE_OPTIONAL),
1983                 ), 'List of categories'
1984             )
1985         );
1986     }
1988     /**
1989      * Returns description of method parameters
1990      *
1991      * @return external_function_parameters
1992      * @since Moodle 2.3
1993      */
1994     public static function create_categories_parameters() {
1995         return new external_function_parameters(
1996             array(
1997                 'categories' => new external_multiple_structure(
1998                         new external_single_structure(
1999                             array(
2000                                 'name' => new external_value(PARAM_TEXT, 'new category name'),
2001                                 'parent' => new external_value(PARAM_INT,
2002                                         'the parent category id inside which the new category will be created
2003                                          - set to 0 for a root category',
2004                                         VALUE_DEFAULT, 0),
2005                                 'idnumber' => new external_value(PARAM_RAW,
2006                                         'the new category idnumber', VALUE_OPTIONAL),
2007                                 'description' => new external_value(PARAM_RAW,
2008                                         'the new category description', VALUE_OPTIONAL),
2009                                 'descriptionformat' => new external_format_value('description', VALUE_DEFAULT),
2010                                 'theme' => new external_value(PARAM_THEME,
2011                                         'the new category theme. This option must be enabled on moodle',
2012                                         VALUE_OPTIONAL),
2013                         )
2014                     )
2015                 )
2016             )
2017         );
2018     }
2020     /**
2021      * Create categories
2022      *
2023      * @param array $categories - see create_categories_parameters() for the array structure
2024      * @return array - see create_categories_returns() for the array structure
2025      * @since Moodle 2.3
2026      */
2027     public static function create_categories($categories) {
2028         global $DB;
2030         $params = self::validate_parameters(self::create_categories_parameters(),
2031                         array('categories' => $categories));
2033         $transaction = $DB->start_delegated_transaction();
2035         $createdcategories = array();
2036         foreach ($params['categories'] as $category) {
2037             if ($category['parent']) {
2038                 if (!$DB->record_exists('course_categories', array('id' => $category['parent']))) {
2039                     throw new moodle_exception('unknowcategory');
2040                 }
2041                 $context = context_coursecat::instance($category['parent']);
2042             } else {
2043                 $context = context_system::instance();
2044             }
2045             self::validate_context($context);
2046             require_capability('moodle/category:manage', $context);
2048             // this will validate format and throw an exception if there are errors
2049             external_validate_format($category['descriptionformat']);
2051             $newcategory = core_course_category::create($category);
2052             $context = context_coursecat::instance($newcategory->id);
2054             $createdcategories[] = array(
2055                 'id' => $newcategory->id,
2056                 'name' => external_format_string($newcategory->name, $context),
2057             );
2058         }
2060         $transaction->allow_commit();
2062         return $createdcategories;
2063     }
2065     /**
2066      * Returns description of method parameters
2067      *
2068      * @return external_function_parameters
2069      * @since Moodle 2.3
2070      */
2071     public static function create_categories_returns() {
2072         return new external_multiple_structure(
2073             new external_single_structure(
2074                 array(
2075                     'id' => new external_value(PARAM_INT, 'new category id'),
2076                     'name' => new external_value(PARAM_RAW, 'new category name'),
2077                 )
2078             )
2079         );
2080     }
2082     /**
2083      * Returns description of method parameters
2084      *
2085      * @return external_function_parameters
2086      * @since Moodle 2.3
2087      */
2088     public static function update_categories_parameters() {
2089         return new external_function_parameters(
2090             array(
2091                 'categories' => new external_multiple_structure(
2092                     new external_single_structure(
2093                         array(
2094                             'id'       => new external_value(PARAM_INT, 'course id'),
2095                             'name' => new external_value(PARAM_TEXT, 'category name', VALUE_OPTIONAL),
2096                             'idnumber' => new external_value(PARAM_RAW, 'category id number', VALUE_OPTIONAL),
2097                             'parent' => new external_value(PARAM_INT, 'parent category id', VALUE_OPTIONAL),
2098                             'description' => new external_value(PARAM_RAW, 'category description', VALUE_OPTIONAL),
2099                             'descriptionformat' => new external_format_value('description', VALUE_DEFAULT),
2100                             'theme' => new external_value(PARAM_THEME,
2101                                     'the category theme. This option must be enabled on moodle', VALUE_OPTIONAL),
2102                         )
2103                     )
2104                 )
2105             )
2106         );
2107     }
2109     /**
2110      * Update categories
2111      *
2112      * @param array $categories The list of categories to update
2113      * @return null
2114      * @since Moodle 2.3
2115      */
2116     public static function update_categories($categories) {
2117         global $DB;
2119         // Validate parameters.
2120         $params = self::validate_parameters(self::update_categories_parameters(), array('categories' => $categories));
2122         $transaction = $DB->start_delegated_transaction();
2124         foreach ($params['categories'] as $cat) {
2125             $category = core_course_category::get($cat['id']);
2127             $categorycontext = context_coursecat::instance($cat['id']);
2128             self::validate_context($categorycontext);
2129             require_capability('moodle/category:manage', $categorycontext);
2131             // this will throw an exception if descriptionformat is not valid
2132             external_validate_format($cat['descriptionformat']);
2134             $category->update($cat);
2135         }
2137         $transaction->allow_commit();
2138     }
2140     /**
2141      * Returns description of method result value
2142      *
2143      * @return external_description
2144      * @since Moodle 2.3
2145      */
2146     public static function update_categories_returns() {
2147         return null;
2148     }
2150     /**
2151      * Returns description of method parameters
2152      *
2153      * @return external_function_parameters
2154      * @since Moodle 2.3
2155      */
2156     public static function delete_categories_parameters() {
2157         return new external_function_parameters(
2158             array(
2159                 'categories' => new external_multiple_structure(
2160                     new external_single_structure(
2161                         array(
2162                             'id' => new external_value(PARAM_INT, 'category id to delete'),
2163                             'newparent' => new external_value(PARAM_INT,
2164                                 'the parent category to move the contents to, if specified', VALUE_OPTIONAL),
2165                             'recursive' => new external_value(PARAM_BOOL, '1: recursively delete all contents inside this
2166                                 category, 0 (default): move contents to newparent or current parent category (except if parent is root)', VALUE_DEFAULT, 0)
2167                         )
2168                     )
2169                 )
2170             )
2171         );
2172     }
2174     /**
2175      * Delete categories
2176      *
2177      * @param array $categories A list of category ids
2178      * @return array
2179      * @since Moodle 2.3
2180      */
2181     public static function delete_categories($categories) {
2182         global $CFG, $DB;
2183         require_once($CFG->dirroot . "/course/lib.php");
2185         // Validate parameters.
2186         $params = self::validate_parameters(self::delete_categories_parameters(), array('categories' => $categories));
2188         $transaction = $DB->start_delegated_transaction();
2190         foreach ($params['categories'] as $category) {
2191             $deletecat = core_course_category::get($category['id'], MUST_EXIST);
2192             $context = context_coursecat::instance($deletecat->id);
2193             require_capability('moodle/category:manage', $context);
2194             self::validate_context($context);
2195             self::validate_context(get_category_or_system_context($deletecat->parent));
2197             if ($category['recursive']) {
2198                 // If recursive was specified, then we recursively delete the category's contents.
2199                 if ($deletecat->can_delete_full()) {
2200                     $deletecat->delete_full(false);
2201                 } else {
2202                     throw new moodle_exception('youcannotdeletecategory', '', '', $deletecat->get_formatted_name());
2203                 }
2204             } else {
2205                 // In this situation, we don't delete the category's contents, we either move it to newparent or parent.
2206                 // If the parent is the root, moving is not supported (because a course must always be inside a category).
2207                 // We must move to an existing category.
2208                 if (!empty($category['newparent'])) {
2209                     $newparentcat = core_course_category::get($category['newparent']);
2210                 } else {
2211                     $newparentcat = core_course_category::get($deletecat->parent);
2212                 }
2214                 // This operation is not allowed. We must move contents to an existing category.
2215                 if (!$newparentcat->id) {
2216                     throw new moodle_exception('movecatcontentstoroot');
2217                 }
2219                 self::validate_context(context_coursecat::instance($newparentcat->id));
2220                 if ($deletecat->can_move_content_to($newparentcat->id)) {
2221                     $deletecat->delete_move($newparentcat->id, false);
2222                 } else {
2223                     throw new moodle_exception('youcannotdeletecategory', '', '', $deletecat->get_formatted_name());
2224                 }
2225             }
2226         }
2228         $transaction->allow_commit();
2229     }
2231     /**
2232      * Returns description of method parameters
2233      *
2234      * @return external_function_parameters
2235      * @since Moodle 2.3
2236      */
2237     public static function delete_categories_returns() {
2238         return null;
2239     }
2241     /**
2242      * Describes the parameters for delete_modules.
2243      *
2244      * @return external_function_parameters
2245      * @since Moodle 2.5
2246      */
2247     public static function delete_modules_parameters() {
2248         return new external_function_parameters (
2249             array(
2250                 'cmids' => new external_multiple_structure(new external_value(PARAM_INT, 'course module ID',
2251                         VALUE_REQUIRED, '', NULL_NOT_ALLOWED), 'Array of course module IDs'),
2252             )
2253         );
2254     }
2256     /**
2257      * Deletes a list of provided module instances.
2258      *
2259      * @param array $cmids the course module ids
2260      * @since Moodle 2.5
2261      */
2262     public static function delete_modules($cmids) {
2263         global $CFG, $DB;
2265         // Require course file containing the course delete module function.
2266         require_once($CFG->dirroot . "/course/lib.php");
2268         // Clean the parameters.
2269         $params = self::validate_parameters(self::delete_modules_parameters(), array('cmids' => $cmids));
2271         // Keep track of the course ids we have performed a capability check on to avoid repeating.
2272         $arrcourseschecked = array();
2274         foreach ($params['cmids'] as $cmid) {
2275             // Get the course module.
2276             $cm = $DB->get_record('course_modules', array('id' => $cmid), '*', MUST_EXIST);
2278             // Check if we have not yet confirmed they have permission in this course.
2279             if (!in_array($cm->course, $arrcourseschecked)) {
2280                 // Ensure the current user has required permission in this course.
2281                 $context = context_course::instance($cm->course);
2282                 self::validate_context($context);
2283                 // Add to the array.
2284                 $arrcourseschecked[] = $cm->course;
2285             }
2287             // Ensure they can delete this module.
2288             $modcontext = context_module::instance($cm->id);
2289             require_capability('moodle/course:manageactivities', $modcontext);
2291             // Delete the module.
2292             course_delete_module($cm->id);
2293         }
2294     }
2296     /**
2297      * Describes the delete_modules return value.
2298      *
2299      * @return external_single_structure
2300      * @since Moodle 2.5
2301      */
2302     public static function delete_modules_returns() {
2303         return null;
2304     }
2306     /**
2307      * Returns description of method parameters
2308      *
2309      * @return external_function_parameters
2310      * @since Moodle 2.9
2311      */
2312     public static function view_course_parameters() {
2313         return new external_function_parameters(
2314             array(
2315                 'courseid' => new external_value(PARAM_INT, 'id of the course'),
2316                 'sectionnumber' => new external_value(PARAM_INT, 'section number', VALUE_DEFAULT, 0)
2317             )
2318         );
2319     }
2321     /**
2322      * Trigger the course viewed event.
2323      *
2324      * @param int $courseid id of course
2325      * @param int $sectionnumber sectionnumber (0, 1, 2...)
2326      * @return array of warnings and status result
2327      * @since Moodle 2.9
2328      * @throws moodle_exception
2329      */
2330     public static function view_course($courseid, $sectionnumber = 0) {
2331         global $CFG;
2332         require_once($CFG->dirroot . "/course/lib.php");
2334         $params = self::validate_parameters(self::view_course_parameters(),
2335                                             array(
2336                                                 'courseid' => $courseid,
2337                                                 'sectionnumber' => $sectionnumber
2338                                             ));
2340         $warnings = array();
2342         $course = get_course($params['courseid']);
2343         $context = context_course::instance($course->id);
2344         self::validate_context($context);
2346         if (!empty($params['sectionnumber'])) {
2348             // Get section details and check it exists.
2349             $modinfo = get_fast_modinfo($course);
2350             $coursesection = $modinfo->get_section_info($params['sectionnumber'], MUST_EXIST);
2352             // Check user is allowed to see it.
2353             if (!$coursesection->uservisible) {
2354                 require_capability('moodle/course:viewhiddensections', $context);
2355             }
2356         }
2358         course_view($context, $params['sectionnumber']);
2360         $result = array();
2361         $result['status'] = true;
2362         $result['warnings'] = $warnings;
2363         return $result;
2364     }
2366     /**
2367      * Returns description of method result value
2368      *
2369      * @return external_description
2370      * @since Moodle 2.9
2371      */
2372     public static function view_course_returns() {
2373         return new external_single_structure(
2374             array(
2375                 'status' => new external_value(PARAM_BOOL, 'status: true if success'),
2376                 'warnings' => new external_warnings()
2377             )
2378         );
2379     }
2381     /**
2382      * Returns description of method parameters
2383      *
2384      * @return external_function_parameters
2385      * @since Moodle 3.0
2386      */
2387     public static function search_courses_parameters() {
2388         return new external_function_parameters(
2389             array(
2390                 'criterianame'  => new external_value(PARAM_ALPHA, 'criteria name
2391                                                         (search, modulelist (only admins), blocklist (only admins), tagid)'),
2392                 'criteriavalue' => new external_value(PARAM_RAW, 'criteria value'),
2393                 'page'          => new external_value(PARAM_INT, 'page number (0 based)', VALUE_DEFAULT, 0),
2394                 'perpage'       => new external_value(PARAM_INT, 'items per page', VALUE_DEFAULT, 0),
2395                 'requiredcapabilities' => new external_multiple_structure(
2396                     new external_value(PARAM_CAPABILITY, 'Capability string used to filter courses by permission'),
2397                     'Optional list of required capabilities (used to filter the list)', VALUE_DEFAULT, array()
2398                 ),
2399                 'limittoenrolled' => new external_value(PARAM_BOOL, 'limit to enrolled courses', VALUE_DEFAULT, 0),
2400                 'onlywithcompletion' => new external_value(PARAM_BOOL, 'limit to courses where completion is enabled',
2401                     VALUE_DEFAULT, 0),
2402             )
2403         );
2404     }
2406     /**
2407      * Return the course information that is public (visible by every one)
2408      *
2409      * @param  core_course_list_element $course        course in list object
2410      * @param  stdClass       $coursecontext course context object
2411      * @return array the course information
2412      * @since  Moodle 3.2
2413      */
2414     protected static function get_course_public_information(core_course_list_element $course, $coursecontext) {
2416         static $categoriescache = array();
2418         // Category information.
2419         if (!array_key_exists($course->category, $categoriescache)) {
2420             $categoriescache[$course->category] = core_course_category::get($course->category, IGNORE_MISSING);
2421         }
2422         $category = $categoriescache[$course->category];
2424         // Retrieve course overview used files.
2425         $files = array();
2426         foreach ($course->get_course_overviewfiles() as $file) {
2427             $fileurl = moodle_url::make_webservice_pluginfile_url($file->get_contextid(), $file->get_component(),
2428                                                                     $file->get_filearea(), null, $file->get_filepath(),
2429                                                                     $file->get_filename())->out(false);
2430             $files[] = array(
2431                 'filename' => $file->get_filename(),
2432                 'fileurl' => $fileurl,
2433                 'filesize' => $file->get_filesize(),
2434                 'filepath' => $file->get_filepath(),
2435                 'mimetype' => $file->get_mimetype(),
2436                 'timemodified' => $file->get_timemodified(),
2437             );
2438         }
2440         // Retrieve the course contacts,
2441         // we need here the users fullname since if we are not enrolled can be difficult to obtain them via other Web Services.
2442         $coursecontacts = array();
2443         foreach ($course->get_course_contacts() as $contact) {
2444              $coursecontacts[] = array(
2445                 'id' => $contact['user']->id,
2446                 'fullname' => $contact['username'],
2447                 'roles' => array_map(function($role){
2448                     return array('id' => $role->id, 'name' => $role->displayname);
2449                 }, $contact['roles']),
2450                 'role' => array('id' => $contact['role']->id, 'name' => $contact['role']->displayname),
2451                 'rolename' => $contact['rolename']
2452              );
2453         }
2455         // Allowed enrolment methods (maybe we can self-enrol).
2456         $enroltypes = array();
2457         $instances = enrol_get_instances($course->id, true);
2458         foreach ($instances as $instance) {
2459             $enroltypes[] = $instance->enrol;
2460         }
2462         // Format summary.
2463         list($summary, $summaryformat) =
2464             external_format_text($course->summary, $course->summaryformat, $coursecontext->id, 'course', 'summary', null);
2466         $categoryname = '';
2467         if (!empty($category)) {
2468             $categoryname = external_format_string($category->name, $category->get_context());
2469         }
2471         $displayname = get_course_display_name_for_list($course);
2472         $coursereturns = array();
2473         $coursereturns['id']                = $course->id;
2474         $coursereturns['fullname']          = external_format_string($course->fullname, $coursecontext->id);
2475         $coursereturns['displayname']       = external_format_string($displayname, $coursecontext->id);
2476         $coursereturns['shortname']         = external_format_string($course->shortname, $coursecontext->id);
2477         $coursereturns['categoryid']        = $course->category;
2478         $coursereturns['categoryname']      = $categoryname;
2479         $coursereturns['summary']           = $summary;
2480         $coursereturns['summaryformat']     = $summaryformat;
2481         $coursereturns['summaryfiles']      = external_util::get_area_files($coursecontext->id, 'course', 'summary', false, false);
2482         $coursereturns['overviewfiles']     = $files;
2483         $coursereturns['contacts']          = $coursecontacts;
2484         $coursereturns['enrollmentmethods'] = $enroltypes;
2485         $coursereturns['sortorder']         = $course->sortorder;
2487         $handler = core_course\customfield\course_handler::create();
2488         if ($customfields = $handler->export_instance_data($course->id)) {
2489             $coursereturns['customfields'] = [];
2490             foreach ($customfields as $data) {
2491                 $coursereturns['customfields'][] = [
2492                     'type' => $data->get_type(),
2493                     'value' => $data->get_value(),
2494                     'valueraw' => $data->get_data_controller()->get_value(),
2495                     'name' => $data->get_name(),
2496                     'shortname' => $data->get_shortname()
2497                 ];
2498             }
2499         }
2501         return $coursereturns;
2502     }
2504     /**
2505      * Search courses following the specified criteria.
2506      *
2507      * @param string $criterianame  Criteria name (search, modulelist (only admins), blocklist (only admins), tagid)
2508      * @param string $criteriavalue Criteria value
2509      * @param int $page             Page number (for pagination)
2510      * @param int $perpage          Items per page
2511      * @param array $requiredcapabilities Optional list of required capabilities (used to filter the list).
2512      * @param int $limittoenrolled  Limit to only enrolled courses
2513      * @param int onlywithcompletion Limit to only courses where completion is enabled
2514      * @return array of course objects and warnings
2515      * @since Moodle 3.0
2516      * @throws moodle_exception
2517      */
2518     public static function search_courses($criterianame,
2519                                           $criteriavalue,
2520                                           $page=0,
2521                                           $perpage=0,
2522                                           $requiredcapabilities=array(),
2523                                           $limittoenrolled=0,
2524                                           $onlywithcompletion=0) {
2525         global $CFG;
2527         $warnings = array();
2529         $parameters = array(
2530             'criterianame'  => $criterianame,
2531             'criteriavalue' => $criteriavalue,
2532             'page'          => $page,
2533             'perpage'       => $perpage,
2534             'requiredcapabilities' => $requiredcapabilities,
2535             'limittoenrolled' => $limittoenrolled,
2536             'onlywithcompletion' => $onlywithcompletion
2537         );
2538         $params = self::validate_parameters(self::search_courses_parameters(), $parameters);
2539         self::validate_context(context_system::instance());
2541         $allowedcriterianames = array('search', 'modulelist', 'blocklist', 'tagid');
2542         if (!in_array($params['criterianame'], $allowedcriterianames)) {
2543             throw new invalid_parameter_exception('Invalid value for criterianame parameter (value: '.$params['criterianame'].'),' .
2544                 'allowed values are: '.implode(',', $allowedcriterianames));
2545         }
2547         if ($params['criterianame'] == 'modulelist' or $params['criterianame'] == 'blocklist') {
2548             require_capability('moodle/site:config', context_system::instance());
2549         }
2551         $paramtype = array(
2552             'search' => PARAM_RAW,
2553             'modulelist' => PARAM_PLUGIN,
2554             'blocklist' => PARAM_INT,
2555             'tagid' => PARAM_INT
2556         );
2557         $params['criteriavalue'] = clean_param($params['criteriavalue'], $paramtype[$params['criterianame']]);
2559         // Prepare the search API options.
2560         $searchcriteria = array();
2561         $searchcriteria[$params['criterianame']] = $params['criteriavalue'];
2562         if ($params['onlywithcompletion']) {
2563             $searchcriteria['onlywithcompletion'] = true;
2564         }
2566         $options = array();
2567         if ($params['perpage'] != 0) {
2568             $offset = $params['page'] * $params['perpage'];
2569             $options = array('offset' => $offset, 'limit' => $params['perpage']);
2570         }
2572         // Search the courses.
2573         $courses = core_course_category::search_courses($searchcriteria, $options, $params['requiredcapabilities']);
2574         $totalcount = core_course_category::search_courses_count($searchcriteria, $options, $params['requiredcapabilities']);
2576         if (!empty($limittoenrolled)) {
2577             // Get the courses where the current user has access.
2578             $enrolled = enrol_get_my_courses(array('id', 'cacherev'));
2579         }
2581         $finalcourses = array();
2582         $categoriescache = array();
2584         foreach ($courses as $course) {
2585             if (!empty($limittoenrolled)) {
2586                 // Filter out not enrolled courses.
2587                 if (!isset($enrolled[$course->id])) {
2588                     $totalcount--;
2589                     continue;
2590                 }
2591             }
2593             $coursecontext = context_course::instance($course->id);
2595             $finalcourses[] = self::get_course_public_information($course, $coursecontext);
2596         }
2598         return array(
2599             'total' => $totalcount,
2600             'courses' => $finalcourses,
2601             'warnings' => $warnings
2602         );
2603     }
2605     /**
2606      * Returns a course structure definition
2607      *
2608      * @param  boolean $onlypublicdata set to true, to retrieve only fields viewable by anyone when the course is visible
2609      * @return array the course structure
2610      * @since  Moodle 3.2
2611      */
2612     protected static function get_course_structure($onlypublicdata = true) {
2613         $coursestructure = array(
2614             'id' => new external_value(PARAM_INT, 'course id'),
2615             'fullname' => new external_value(PARAM_RAW, 'course full name'),
2616             'displayname' => new external_value(PARAM_RAW, 'course display name'),
2617             'shortname' => new external_value(PARAM_RAW, 'course short name'),
2618             'categoryid' => new external_value(PARAM_INT, 'category id'),
2619             'categoryname' => new external_value(PARAM_RAW, 'category name'),
2620             'sortorder' => new external_value(PARAM_INT, 'Sort order in the category', VALUE_OPTIONAL),
2621             'summary' => new external_value(PARAM_RAW, 'summary'),
2622             'summaryformat' => new external_format_value('summary'),
2623             'summaryfiles' => new external_files('summary files in the summary field', VALUE_OPTIONAL),
2624             'overviewfiles' => new external_files('additional overview files attached to this course'),
2625             'contacts' => new external_multiple_structure(
2626                 new external_single_structure(
2627                     array(
2628                         'id' => new external_value(PARAM_INT, 'contact user id'),
2629                         'fullname'  => new external_value(PARAM_NOTAGS, 'contact user fullname'),
2630                     )
2631                 ),
2632                 'contact users'
2633             ),
2634             'enrollmentmethods' => new external_multiple_structure(
2635                 new external_value(PARAM_PLUGIN, 'enrollment method'),
2636                 'enrollment methods list'
2637             ),
2638             'customfields' => new external_multiple_structure(
2639                 new external_single_structure(
2640                     array(
2641                         'name' => new external_value(PARAM_RAW, 'The name of the custom field'),
2642                         'shortname' => new external_value(PARAM_RAW,
2643                             'The shortname of the custom field - to be able to build the field class in the code'),
2644                         'type'  => new external_value(PARAM_ALPHANUMEXT,
2645                             'The type of the custom field - text field, checkbox...'),
2646                         'valueraw' => new external_value(PARAM_RAW, 'The raw value of the custom field'),
2647                         'value' => new external_value(PARAM_RAW, 'The value of the custom field'),
2648                     )
2649                 ), 'Custom fields', VALUE_OPTIONAL),
2650         );
2652         if (!$onlypublicdata) {
2653             $extra = array(
2654                 'idnumber' => new external_value(PARAM_RAW, 'Id number', VALUE_OPTIONAL),
2655                 'format' => new external_value(PARAM_PLUGIN, 'Course format: weeks, topics, social, site,..', VALUE_OPTIONAL),
2656                 'showgrades' => new external_value(PARAM_INT, '1 if grades are shown, otherwise 0', VALUE_OPTIONAL),
2657                 'newsitems' => new external_value(PARAM_INT, 'Number of recent items appearing on the course page', VALUE_OPTIONAL),
2658                 'startdate' => new external_value(PARAM_INT, 'Timestamp when the course start', VALUE_OPTIONAL),
2659                 'enddate' => new external_value(PARAM_INT, 'Timestamp when the course end', VALUE_OPTIONAL),
2660                 'maxbytes' => new external_value(PARAM_INT, 'Largest size of file that can be uploaded into', VALUE_OPTIONAL),
2661                 'showreports' => new external_value(PARAM_INT, 'Are activity report shown (yes = 1, no =0)', VALUE_OPTIONAL),
2662                 'visible' => new external_value(PARAM_INT, '1: available to student, 0:not available', VALUE_OPTIONAL),
2663                 'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible', VALUE_OPTIONAL),
2664                 'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no', VALUE_OPTIONAL),
2665                 'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id', VALUE_OPTIONAL),
2666                 'enablecompletion' => new external_value(PARAM_INT, 'Completion enabled? 1: yes 0: no', VALUE_OPTIONAL),
2667                 'completionnotify' => new external_value(PARAM_INT, '1: yes 0: no', VALUE_OPTIONAL),
2668                 'lang' => new external_value(PARAM_SAFEDIR, 'Forced course language', VALUE_OPTIONAL),
2669                 'theme' => new external_value(PARAM_PLUGIN, 'Fame of the forced theme', VALUE_OPTIONAL),
2670                 'marker' => new external_value(PARAM_INT, 'Current course marker', VALUE_OPTIONAL),
2671                 'legacyfiles' => new external_value(PARAM_INT, 'If legacy files are enabled', VALUE_OPTIONAL),
2672                 'calendartype' => new external_value(PARAM_PLUGIN, 'Calendar type', VALUE_OPTIONAL),
2673                 'timecreated' => new external_value(PARAM_INT, 'Time when the course was created', VALUE_OPTIONAL),
2674                 'timemodified' => new external_value(PARAM_INT, 'Last time  the course was updated', VALUE_OPTIONAL),
2675                 'requested' => new external_value(PARAM_INT, 'If is a requested course', VALUE_OPTIONAL),
2676                 'cacherev' => new external_value(PARAM_INT, 'Cache revision number', VALUE_OPTIONAL),
2677                 'filters' => new external_multiple_structure(
2678                     new external_single_structure(
2679                         array(
2680                             'filter'  => new external_value(PARAM_PLUGIN, 'Filter plugin name'),
2681                             'localstate' => new external_value(PARAM_INT, 'Filter state: 1 for on, -1 for off, 0 if inherit'),
2682                             'inheritedstate' => new external_value(PARAM_INT, '1 or 0 to use when localstate is set to inherit'),
2683                         )
2684                     ),
2685                     'Course filters', VALUE_OPTIONAL
2686                 ),
2687                 'courseformatoptions' => new external_multiple_structure(
2688                     new external_single_structure(
2689                         array(
2690                             'name' => new external_value(PARAM_RAW, 'Course format option name.'),
2691                             'value' => new external_value(PARAM_RAW, 'Course format option value.'),
2692                         )
2693                     ),
2694                     'Additional options for particular course format.', VALUE_OPTIONAL
2695                 ),
2696             );
2697             $coursestructure = array_merge($coursestructure, $extra);
2698         }
2699         return new external_single_structure($coursestructure);
2700     }
2702     /**
2703      * Returns description of method result value
2704      *
2705      * @return external_description
2706      * @since Moodle 3.0
2707      */
2708     public static function search_courses_returns() {
2709         return new external_single_structure(
2710             array(
2711                 'total' => new external_value(PARAM_INT, 'total course count'),
2712                 'courses' => new external_multiple_structure(self::get_course_structure(), 'course'),
2713                 'warnings' => new external_warnings()
2714             )
2715         );
2716     }
2718     /**
2719      * Returns description of method parameters
2720      *
2721      * @return external_function_parameters
2722      * @since Moodle 3.0
2723      */
2724     public static function get_course_module_parameters() {
2725         return new external_function_parameters(
2726             array(
2727                 'cmid' => new external_value(PARAM_INT, 'The course module id')
2728             )
2729         );
2730     }
2732     /**
2733      * Return information about a course module.
2734      *
2735      * @param int $cmid the course module id
2736      * @return array of warnings and the course module
2737      * @since Moodle 3.0
2738      * @throws moodle_exception
2739      */
2740     public static function get_course_module($cmid) {
2741         global $CFG, $DB;
2743         $params = self::validate_parameters(self::get_course_module_parameters(), array('cmid' => $cmid));
2744         $warnings = array();
2746         $cm = get_coursemodule_from_id(null, $params['cmid'], 0, true, MUST_EXIST);
2747         $context = context_module::instance($cm->id);
2748         self::validate_context($context);
2750         // If the user has permissions to manage the activity, return all the information.
2751         if (has_capability('moodle/course:manageactivities', $context)) {
2752             require_once($CFG->dirroot . '/course/modlib.php');
2753             require_once($CFG->libdir . '/gradelib.php');
2755             $info = $cm;
2756             // Get the extra information: grade, advanced grading and outcomes data.
2757             $course = get_course($cm->course);
2758             list($newcm, $newcontext, $module, $extrainfo, $cw) = get_moduleinfo_data($cm, $course);
2759             // Grades.
2760             $gradeinfo = array('grade', 'gradepass', 'gradecat');
2761             foreach ($gradeinfo as $gfield) {
2762                 if (isset($extrainfo->{$gfield})) {
2763                     $info->{$gfield} = $extrainfo->{$gfield};
2764                 }
2765             }
2766             if (isset($extrainfo->grade) and $extrainfo->grade < 0) {
2767                 $info->scale = $DB->get_field('scale', 'scale', array('id' => abs($extrainfo->grade)));
2768             }
2769             // Advanced grading.
2770             if (isset($extrainfo->_advancedgradingdata)) {
2771                 $info->advancedgrading = array();
2772                 foreach ($extrainfo as $key => $val) {
2773                     if (strpos($key, 'advancedgradingmethod_') === 0) {
2774                         $info->advancedgrading[] = array(
2775                             'area' => str_replace('advancedgradingmethod_', '', $key),
2776                             'method' => $val
2777                         );
2778                     }
2779                 }
2780             }
2781             // Outcomes.
2782             foreach ($extrainfo as $key => $val) {
2783                 if (strpos($key, 'outcome_') === 0) {
2784                     if (!isset($info->outcomes)) {
2785                         $info->outcomes = array();
2786                     }
2787                     $id = str_replace('outcome_', '', $key);
2788                     $outcome = grade_outcome::fetch(array('id' => $id));
2789                     $scaleitems = $outcome->load_scale();
2790                     $info->outcomes[] = array(
2791                         'id' => $id,
2792                         'name' => external_format_string($outcome->get_name(), $context->id),
2793                         'scale' => $scaleitems->scale
2794                     );
2795                 }
2796             }
2797         } else {
2798             // Return information is safe to show to any user.
2799             $info = new stdClass();
2800             $info->id = $cm->id;
2801             $info->course = $cm->course;
2802             $info->module = $cm->module;
2803             $info->modname = $cm->modname;
2804             $info->instance = $cm->instance;
2805             $info->section = $cm->section;
2806             $info->sectionnum = $cm->sectionnum;
2807             $info->groupmode = $cm->groupmode;
2808             $info->groupingid = $cm->groupingid;
2809             $info->completion = $cm->completion;
2810         }
2811         // Format name.
2812         $info->name = external_format_string($cm->name, $context->id);
2813         $result = array();
2814         $result['cm'] = $info;
2815         $result['warnings'] = $warnings;
2816         return $result;
2817     }
2819     /**
2820      * Returns description of method result value
2821      *
2822      * @return external_description
2823      * @since Moodle 3.0
2824      */
2825     public static function get_course_module_returns() {
2826         return new external_single_structure(
2827             array(
2828                 'cm' => new external_single_structure(
2829                     array(
2830                         'id' => new external_value(PARAM_INT, 'The course module id'),
2831                         'course' => new external_value(PARAM_INT, 'The course id'),
2832                         'module' => new external_value(PARAM_INT, 'The module type id'),
2833                         'name' => new external_value(PARAM_RAW, 'The activity name'),
2834                         'modname' => new external_value(PARAM_COMPONENT, 'The module component name (forum, assign, etc..)'),
2835                         'instance' => new external_value(PARAM_INT, 'The activity instance id'),
2836                         'section' => new external_value(PARAM_INT, 'The module section id'),
2837                         'sectionnum' => new external_value(PARAM_INT, 'The module section number'),
2838                         'groupmode' => new external_value(PARAM_INT, 'Group mode'),
2839                         'groupingid' => new external_value(PARAM_INT, 'Grouping id'),
2840                         'completion' => new external_value(PARAM_INT, 'If completion is enabled'),
2841                         'idnumber' => new external_value(PARAM_RAW, 'Module id number', VALUE_OPTIONAL),
2842                         'added' => new external_value(PARAM_INT, 'Time added', VALUE_OPTIONAL),
2843                         'score' => new external_value(PARAM_INT, 'Score', VALUE_OPTIONAL),
2844                         'indent' => new external_value(PARAM_INT, 'Indentation', VALUE_OPTIONAL),
2845                         'visible' => new external_value(PARAM_INT, 'If visible', VALUE_OPTIONAL),
2846                         'visibleoncoursepage' => new external_value(PARAM_INT, 'If visible on course page', VALUE_OPTIONAL),
2847                         'visibleold' => new external_value(PARAM_INT, 'Visible old', VALUE_OPTIONAL),
2848                         'completiongradeitemnumber' => new external_value(PARAM_INT, 'Completion grade item', VALUE_OPTIONAL),
2849                         'completionview' => new external_value(PARAM_INT, 'Completion view setting', VALUE_OPTIONAL),
2850                         'completionexpected' => new external_value(PARAM_INT, 'Completion time expected', VALUE_OPTIONAL),
2851                         'showdescription' => new external_value(PARAM_INT, 'If the description is showed', VALUE_OPTIONAL),
2852                         'availability' => new external_value(PARAM_RAW, 'Availability settings', VALUE_OPTIONAL),
2853                         'grade' => new external_value(PARAM_FLOAT, 'Grade (max value or scale id)', VALUE_OPTIONAL),
2854                         'scale' => new external_value(PARAM_TEXT, 'Scale items (if used)', VALUE_OPTIONAL),
2855                         'gradepass' => new external_value(PARAM_RAW, 'Grade to pass (float)', VALUE_OPTIONAL),
2856                         'gradecat' => new external_value(PARAM_INT, 'Grade category', VALUE_OPTIONAL),
2857                         'advancedgrading' => new external_multiple_structure(
2858                             new external_single_structure(
2859                                 array(
2860                                     'area' => new external_value(PARAM_AREA, 'Gradable area name'),
2861                                     'method'  => new external_value(PARAM_COMPONENT, 'Grading method'),
2862                                 )
2863                             ),
2864                             'Advanced grading settings', VALUE_OPTIONAL
2865                         ),
2866                         'outcomes' => new external_multiple_structure(
2867                             new external_single_structure(
2868                                 array(
2869                                     'id' => new external_value(PARAM_ALPHANUMEXT, 'Outcome id'),
2870                                     'name'  => new external_value(PARAM_RAW, 'Outcome full name'),
2871                                     'scale' => new external_value(PARAM_TEXT, 'Scale items')
2872                                 )
2873                             ),
2874                             'Outcomes information', VALUE_OPTIONAL
2875                         ),
2876                     )
2877                 ),
2878                 'warnings' => new external_warnings()
2879             )
2880         );
2881     }
2883     /**
2884      * Returns description of method parameters
2885      *
2886      * @return external_function_parameters
2887      * @since Moodle 3.0
2888      */
2889     public static function get_course_module_by_instance_parameters() {
2890         return new external_function_parameters(
2891             array(
2892                 'module' => new external_value(PARAM_COMPONENT, 'The module name'),
2893                 'instance' => new external_value(PARAM_INT, 'The module instance id')
2894             )
2895         );
2896     }
2898     /**
2899      * Return information about a course module.
2900      *
2901      * @param string $module the module name
2902      * @param int $instance the activity instance id
2903      * @return array of warnings and the course module
2904      * @since Moodle 3.0
2905      * @throws moodle_exception
2906      */
2907     public static function get_course_module_by_instance($module, $instance) {
2909         $params = self::validate_parameters(self::get_course_module_by_instance_parameters(),
2910                                             array(
2911                                                 'module' => $module,
2912                                                 'instance' => $instance,
2913                                             ));
2915         $warnings = array();
2916         $cm = get_coursemodule_from_instance($params['module'], $params['instance'], 0, false, MUST_EXIST);
2918         return self::get_course_module($cm->id);
2919     }
2921     /**
2922      * Returns description of method result value
2923      *
2924      * @return external_description
2925      * @since Moodle 3.0
2926      */
2927     public static function get_course_module_by_instance_returns() {
2928         return self::get_course_module_returns();
2929     }
2931     /**
2932      * Returns description of method parameters
2933      *
2934      * @return external_function_parameters
2935      * @since Moodle 3.2
2936      */
2937     public static function get_user_navigation_options_parameters() {
2938         return new external_function_parameters(
2939             array(
2940                 'courseids' => new external_multiple_structure(new external_value(PARAM_INT, 'Course id.')),
2941             )
2942         );
2943     }
2945     /**
2946      * Return a list of navigation options in a set of courses that are avaialable or not for the current user.
2947      *
2948      * @param array $courseids a list of course ids
2949      * @return array of warnings and the options availability
2950      * @since Moodle 3.2
2951      * @throws moodle_exception
2952      */
2953     public static function get_user_navigation_options($courseids) {
2954         global $CFG;
2955         require_once($CFG->dirroot . '/course/lib.php');
2957         // Parameter validation.
2958         $params = self::validate_parameters(self::get_user_navigation_options_parameters(), array('courseids' => $courseids));
2959         $courseoptions = array();
2961         list($courses, $warnings) = external_util::validate_courses($params['courseids'], array(), true);
2963         if (!empty($courses)) {
2964             foreach ($courses as $course) {
2965                 // Fix the context for the frontpage.
2966                 if ($course->id == SITEID) {
2967                     $course->context = context_system::instance();