Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

Differences Between: [Versions 310 and 400] [Versions 311 and 400] [Versions 39 and 400] [Versions 400 and 402] [Versions 400 and 403]

   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/>.
  16  
  17  
  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   */
  26  
  27  defined('MOODLE_INTERNAL') || die;
  28  
  29  use core_course\external\course_summary_exporter;
  30  use core_availability\info;
  31  
  32  
  33  require_once("$CFG->libdir/externallib.php");
  34  require_once (__DIR__ . "/lib.php");
  35  
  36  /**
  37   * Course external functions
  38   *
  39   * @package    core_course
  40   * @category   external
  41   * @copyright  2011 Jerome Mouneyrac
  42   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  43   * @since Moodle 2.2
  44   */
  45  class core_course_external extends external_api {
  46  
  47      /**
  48       * Returns description of method parameters
  49       *
  50       * @return external_function_parameters
  51       * @since Moodle 2.9 Options available
  52       * @since Moodle 2.2
  53       */
  54      public static function get_course_contents_parameters() {
  55          return new external_function_parameters(
  56                  array('courseid' => new external_value(PARAM_INT, 'course id'),
  57                        'options' => new external_multiple_structure (
  58                                new external_single_structure(
  59                                  array(
  60                                      'name' => new external_value(PARAM_ALPHANUM,
  61                                                  'The expected keys (value format) are:
  62                                                  excludemodules (bool) Do not return modules, return only the sections structure
  63                                                  excludecontents (bool) Do not return module contents (i.e: files inside a resource)
  64                                                  includestealthmodules (bool) Return stealth modules for students in a special
  65                                                      section (with id -1)
  66                                                  sectionid (int) Return only this section
  67                                                  sectionnumber (int) Return only this section with number (order)
  68                                                  cmid (int) Return only this module information (among the whole sections structure)
  69                                                  modname (string) Return only modules with this name "label, forum, etc..."
  70                                                  modid (int) Return only the module with this id (to be used with modname'),
  71                                      'value' => new external_value(PARAM_RAW, 'the value of the option,
  72                                                                      this param is personaly validated in the external function.')
  73                                )
  74                        ), 'Options, used since Moodle 2.9', VALUE_DEFAULT, array())
  75                  )
  76          );
  77      }
  78  
  79      /**
  80       * Get course contents
  81       *
  82       * @param int $courseid course id
  83       * @param array $options Options for filtering the results, used since Moodle 2.9
  84       * @return array
  85       * @since Moodle 2.9 Options available
  86       * @since Moodle 2.2
  87       */
  88      public static function get_course_contents($courseid, $options = array()) {
  89          global $CFG, $DB, $USER, $PAGE;
  90          require_once($CFG->dirroot . "/course/lib.php");
  91          require_once($CFG->libdir . '/completionlib.php');
  92  
  93          //validate parameter
  94          $params = self::validate_parameters(self::get_course_contents_parameters(),
  95                          array('courseid' => $courseid, 'options' => $options));
  96  
  97          $filters = array();
  98          if (!empty($params['options'])) {
  99  
 100              foreach ($params['options'] as $option) {
 101                  $name = trim($option['name']);
 102                  // Avoid duplicated options.
 103                  if (!isset($filters[$name])) {
 104                      switch ($name) {
 105                          case 'excludemodules':
 106                          case 'excludecontents':
 107                          case 'includestealthmodules':
 108                              $value = clean_param($option['value'], PARAM_BOOL);
 109                              $filters[$name] = $value;
 110                              break;
 111                          case 'sectionid':
 112                          case 'sectionnumber':
 113                          case 'cmid':
 114                          case 'modid':
 115                              $value = clean_param($option['value'], PARAM_INT);
 116                              if (is_numeric($value)) {
 117                                  $filters[$name] = $value;
 118                              } else {
 119                                  throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
 120                              }
 121                              break;
 122                          case 'modname':
 123                              $value = clean_param($option['value'], PARAM_PLUGIN);
 124                              if ($value) {
 125                                  $filters[$name] = $value;
 126                              } else {
 127                                  throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
 128                              }
 129                              break;
 130                          default:
 131                              throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
 132                      }
 133                  }
 134              }
 135          }
 136  
 137          //retrieve the course
 138          $course = $DB->get_record('course', array('id' => $params['courseid']), '*', MUST_EXIST);
 139  
 140          if ($course->id != SITEID) {
 141              // Check course format exist.
 142              if (!file_exists($CFG->dirroot . '/course/format/' . $course->format . '/lib.php')) {
 143                  throw new moodle_exception('cannotgetcoursecontents', 'webservice', '', null,
 144                                              get_string('courseformatnotfound', 'error', $course->format));
 145              } else {
 146                  require_once($CFG->dirroot . '/course/format/' . $course->format . '/lib.php');
 147              }
 148          }
 149  
 150          // now security checks
 151          $context = context_course::instance($course->id, IGNORE_MISSING);
 152          try {
 153              self::validate_context($context);
 154          } catch (Exception $e) {
 155              $exceptionparam = new stdClass();
 156              $exceptionparam->message = $e->getMessage();
 157              $exceptionparam->courseid = $course->id;
 158              throw new moodle_exception('errorcoursecontextnotvalid', 'webservice', '', $exceptionparam);
 159          }
 160  
 161          $canupdatecourse = has_capability('moodle/course:update', $context);
 162  
 163          //create return value
 164          $coursecontents = array();
 165  
 166          if ($canupdatecourse or $course->visible
 167                  or has_capability('moodle/course:viewhiddencourses', $context)) {
 168  
 169              //retrieve sections
 170              $modinfo = get_fast_modinfo($course);
 171              $sections = $modinfo->get_section_info_all();
 172              $courseformat = course_get_format($course);
 173              $coursenumsections = $courseformat->get_last_section_number();
 174              $stealthmodules = array();   // Array to keep all the modules available but not visible in a course section/topic.
 175  
 176              $completioninfo = new completion_info($course);
 177  
 178              //for each sections (first displayed to last displayed)
 179              $modinfosections = $modinfo->get_sections();
 180              foreach ($sections as $key => $section) {
 181  
 182                  // This becomes true when we are filtering and we found the value to filter with.
 183                  $sectionfound = false;
 184  
 185                  // Filter by section id.
 186                  if (!empty($filters['sectionid'])) {
 187                      if ($section->id != $filters['sectionid']) {
 188                          continue;
 189                      } else {
 190                          $sectionfound = true;
 191                      }
 192                  }
 193  
 194                  // Filter by section number. Note that 0 is a valid section number.
 195                  if (isset($filters['sectionnumber'])) {
 196                      if ($key != $filters['sectionnumber']) {
 197                          continue;
 198                      } else {
 199                          $sectionfound = true;
 200                      }
 201                  }
 202  
 203                  // reset $sectioncontents
 204                  $sectionvalues = array();
 205                  $sectionvalues['id'] = $section->id;
 206                  $sectionvalues['name'] = get_section_name($course, $section);
 207                  $sectionvalues['visible'] = $section->visible;
 208  
 209                  $options = (object) array('noclean' => true);
 210                  list($sectionvalues['summary'], $sectionvalues['summaryformat']) =
 211                          external_format_text($section->summary, $section->summaryformat,
 212                                  $context->id, 'course', 'section', $section->id, $options);
 213                  $sectionvalues['section'] = $section->section;
 214                  $sectionvalues['hiddenbynumsections'] = $section->section > $coursenumsections ? 1 : 0;
 215                  $sectionvalues['uservisible'] = $section->uservisible;
 216                  if (!empty($section->availableinfo)) {
 217                      $sectionvalues['availabilityinfo'] = \core_availability\info::format_info($section->availableinfo, $course);
 218                  }
 219  
 220                  $sectioncontents = array();
 221  
 222                  // For each module of the section.
 223                  if (empty($filters['excludemodules']) and !empty($modinfosections[$section->section])) {
 224                      foreach ($modinfosections[$section->section] as $cmid) {
 225                          $cm = $modinfo->cms[$cmid];
 226                          $cminfo = cm_info::create($cm);
 227                          $activitydates = \core\activity_dates::get_dates_for_module($cminfo, $USER->id);
 228  
 229                          // Stop here if the module is not visible to the user on the course main page:
 230                          // The user can't access the module and the user can't view the module on the course page.
 231                          if (!$cm->uservisible && !$cm->is_visible_on_course_page()) {
 232                              continue;
 233                          }
 234  
 235                          // This becomes true when we are filtering and we found the value to filter with.
 236                          $modfound = false;
 237  
 238                          // Filter by cmid.
 239                          if (!empty($filters['cmid'])) {
 240                              if ($cmid != $filters['cmid']) {
 241                                  continue;
 242                              } else {
 243                                  $modfound = true;
 244                              }
 245                          }
 246  
 247                          // Filter by module name and id.
 248                          if (!empty($filters['modname'])) {
 249                              if ($cm->modname != $filters['modname']) {
 250                                  continue;
 251                              } else if (!empty($filters['modid'])) {
 252                                  if ($cm->instance != $filters['modid']) {
 253                                      continue;
 254                                  } else {
 255                                      // Note that if we are only filtering by modname we don't break the loop.
 256                                      $modfound = true;
 257                                  }
 258                              }
 259                          }
 260  
 261                          $module = array();
 262  
 263                          $modcontext = context_module::instance($cm->id);
 264  
 265                          //common info (for people being able to see the module or availability dates)
 266                          $module['id'] = $cm->id;
 267                          $module['name'] = external_format_string($cm->name, $modcontext->id);
 268                          $module['instance'] = $cm->instance;
 269                          $module['contextid'] = $modcontext->id;
 270                          $module['modname'] = (string) $cm->modname;
 271                          $module['modplural'] = (string) $cm->modplural;
 272                          $module['modicon'] = $cm->get_icon_url()->out(false);
 273                          $module['indent'] = $cm->indent;
 274                          $module['onclick'] = $cm->onclick;
 275                          $module['afterlink'] = $cm->afterlink;
 276                          $module['customdata'] = json_encode($cm->customdata);
 277                          $module['completion'] = $cm->completion;
 278                          $module['downloadcontent'] = $cm->downloadcontent;
 279                          $module['noviewlink'] = plugin_supports('mod', $cm->modname, FEATURE_NO_VIEW_LINK, false);
 280                          $module['dates'] = $activitydates;
 281  
 282                          // Check module completion.
 283                          $completion = $completioninfo->is_enabled($cm);
 284                          if ($completion != COMPLETION_DISABLED) {
 285                              $exporter = new \core_completion\external\completion_info_exporter($course, $cm, $USER->id);
 286                              $renderer = $PAGE->get_renderer('core');
 287                              $modulecompletiondata = (array)$exporter->export($renderer);
 288                              $module['completiondata'] = $modulecompletiondata;
 289                          }
 290  
 291                          if (!empty($cm->showdescription) or $module['noviewlink']) {
 292                              // We want to use the external format. However from reading get_formatted_content(), $cm->content format is always FORMAT_HTML.
 293                              $options = array('noclean' => true);
 294                              list($module['description'], $descriptionformat) = external_format_text($cm->content,
 295                                  FORMAT_HTML, $modcontext->id, $cm->modname, 'intro', $cm->id, $options);
 296                          }
 297  
 298                          //url of the module
 299                          $url = $cm->url;
 300                          if ($url) { //labels don't have url
 301                              $module['url'] = $url->out(false);
 302                          }
 303  
 304                          $canviewhidden = has_capability('moodle/course:viewhiddenactivities',
 305                                              context_module::instance($cm->id));
 306                          //user that can view hidden module should know about the visibility
 307                          $module['visible'] = $cm->visible;
 308                          $module['visibleoncoursepage'] = $cm->visibleoncoursepage;
 309                          $module['uservisible'] = $cm->uservisible;
 310                          if (!empty($cm->availableinfo)) {
 311                              $module['availabilityinfo'] = \core_availability\info::format_info($cm->availableinfo, $course);
 312                          }
 313  
 314                          // Availability date (also send to user who can see hidden module).
 315                          if ($CFG->enableavailability && ($canviewhidden || $canupdatecourse)) {
 316                              $module['availability'] = $cm->availability;
 317                          }
 318  
 319                          // Return contents only if the user can access to the module.
 320                          if ($cm->uservisible) {
 321                              $baseurl = 'webservice/pluginfile.php';
 322  
 323                              // Call $modulename_export_contents (each module callback take care about checking the capabilities).
 324                              require_once($CFG->dirroot . '/mod/' . $cm->modname . '/lib.php');
 325                              $getcontentfunction = $cm->modname.'_export_contents';
 326                              if (function_exists($getcontentfunction)) {
 327                                  $contents = $getcontentfunction($cm, $baseurl);
 328                                  $module['contentsinfo'] = array(
 329                                      'filescount' => count($contents),
 330                                      'filessize' => 0,
 331                                      'lastmodified' => 0,
 332                                      'mimetypes' => array(),
 333                                  );
 334                                  foreach ($contents as $content) {
 335                                      // Check repository file (only main file).
 336                                      if (!isset($module['contentsinfo']['repositorytype'])) {
 337                                          $module['contentsinfo']['repositorytype'] =
 338                                              isset($content['repositorytype']) ? $content['repositorytype'] : '';
 339                                      }
 340                                      if (isset($content['filesize'])) {
 341                                          $module['contentsinfo']['filessize'] += $content['filesize'];
 342                                      }
 343                                      if (isset($content['timemodified']) &&
 344                                              ($content['timemodified'] > $module['contentsinfo']['lastmodified'])) {
 345  
 346                                          $module['contentsinfo']['lastmodified'] = $content['timemodified'];
 347                                      }
 348                                      if (isset($content['mimetype'])) {
 349                                          $module['contentsinfo']['mimetypes'][$content['mimetype']] = $content['mimetype'];
 350                                      }
 351                                  }
 352  
 353                                  if (empty($filters['excludecontents']) and !empty($contents)) {
 354                                      $module['contents'] = $contents;
 355                                  } else {
 356                                      $module['contents'] = array();
 357                                  }
 358                              }
 359                          }
 360  
 361                          // Assign result to $sectioncontents, there is an exception,
 362                          // stealth activities in non-visible sections for students go to a special section.
 363                          if (!empty($filters['includestealthmodules']) && !$section->uservisible && $cm->is_stealth()) {
 364                              $stealthmodules[] = $module;
 365                          } else {
 366                              $sectioncontents[] = $module;
 367                          }
 368  
 369                          // If we just did a filtering, break the loop.
 370                          if ($modfound) {
 371                              break;
 372                          }
 373  
 374                      }
 375                  }
 376                  $sectionvalues['modules'] = $sectioncontents;
 377  
 378                  // assign result to $coursecontents
 379                  $coursecontents[$key] = $sectionvalues;
 380  
 381                  // Break the loop if we are filtering.
 382                  if ($sectionfound) {
 383                      break;
 384                  }
 385              }
 386  
 387              // Now that we have iterated over all the sections and activities, check the visibility.
 388              // We didn't this before to be able to retrieve stealth activities.
 389              foreach ($coursecontents as $sectionnumber => $sectioncontents) {
 390                  $section = $sections[$sectionnumber];
 391  
 392                  if (!$courseformat->is_section_visible($section)) {
 393                      unset($coursecontents[$sectionnumber]);
 394                      continue;
 395                  }
 396  
 397                  // Remove section and modules information if the section is not visible for the user.
 398                  if (!$section->uservisible) {
 399                      $coursecontents[$sectionnumber]['modules'] = array();
 400                      // Remove summary information if the section is completely hidden only,
 401                      // even if the section is not user visible, the summary is always displayed among the availability information.
 402                      if (!$section->visible) {
 403                          $coursecontents[$sectionnumber]['summary'] = '';
 404                      }
 405                  }
 406              }
 407  
 408              // Include stealth modules in special section (without any info).
 409              if (!empty($stealthmodules)) {
 410                  $coursecontents[] = array(
 411                      'id' => -1,
 412                      'name' => '',
 413                      'summary' => '',
 414                      'summaryformat' => FORMAT_MOODLE,
 415                      'modules' => $stealthmodules
 416                  );
 417              }
 418  
 419          }
 420          return $coursecontents;
 421      }
 422  
 423      /**
 424       * Returns description of method result value
 425       *
 426       * @return external_description
 427       * @since Moodle 2.2
 428       */
 429      public static function get_course_contents_returns() {
 430          $completiondefinition = \core_completion\external\completion_info_exporter::get_read_structure(VALUE_DEFAULT, []);
 431  
 432          return new external_multiple_structure(
 433              new external_single_structure(
 434                  array(
 435                      'id' => new external_value(PARAM_INT, 'Section ID'),
 436                      'name' => new external_value(PARAM_RAW, 'Section name'),
 437                      'visible' => new external_value(PARAM_INT, 'is the section visible', VALUE_OPTIONAL),
 438                      'summary' => new external_value(PARAM_RAW, 'Section description'),
 439                      'summaryformat' => new external_format_value('summary'),
 440                      'section' => new external_value(PARAM_INT, 'Section number inside the course', VALUE_OPTIONAL),
 441                      'hiddenbynumsections' => new external_value(PARAM_INT, 'Whether is a section hidden in the course format',
 442                                                                  VALUE_OPTIONAL),
 443                      'uservisible' => new external_value(PARAM_BOOL, 'Is the section visible for the user?', VALUE_OPTIONAL),
 444                      'availabilityinfo' => new external_value(PARAM_RAW, 'Availability information.', VALUE_OPTIONAL),
 445                      'modules' => new external_multiple_structure(
 446                              new external_single_structure(
 447                                  array(
 448                                      'id' => new external_value(PARAM_INT, 'activity id'),
 449                                      'url' => new external_value(PARAM_URL, 'activity url', VALUE_OPTIONAL),
 450                                      'name' => new external_value(PARAM_RAW, 'activity module name'),
 451                                      'instance' => new external_value(PARAM_INT, 'instance id', VALUE_OPTIONAL),
 452                                      'contextid' => new external_value(PARAM_INT, 'Activity context id.', VALUE_OPTIONAL),
 453                                      'description' => new external_value(PARAM_RAW, 'activity description', VALUE_OPTIONAL),
 454                                      'visible' => new external_value(PARAM_INT, 'is the module visible', VALUE_OPTIONAL),
 455                                      'uservisible' => new external_value(PARAM_BOOL, 'Is the module visible for the user?',
 456                                          VALUE_OPTIONAL),
 457                                      'availabilityinfo' => new external_value(PARAM_RAW, 'Availability information.',
 458                                          VALUE_OPTIONAL),
 459                                      'visibleoncoursepage' => new external_value(PARAM_INT, 'is the module visible on course page',
 460                                          VALUE_OPTIONAL),
 461                                      'modicon' => new external_value(PARAM_URL, 'activity icon url'),
 462                                      'modname' => new external_value(PARAM_PLUGIN, 'activity module type'),
 463                                      'modplural' => new external_value(PARAM_TEXT, 'activity module plural name'),
 464                                      'availability' => new external_value(PARAM_RAW, 'module availability settings', VALUE_OPTIONAL),
 465                                      'indent' => new external_value(PARAM_INT, 'number of identation in the site'),
 466                                      'onclick' => new external_value(PARAM_RAW, 'Onclick action.', VALUE_OPTIONAL),
 467                                      'afterlink' => new external_value(PARAM_RAW, 'After link info to be displayed.',
 468                                          VALUE_OPTIONAL),
 469                                      'customdata' => new external_value(PARAM_RAW, 'Custom data (JSON encoded).', VALUE_OPTIONAL),
 470                                      'noviewlink' => new external_value(PARAM_BOOL, 'Whether the module has no view page',
 471                                          VALUE_OPTIONAL),
 472                                      'completion' => new external_value(PARAM_INT, 'Type of completion tracking:
 473                                          0 means none, 1 manual, 2 automatic.', VALUE_OPTIONAL),
 474                                      'completiondata' => $completiondefinition,
 475                                      'downloadcontent' => new external_value(PARAM_INT, 'The download content value', VALUE_OPTIONAL),
 476                                      'dates' => new external_multiple_structure(
 477                                          new external_single_structure(
 478                                              array(
 479                                                  'label' => new external_value(PARAM_TEXT, 'date label'),
 480                                                  'timestamp' => new external_value(PARAM_INT, 'date timestamp'),
 481                                                  'relativeto' => new external_value(PARAM_INT, 'relative date timestamp',
 482                                                      VALUE_OPTIONAL),
 483                                                  'dataid' => new external_value(PARAM_NOTAGS, 'cm data id', VALUE_OPTIONAL),
 484                                              )
 485                                          ),
 486                                          'Course dates',
 487                                          VALUE_DEFAULT,
 488                                          []
 489                                      ),
 490                                      'contents' => new external_multiple_structure(
 491                                            new external_single_structure(
 492                                                array(
 493                                                    // content info
 494                                                    'type'=> new external_value(PARAM_TEXT, 'a file or a folder or external link'),
 495                                                    'filename'=> new external_value(PARAM_FILE, 'filename'),
 496                                                    'filepath'=> new external_value(PARAM_PATH, 'filepath'),
 497                                                    'filesize'=> new external_value(PARAM_INT, 'filesize'),
 498                                                    'fileurl' => new external_value(PARAM_URL, 'downloadable file url', VALUE_OPTIONAL),
 499                                                    'content' => new external_value(PARAM_RAW, 'Raw content, will be used when type is content', VALUE_OPTIONAL),
 500                                                    'timecreated' => new external_value(PARAM_INT, 'Time created'),
 501                                                    'timemodified' => new external_value(PARAM_INT, 'Time modified'),
 502                                                    'sortorder' => new external_value(PARAM_INT, 'Content sort order'),
 503                                                    'mimetype' => new external_value(PARAM_RAW, 'File mime type.', VALUE_OPTIONAL),
 504                                                    'isexternalfile' => new external_value(PARAM_BOOL, 'Whether is an external file.',
 505                                                      VALUE_OPTIONAL),
 506                                                    'repositorytype' => new external_value(PARAM_PLUGIN, 'The repository type for external files.',
 507                                                      VALUE_OPTIONAL),
 508  
 509                                                    // copyright related info
 510                                                    'userid' => new external_value(PARAM_INT, 'User who added this content to moodle'),
 511                                                    'author' => new external_value(PARAM_TEXT, 'Content owner'),
 512                                                    'license' => new external_value(PARAM_TEXT, 'Content license'),
 513                                                    'tags' => new external_multiple_structure(
 514                                                         \core_tag\external\tag_item_exporter::get_read_structure(), 'Tags',
 515                                                              VALUE_OPTIONAL
 516                                                     ),
 517                                                )
 518                                            ), 'Course contents', VALUE_DEFAULT, array()
 519                                        ),
 520                                      'contentsinfo' => new external_single_structure(
 521                                          array(
 522                                              'filescount' => new external_value(PARAM_INT, 'Total number of files.'),
 523                                              'filessize' => new external_value(PARAM_INT, 'Total files size.'),
 524                                              'lastmodified' => new external_value(PARAM_INT, 'Last time files were modified.'),
 525                                              'mimetypes' => new external_multiple_structure(
 526                                                  new external_value(PARAM_RAW, 'File mime type.'),
 527                                                  'Files mime types.'
 528                                              ),
 529                                              'repositorytype' => new external_value(PARAM_PLUGIN, 'The repository type for
 530                                                  the main file.', VALUE_OPTIONAL),
 531                                          ), 'Contents summary information.', VALUE_OPTIONAL
 532                                      ),
 533                                  )
 534                              ), 'list of module'
 535                      )
 536                  )
 537              )
 538          );
 539      }
 540  
 541      /**
 542       * Returns description of method parameters
 543       *
 544       * @return external_function_parameters
 545       * @since Moodle 2.3
 546       */
 547      public static function get_courses_parameters() {
 548          return new external_function_parameters(
 549                  array('options' => new external_single_structure(
 550                              array('ids' => new external_multiple_structure(
 551                                          new external_value(PARAM_INT, 'Course id')
 552                                          , 'List of course id. If empty return all courses
 553                                              except front page course.',
 554                                          VALUE_OPTIONAL)
 555                              ), 'options - operator OR is used', VALUE_DEFAULT, array())
 556                  )
 557          );
 558      }
 559  
 560      /**
 561       * Get courses
 562       *
 563       * @param array $options It contains an array (list of ids)
 564       * @return array
 565       * @since Moodle 2.2
 566       */
 567      public static function get_courses($options = array()) {
 568          global $CFG, $DB;
 569          require_once($CFG->dirroot . "/course/lib.php");
 570  
 571          //validate parameter
 572          $params = self::validate_parameters(self::get_courses_parameters(),
 573                          array('options' => $options));
 574  
 575          //retrieve courses
 576          if (!array_key_exists('ids', $params['options'])
 577                  or empty($params['options']['ids'])) {
 578              $courses = $DB->get_records('course');
 579          } else {
 580              $courses = $DB->get_records_list('course', 'id', $params['options']['ids']);
 581          }
 582  
 583          //create return value
 584          $coursesinfo = array();
 585          foreach ($courses as $course) {
 586  
 587              // now security checks
 588              $context = context_course::instance($course->id, IGNORE_MISSING);
 589              $courseformatoptions = course_get_format($course)->get_format_options();
 590              try {
 591                  self::validate_context($context);
 592              } catch (Exception $e) {
 593                  $exceptionparam = new stdClass();
 594                  $exceptionparam->message = $e->getMessage();
 595                  $exceptionparam->courseid = $course->id;
 596                  throw new moodle_exception('errorcoursecontextnotvalid', 'webservice', '', $exceptionparam);
 597              }
 598              if ($course->id != SITEID) {
 599                  require_capability('moodle/course:view', $context);
 600              }
 601  
 602              $courseinfo = array();
 603              $courseinfo['id'] = $course->id;
 604              $courseinfo['fullname'] = external_format_string($course->fullname, $context->id);
 605              $courseinfo['shortname'] = external_format_string($course->shortname, $context->id);
 606              $courseinfo['displayname'] = external_format_string(get_course_display_name_for_list($course), $context->id);
 607              $courseinfo['categoryid'] = $course->category;
 608              list($courseinfo['summary'], $courseinfo['summaryformat']) =
 609                  external_format_text($course->summary, $course->summaryformat, $context->id, 'course', 'summary', 0);
 610              $courseinfo['format'] = $course->format;
 611              $courseinfo['startdate'] = $course->startdate;
 612              $courseinfo['enddate'] = $course->enddate;
 613              $courseinfo['showactivitydates'] = $course->showactivitydates;
 614              $courseinfo['showcompletionconditions'] = $course->showcompletionconditions;
 615              if (array_key_exists('numsections', $courseformatoptions)) {
 616                  // For backward-compartibility
 617                  $courseinfo['numsections'] = $courseformatoptions['numsections'];
 618              }
 619  
 620              $handler = core_course\customfield\course_handler::create();
 621              if ($customfields = $handler->export_instance_data($course->id)) {
 622                  $courseinfo['customfields'] = [];
 623                  foreach ($customfields as $data) {
 624                      $courseinfo['customfields'][] = [
 625                          'type' => $data->get_type(),
 626                          'value' => $data->get_value(),
 627                          'valueraw' => $data->get_data_controller()->get_value(),
 628                          'name' => $data->get_name(),
 629                          'shortname' => $data->get_shortname()
 630                      ];
 631                  }
 632              }
 633  
 634              //some field should be returned only if the user has update permission
 635              $courseadmin = has_capability('moodle/course:update', $context);
 636              if ($courseadmin) {
 637                  $courseinfo['categorysortorder'] = $course->sortorder;
 638                  $courseinfo['idnumber'] = $course->idnumber;
 639                  $courseinfo['showgrades'] = $course->showgrades;
 640                  $courseinfo['showreports'] = $course->showreports;
 641                  $courseinfo['newsitems'] = $course->newsitems;
 642                  $courseinfo['visible'] = $course->visible;
 643                  $courseinfo['maxbytes'] = $course->maxbytes;
 644                  if (array_key_exists('hiddensections', $courseformatoptions)) {
 645                      // For backward-compartibility
 646                      $courseinfo['hiddensections'] = $courseformatoptions['hiddensections'];
 647                  }
 648                  // Return numsections for backward-compatibility with clients who expect it.
 649                  $courseinfo['numsections'] = course_get_format($course)->get_last_section_number();
 650                  $courseinfo['groupmode'] = $course->groupmode;
 651                  $courseinfo['groupmodeforce'] = $course->groupmodeforce;
 652                  $courseinfo['defaultgroupingid'] = $course->defaultgroupingid;
 653                  $courseinfo['lang'] = clean_param($course->lang, PARAM_LANG);
 654                  $courseinfo['timecreated'] = $course->timecreated;
 655                  $courseinfo['timemodified'] = $course->timemodified;
 656                  $courseinfo['forcetheme'] = clean_param($course->theme, PARAM_THEME);
 657                  $courseinfo['enablecompletion'] = $course->enablecompletion;
 658                  $courseinfo['completionnotify'] = $course->completionnotify;
 659                  $courseinfo['courseformatoptions'] = array();
 660                  foreach ($courseformatoptions as $key => $value) {
 661                      $courseinfo['courseformatoptions'][] = array(
 662                          'name' => $key,
 663                          'value' => $value
 664                      );
 665                  }
 666              }
 667  
 668              if ($courseadmin or $course->visible
 669                      or has_capability('moodle/course:viewhiddencourses', $context)) {
 670                  $coursesinfo[] = $courseinfo;
 671              }
 672          }
 673  
 674          return $coursesinfo;
 675      }
 676  
 677      /**
 678       * Returns description of method result value
 679       *
 680       * @return external_description
 681       * @since Moodle 2.2
 682       */
 683      public static function get_courses_returns() {
 684          return new external_multiple_structure(
 685                  new external_single_structure(
 686                          array(
 687                              'id' => new external_value(PARAM_INT, 'course id'),
 688                              'shortname' => new external_value(PARAM_RAW, 'course short name'),
 689                              'categoryid' => new external_value(PARAM_INT, 'category id'),
 690                              'categorysortorder' => new external_value(PARAM_INT,
 691                                      'sort order into the category', VALUE_OPTIONAL),
 692                              'fullname' => new external_value(PARAM_RAW, 'full name'),
 693                              'displayname' => new external_value(PARAM_RAW, 'course display name'),
 694                              'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
 695                              'summary' => new external_value(PARAM_RAW, 'summary'),
 696                              'summaryformat' => new external_format_value('summary'),
 697                              'format' => new external_value(PARAM_PLUGIN,
 698                                      'course format: weeks, topics, social, site,..'),
 699                              'showgrades' => new external_value(PARAM_INT,
 700                                      '1 if grades are shown, otherwise 0', VALUE_OPTIONAL),
 701                              'newsitems' => new external_value(PARAM_INT,
 702                                      'number of recent items appearing on the course page', VALUE_OPTIONAL),
 703                              'startdate' => new external_value(PARAM_INT,
 704                                      'timestamp when the course start'),
 705                              'enddate' => new external_value(PARAM_INT,
 706                                      'timestamp when the course end'),
 707                              'numsections' => new external_value(PARAM_INT,
 708                                      '(deprecated, use courseformatoptions) number of weeks/topics',
 709                                      VALUE_OPTIONAL),
 710                              'maxbytes' => new external_value(PARAM_INT,
 711                                      'largest size of file that can be uploaded into the course',
 712                                      VALUE_OPTIONAL),
 713                              'showreports' => new external_value(PARAM_INT,
 714                                      'are activity report shown (yes = 1, no =0)', VALUE_OPTIONAL),
 715                              'visible' => new external_value(PARAM_INT,
 716                                      '1: available to student, 0:not available', VALUE_OPTIONAL),
 717                              'hiddensections' => new external_value(PARAM_INT,
 718                                      '(deprecated, use courseformatoptions) How the hidden sections in the course are displayed to students',
 719                                      VALUE_OPTIONAL),
 720                              'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible',
 721                                      VALUE_OPTIONAL),
 722                              'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no',
 723                                      VALUE_OPTIONAL),
 724                              'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id',
 725                                      VALUE_OPTIONAL),
 726                              'timecreated' => new external_value(PARAM_INT,
 727                                      'timestamp when the course have been created', VALUE_OPTIONAL),
 728                              'timemodified' => new external_value(PARAM_INT,
 729                                      'timestamp when the course have been modified', VALUE_OPTIONAL),
 730                              'enablecompletion' => new external_value(PARAM_INT,
 731                                      'Enabled, control via completion and activity settings. Disbaled,
 732                                          not shown in activity settings.',
 733                                      VALUE_OPTIONAL),
 734                              'completionnotify' => new external_value(PARAM_INT,
 735                                      '1: yes 0: no', VALUE_OPTIONAL),
 736                              'lang' => new external_value(PARAM_SAFEDIR,
 737                                      'forced course language', VALUE_OPTIONAL),
 738                              'forcetheme' => new external_value(PARAM_PLUGIN,
 739                                      'name of the force theme', VALUE_OPTIONAL),
 740                              'courseformatoptions' => new external_multiple_structure(
 741                                  new external_single_structure(
 742                                      array('name' => new external_value(PARAM_ALPHANUMEXT, 'course format option name'),
 743                                          'value' => new external_value(PARAM_RAW, 'course format option value')
 744                                  )), 'additional options for particular course format', VALUE_OPTIONAL
 745                               ),
 746                              'showactivitydates' => new external_value(PARAM_BOOL, 'Whether the activity dates are shown or not'),
 747                              'showcompletionconditions' => new external_value(PARAM_BOOL,
 748                                  'Whether the activity completion conditions are shown or not'),
 749                              'customfields' => new external_multiple_structure(
 750                                  new external_single_structure(
 751                                      ['name' => new external_value(PARAM_RAW, 'The name of the custom field'),
 752                                       'shortname' => new external_value(PARAM_ALPHANUMEXT, 'The shortname of the custom field'),
 753                                       'type'  => new external_value(PARAM_COMPONENT,
 754                                           'The type of the custom field - text, checkbox...'),
 755                                       'valueraw' => new external_value(PARAM_RAW, 'The raw value of the custom field'),
 756                                       'value' => new external_value(PARAM_RAW, 'The value of the custom field')]
 757                                  ), 'Custom fields and associated values', VALUE_OPTIONAL),
 758                          ), 'course'
 759                  )
 760          );
 761      }
 762  
 763      /**
 764       * Returns description of method parameters
 765       *
 766       * @return external_function_parameters
 767       * @since Moodle 2.2
 768       */
 769      public static function create_courses_parameters() {
 770          $courseconfig = get_config('moodlecourse'); //needed for many default values
 771          return new external_function_parameters(
 772              array(
 773                  'courses' => new external_multiple_structure(
 774                      new external_single_structure(
 775                          array(
 776                              'fullname' => new external_value(PARAM_TEXT, 'full name'),
 777                              'shortname' => new external_value(PARAM_TEXT, 'course short name'),
 778                              'categoryid' => new external_value(PARAM_INT, 'category id'),
 779                              'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
 780                              'summary' => new external_value(PARAM_RAW, 'summary', VALUE_OPTIONAL),
 781                              'summaryformat' => new external_format_value('summary', VALUE_DEFAULT),
 782                              'format' => new external_value(PARAM_PLUGIN,
 783                                      'course format: weeks, topics, social, site,..',
 784                                      VALUE_DEFAULT, $courseconfig->format),
 785                              'showgrades' => new external_value(PARAM_INT,
 786                                      '1 if grades are shown, otherwise 0', VALUE_DEFAULT,
 787                                      $courseconfig->showgrades),
 788                              'newsitems' => new external_value(PARAM_INT,
 789                                      'number of recent items appearing on the course page',
 790                                      VALUE_DEFAULT, $courseconfig->newsitems),
 791                              'startdate' => new external_value(PARAM_INT,
 792                                      'timestamp when the course start', VALUE_OPTIONAL),
 793                              'enddate' => new external_value(PARAM_INT,
 794                                      'timestamp when the course end', VALUE_OPTIONAL),
 795                              'numsections' => new external_value(PARAM_INT,
 796                                      '(deprecated, use courseformatoptions) number of weeks/topics',
 797                                      VALUE_OPTIONAL),
 798                              'maxbytes' => new external_value(PARAM_INT,
 799                                      'largest size of file that can be uploaded into the course',
 800                                      VALUE_DEFAULT, $courseconfig->maxbytes),
 801                              'showreports' => new external_value(PARAM_INT,
 802                                      'are activity report shown (yes = 1, no =0)', VALUE_DEFAULT,
 803                                      $courseconfig->showreports),
 804                              'visible' => new external_value(PARAM_INT,
 805                                      '1: available to student, 0:not available', VALUE_OPTIONAL),
 806                              'hiddensections' => new external_value(PARAM_INT,
 807                                      '(deprecated, use courseformatoptions) How the hidden sections in the course are displayed to students',
 808                                      VALUE_OPTIONAL),
 809                              'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible',
 810                                      VALUE_DEFAULT, $courseconfig->groupmode),
 811                              'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no',
 812                                      VALUE_DEFAULT, $courseconfig->groupmodeforce),
 813                              'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id',
 814                                      VALUE_DEFAULT, 0),
 815                              'enablecompletion' => new external_value(PARAM_INT,
 816                                      'Enabled, control via completion and activity settings. Disabled,
 817                                          not shown in activity settings.',
 818                                      VALUE_OPTIONAL),
 819                              'completionnotify' => new external_value(PARAM_INT,
 820                                      '1: yes 0: no', VALUE_OPTIONAL),
 821                              'lang' => new external_value(PARAM_SAFEDIR,
 822                                      'forced course language', VALUE_OPTIONAL),
 823                              'forcetheme' => new external_value(PARAM_PLUGIN,
 824                                      'name of the force theme', VALUE_OPTIONAL),
 825                              'courseformatoptions' => new external_multiple_structure(
 826                                  new external_single_structure(
 827                                      array('name' => new external_value(PARAM_ALPHANUMEXT, 'course format option name'),
 828                                          'value' => new external_value(PARAM_RAW, 'course format option value')
 829                                  )),
 830                                      'additional options for particular course format', VALUE_OPTIONAL),
 831                              'customfields' => new external_multiple_structure(
 832                                  new external_single_structure(
 833                                      array(
 834                                          'shortname'  => new external_value(PARAM_ALPHANUMEXT, 'The shortname of the custom field'),
 835                                          'value' => new external_value(PARAM_RAW, 'The value of the custom field'),
 836                                  )), 'custom fields for the course', VALUE_OPTIONAL
 837                              )
 838                      )), 'courses to create'
 839                  )
 840              )
 841          );
 842      }
 843  
 844      /**
 845       * Create  courses
 846       *
 847       * @param array $courses
 848       * @return array courses (id and shortname only)
 849       * @since Moodle 2.2
 850       */
 851      public static function create_courses($courses) {
 852          global $CFG, $DB;
 853          require_once($CFG->dirroot . "/course/lib.php");
 854          require_once($CFG->libdir . '/completionlib.php');
 855  
 856          $params = self::validate_parameters(self::create_courses_parameters(),
 857                          array('courses' => $courses));
 858  
 859          $availablethemes = core_component::get_plugin_list('theme');
 860          $availablelangs = get_string_manager()->get_list_of_translations();
 861  
 862          $transaction = $DB->start_delegated_transaction();
 863  
 864          foreach ($params['courses'] as $course) {
 865  
 866              // Ensure the current user is allowed to run this function
 867              $context = context_coursecat::instance($course['categoryid'], IGNORE_MISSING);
 868              try {
 869                  self::validate_context($context);
 870              } catch (Exception $e) {
 871                  $exceptionparam = new stdClass();
 872                  $exceptionparam->message = $e->getMessage();
 873                  $exceptionparam->catid = $course['categoryid'];
 874                  throw new moodle_exception('errorcatcontextnotvalid', 'webservice', '', $exceptionparam);
 875              }
 876              require_capability('moodle/course:create', $context);
 877  
 878              // Fullname and short name are required to be non-empty.
 879              if (trim($course['fullname']) === '') {
 880                  throw new moodle_exception('errorinvalidparam', 'webservice', '', 'fullname');
 881              } else if (trim($course['shortname']) === '') {
 882                  throw new moodle_exception('errorinvalidparam', 'webservice', '', 'shortname');
 883              }
 884  
 885              // Make sure lang is valid
 886              if (array_key_exists('lang', $course)) {
 887                  if (empty($availablelangs[$course['lang']])) {
 888                      throw new moodle_exception('errorinvalidparam', 'webservice', '', 'lang');
 889                  }
 890                  if (!has_capability('moodle/course:setforcedlanguage', $context)) {
 891                      unset($course['lang']);
 892                  }
 893              }
 894  
 895              // Make sure theme is valid
 896              if (array_key_exists('forcetheme', $course)) {
 897                  if (!empty($CFG->allowcoursethemes)) {
 898                      if (empty($availablethemes[$course['forcetheme']])) {
 899                          throw new moodle_exception('errorinvalidparam', 'webservice', '', 'forcetheme');
 900                      } else {
 901                          $course['theme'] = $course['forcetheme'];
 902                      }
 903                  }
 904              }
 905  
 906              //force visibility if ws user doesn't have the permission to set it
 907              $category = $DB->get_record('course_categories', array('id' => $course['categoryid']));
 908              if (!has_capability('moodle/course:visibility', $context)) {
 909                  $course['visible'] = $category->visible;
 910              }
 911  
 912              //set default value for completion
 913              $courseconfig = get_config('moodlecourse');
 914              if (completion_info::is_enabled_for_site()) {
 915                  if (!array_key_exists('enablecompletion', $course)) {
 916                      $course['enablecompletion'] = $courseconfig->enablecompletion;
 917                  }
 918              } else {
 919                  $course['enablecompletion'] = 0;
 920              }
 921  
 922              $course['category'] = $course['categoryid'];
 923  
 924              // Summary format.
 925              $course['summaryformat'] = external_validate_format($course['summaryformat']);
 926  
 927              if (!empty($course['courseformatoptions'])) {
 928                  foreach ($course['courseformatoptions'] as $option) {
 929                      $course[$option['name']] = $option['value'];
 930                  }
 931              }
 932  
 933              // Custom fields.
 934              if (!empty($course['customfields'])) {
 935                  foreach ($course['customfields'] as $field) {
 936                      $course['customfield_'.$field['shortname']] = $field['value'];
 937                  }
 938              }
 939  
 940              //Note: create_course() core function check shortname, idnumber, category
 941              $course['id'] = create_course((object) $course)->id;
 942  
 943              $resultcourses[] = array('id' => $course['id'], 'shortname' => $course['shortname']);
 944          }
 945  
 946          $transaction->allow_commit();
 947  
 948          return $resultcourses;
 949      }
 950  
 951      /**
 952       * Returns description of method result value
 953       *
 954       * @return external_description
 955       * @since Moodle 2.2
 956       */
 957      public static function create_courses_returns() {
 958          return new external_multiple_structure(
 959              new external_single_structure(
 960                  array(
 961                      'id'       => new external_value(PARAM_INT, 'course id'),
 962                      'shortname' => new external_value(PARAM_RAW, 'short name'),
 963                  )
 964              )
 965          );
 966      }
 967  
 968      /**
 969       * Update courses
 970       *
 971       * @return external_function_parameters
 972       * @since Moodle 2.5
 973       */
 974      public static function update_courses_parameters() {
 975          return new external_function_parameters(
 976              array(
 977                  'courses' => new external_multiple_structure(
 978                      new external_single_structure(
 979                          array(
 980                              'id' => new external_value(PARAM_INT, 'ID of the course'),
 981                              'fullname' => new external_value(PARAM_TEXT, 'full name', VALUE_OPTIONAL),
 982                              'shortname' => new external_value(PARAM_TEXT, 'course short name', VALUE_OPTIONAL),
 983                              'categoryid' => new external_value(PARAM_INT, 'category id', VALUE_OPTIONAL),
 984                              'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
 985                              'summary' => new external_value(PARAM_RAW, 'summary', VALUE_OPTIONAL),
 986                              'summaryformat' => new external_format_value('summary', VALUE_OPTIONAL),
 987                              'format' => new external_value(PARAM_PLUGIN,
 988                                      'course format: weeks, topics, social, site,..', VALUE_OPTIONAL),
 989                              'showgrades' => new external_value(PARAM_INT,
 990                                      '1 if grades are shown, otherwise 0', VALUE_OPTIONAL),
 991                              'newsitems' => new external_value(PARAM_INT,
 992                                      'number of recent items appearing on the course page', VALUE_OPTIONAL),
 993                              'startdate' => new external_value(PARAM_INT,
 994                                      'timestamp when the course start', VALUE_OPTIONAL),
 995                              'enddate' => new external_value(PARAM_INT,
 996                                      'timestamp when the course end', VALUE_OPTIONAL),
 997                              'numsections' => new external_value(PARAM_INT,
 998                                      '(deprecated, use courseformatoptions) number of weeks/topics', VALUE_OPTIONAL),
 999                              'maxbytes' => new external_value(PARAM_INT,
1000                                      'largest size of file that can be uploaded into the course', VALUE_OPTIONAL),
1001                              'showreports' => new external_value(PARAM_INT,
1002                                      'are activity report shown (yes = 1, no =0)', VALUE_OPTIONAL),
1003                              'visible' => new external_value(PARAM_INT,
1004                                      '1: available to student, 0:not available', VALUE_OPTIONAL),
1005                              'hiddensections' => new external_value(PARAM_INT,
1006                                      '(deprecated, use courseformatoptions) How the hidden sections in the course are
1007                                          displayed to students', VALUE_OPTIONAL),
1008                              'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible', VALUE_OPTIONAL),
1009                              'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no', VALUE_OPTIONAL),
1010                              'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id', VALUE_OPTIONAL),
1011                              'enablecompletion' => new external_value(PARAM_INT,
1012                                      'Enabled, control via completion and activity settings. Disabled,
1013                                          not shown in activity settings.', VALUE_OPTIONAL),
1014                              'completionnotify' => new external_value(PARAM_INT, '1: yes 0: no', VALUE_OPTIONAL),
1015                              'lang' => new external_value(PARAM_SAFEDIR, 'forced course language', VALUE_OPTIONAL),
1016                              'forcetheme' => new external_value(PARAM_PLUGIN, 'name of the force theme', VALUE_OPTIONAL),
1017                              'courseformatoptions' => new external_multiple_structure(
1018                                  new external_single_structure(
1019                                      array('name' => new external_value(PARAM_ALPHANUMEXT, 'course format option name'),
1020                                          'value' => new external_value(PARAM_RAW, 'course format option value')
1021                                  )), 'additional options for particular course format', VALUE_OPTIONAL),
1022                              'customfields' => new external_multiple_structure(
1023                                  new external_single_structure(
1024                                      [
1025                                          'shortname'  => new external_value(PARAM_ALPHANUMEXT, 'The shortname of the custom field'),
1026                                          'value' => new external_value(PARAM_RAW, 'The value of the custom field')
1027                                      ]
1028                                  ), 'Custom fields', VALUE_OPTIONAL),
1029                          )
1030                      ), 'courses to update'
1031                  )
1032              )
1033          );
1034      }
1035  
1036      /**
1037       * Update courses
1038       *
1039       * @param array $courses
1040       * @since Moodle 2.5
1041       */
1042      public static function update_courses($courses) {
1043          global $CFG, $DB;
1044          require_once($CFG->dirroot . "/course/lib.php");
1045          $warnings = array();
1046  
1047          $params = self::validate_parameters(self::update_courses_parameters(),
1048                          array('courses' => $courses));
1049  
1050          $availablethemes = core_component::get_plugin_list('theme');
1051          $availablelangs = get_string_manager()->get_list_of_translations();
1052  
1053          foreach ($params['courses'] as $course) {
1054              // Catch any exception while updating course and return as warning to user.
1055              try {
1056                  // Ensure the current user is allowed to run this function.
1057                  $context = context_course::instance($course['id'], MUST_EXIST);
1058                  self::validate_context($context);
1059  
1060                  $oldcourse = course_get_format($course['id'])->get_course();
1061  
1062                  require_capability('moodle/course:update', $context);
1063  
1064                  // Check if user can change category.
1065                  if (array_key_exists('categoryid', $course) && ($oldcourse->category != $course['categoryid'])) {
1066                      require_capability('moodle/course:changecategory', $context);
1067                      $course['category'] = $course['categoryid'];
1068                  }
1069  
1070                  // Check if the user can change fullname, and the new value is non-empty.
1071                  if (array_key_exists('fullname', $course) && ($oldcourse->fullname != $course['fullname'])) {
1072                      require_capability('moodle/course:changefullname', $context);
1073                      if (trim($course['fullname']) === '') {
1074                          throw new moodle_exception('errorinvalidparam', 'webservice', '', 'fullname');
1075                      }
1076                  }
1077  
1078                  // Check if the user can change shortname, and the new value is non-empty.
1079                  if (array_key_exists('shortname', $course) && ($oldcourse->shortname != $course['shortname'])) {
1080                      require_capability('moodle/course:changeshortname', $context);
1081                      if (trim($course['shortname']) === '') {
1082                          throw new moodle_exception('errorinvalidparam', 'webservice', '', 'shortname');
1083                      }
1084                  }
1085  
1086                  // Check if the user can change the idnumber.
1087                  if (array_key_exists('idnumber', $course) && ($oldcourse->idnumber != $course['idnumber'])) {
1088                      require_capability('moodle/course:changeidnumber', $context);
1089                  }
1090  
1091                  // Check if user can change summary.
1092                  if (array_key_exists('summary', $course) && ($oldcourse->summary != $course['summary'])) {
1093                      require_capability('moodle/course:changesummary', $context);
1094                  }
1095  
1096                  // Summary format.
1097                  if (array_key_exists('summaryformat', $course) && ($oldcourse->summaryformat != $course['summaryformat'])) {
1098                      require_capability('moodle/course:changesummary', $context);
1099                      $course['summaryformat'] = external_validate_format($course['summaryformat']);
1100                  }
1101  
1102                  // Check if user can change visibility.
1103                  if (array_key_exists('visible', $course) && ($oldcourse->visible != $course['visible'])) {
1104                      require_capability('moodle/course:visibility', $context);
1105                  }
1106  
1107                  // Make sure lang is valid.
1108                  if (array_key_exists('lang', $course) && ($oldcourse->lang != $course['lang'])) {
1109                      require_capability('moodle/course:setforcedlanguage', $context);
1110                      if (empty($availablelangs[$course['lang']])) {
1111                          throw new moodle_exception('errorinvalidparam', 'webservice', '', 'lang');
1112                      }
1113                  }
1114  
1115                  // Make sure theme is valid.
1116                  if (array_key_exists('forcetheme', $course)) {
1117                      if (!empty($CFG->allowcoursethemes)) {
1118                          if (empty($availablethemes[$course['forcetheme']])) {
1119                              throw new moodle_exception('errorinvalidparam', 'webservice', '', 'forcetheme');
1120                          } else {
1121                              $course['theme'] = $course['forcetheme'];
1122                          }
1123                      }
1124                  }
1125  
1126                  // Make sure completion is enabled before setting it.
1127                  if (array_key_exists('enabledcompletion', $course) && !completion_info::is_enabled_for_site()) {
1128                      $course['enabledcompletion'] = 0;
1129                  }
1130  
1131                  // Make sure maxbytes are less then CFG->maxbytes.
1132                  if (array_key_exists('maxbytes', $course)) {
1133                      // We allow updates back to 0 max bytes, a special value denoting the course uses the site limit.
1134                      // Otherwise, either use the size specified, or cap at the max size for the course.
1135                      if ($course['maxbytes'] != 0) {
1136                          $course['maxbytes'] = get_max_upload_file_size($CFG->maxbytes, $course['maxbytes']);
1137                      }
1138                  }
1139  
1140                  if (!empty($course['courseformatoptions'])) {
1141                      foreach ($course['courseformatoptions'] as $option) {
1142                          if (isset($option['name']) && isset($option['value'])) {
1143                              $course[$option['name']] = $option['value'];
1144                          }
1145                      }
1146                  }
1147  
1148                  // Prepare list of custom fields.
1149                  if (isset($course['customfields'])) {
1150                      foreach ($course['customfields'] as $field) {
1151                          $course['customfield_' . $field['shortname']] = $field['value'];
1152                      }
1153                  }
1154  
1155                  // Update course if user has all required capabilities.
1156                  update_course((object) $course);
1157              } catch (Exception $e) {
1158                  $warning = array();
1159                  $warning['item'] = 'course';
1160                  $warning['itemid'] = $course['id'];
1161                  if ($e instanceof moodle_exception) {
1162                      $warning['warningcode'] = $e->errorcode;
1163                  } else {
1164                      $warning['warningcode'] = $e->getCode();
1165                  }
1166                  $warning['message'] = $e->getMessage();
1167                  $warnings[] = $warning;
1168              }
1169          }
1170  
1171          $result = array();
1172          $result['warnings'] = $warnings;
1173          return $result;
1174      }
1175  
1176      /**
1177       * Returns description of method result value
1178       *
1179       * @return external_description
1180       * @since Moodle 2.5
1181       */
1182      public static function update_courses_returns() {
1183          return new external_single_structure(
1184              array(
1185                  'warnings' => new external_warnings()
1186              )
1187          );
1188      }
1189  
1190      /**
1191       * Returns description of method parameters
1192       *
1193       * @return external_function_parameters
1194       * @since Moodle 2.2
1195       */
1196      public static function delete_courses_parameters() {
1197          return new external_function_parameters(
1198              array(
1199                  'courseids' => new external_multiple_structure(new external_value(PARAM_INT, 'course ID')),
1200              )
1201          );
1202      }
1203  
1204      /**
1205       * Delete courses
1206       *
1207       * @param array $courseids A list of course ids
1208       * @since Moodle 2.2
1209       */
1210      public static function delete_courses($courseids) {
1211          global $CFG, $DB;
1212          require_once($CFG->dirroot."/course/lib.php");
1213  
1214          // Parameter validation.
1215          $params = self::validate_parameters(self::delete_courses_parameters(), array('courseids'=>$courseids));
1216  
1217          $warnings = array();
1218  
1219          foreach ($params['courseids'] as $courseid) {
1220              $course = $DB->get_record('course', array('id' => $courseid));
1221  
1222              if ($course === false) {
1223                  $warnings[] = array(
1224                                  'item' => 'course',
1225                                  'itemid' => $courseid,
1226                                  'warningcode' => 'unknowncourseidnumber',
1227                                  'message' => 'Unknown course ID ' . $courseid
1228                              );
1229                  continue;
1230              }
1231  
1232              // Check if the context is valid.
1233              $coursecontext = context_course::instance($course->id);
1234              self::validate_context($coursecontext);
1235  
1236              // Check if the current user has permission.
1237              if (!can_delete_course($courseid)) {
1238                  $warnings[] = array(
1239                                  'item' => 'course',
1240                                  'itemid' => $courseid,
1241                                  'warningcode' => 'cannotdeletecourse',
1242                                  'message' => 'You do not have the permission to delete this course' . $courseid
1243                              );
1244                  continue;
1245              }
1246  
1247              if (delete_course($course, false) === false) {
1248                  $warnings[] = array(
1249                                  'item' => 'course',
1250                                  'itemid' => $courseid,
1251                                  'warningcode' => 'cannotdeletecategorycourse',
1252                                  'message' => 'Course ' . $courseid . ' failed to be deleted'
1253                              );
1254                  continue;
1255              }
1256          }
1257  
1258          fix_course_sortorder();
1259  
1260          return array('warnings' => $warnings);
1261      }
1262  
1263      /**
1264       * Returns description of method result value
1265       *
1266       * @return external_description
1267       * @since Moodle 2.2
1268       */
1269      public static function delete_courses_returns() {
1270          return new external_single_structure(
1271              array(
1272                  'warnings' => new external_warnings()
1273              )
1274          );
1275      }
1276  
1277      /**
1278       * Returns description of method parameters
1279       *
1280       * @return external_function_parameters
1281       * @since Moodle 2.3
1282       */
1283      public static function duplicate_course_parameters() {
1284          return new external_function_parameters(
1285              array(
1286                  'courseid' => new external_value(PARAM_INT, 'course to duplicate id'),
1287                  'fullname' => new external_value(PARAM_TEXT, 'duplicated course full name'),
1288                  'shortname' => new external_value(PARAM_TEXT, 'duplicated course short name'),
1289                  'categoryid' => new external_value(PARAM_INT, 'duplicated course category parent'),
1290                  'visible' => new external_value(PARAM_INT, 'duplicated course visible, default to yes', VALUE_DEFAULT, 1),
1291                  'options' => new external_multiple_structure(
1292                      new external_single_structure(
1293                          array(
1294                                  'name' => new external_value(PARAM_ALPHAEXT, 'The backup option name:
1295                                              "activities" (int) Include course activites (default to 1 that is equal to yes),
1296                                              "blocks" (int) Include course blocks (default to 1 that is equal to yes),
1297                                              "filters" (int) Include course filters  (default to 1 that is equal to yes),
1298                                              "users" (int) Include users (default to 0 that is equal to no),
1299                                              "enrolments" (int) Include enrolment methods (default to 1 - restore only with users),
1300                                              "role_assignments" (int) Include role assignments  (default to 0 that is equal to no),
1301                                              "comments" (int) Include user comments  (default to 0 that is equal to no),
1302                                              "userscompletion" (int) Include user course completion information  (default to 0 that is equal to no),
1303                                              "logs" (int) Include course logs  (default to 0 that is equal to no),
1304                                              "grade_histories" (int) Include histories  (default to 0 that is equal to no)'
1305                                              ),
1306                                  'value' => new external_value(PARAM_RAW, 'the value for the option 1 (yes) or 0 (no)'
1307                              )
1308                          )
1309                      ), 'Course duplication options', VALUE_DEFAULT, array()
1310                  ),
1311              )
1312          );
1313      }
1314  
1315      /**
1316       * Duplicate a course
1317       *
1318       * @param int $courseid
1319       * @param string $fullname Duplicated course fullname
1320       * @param string $shortname Duplicated course shortname
1321       * @param int $categoryid Duplicated course parent category id
1322       * @param int $visible Duplicated course availability
1323       * @param array $options List of backup options
1324       * @return array New course info
1325       * @since Moodle 2.3
1326       */
1327      public static function duplicate_course($courseid, $fullname, $shortname, $categoryid, $visible = 1, $options = array()) {
1328          global $CFG, $USER, $DB;
1329          require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
1330          require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
1331  
1332          // Parameter validation.
1333          $params = self::validate_parameters(
1334                  self::duplicate_course_parameters(),
1335                  array(
1336                        'courseid' => $courseid,
1337                        'fullname' => $fullname,
1338                        'shortname' => $shortname,
1339                        'categoryid' => $categoryid,
1340                        'visible' => $visible,
1341                        'options' => $options
1342                  )
1343          );
1344  
1345          // Context validation.
1346  
1347          if (! ($course = $DB->get_record('course', array('id'=>$params['courseid'])))) {
1348              throw new moodle_exception('invalidcourseid', 'error');
1349          }
1350  
1351          // Category where duplicated course is going to be created.
1352          $categorycontext = context_coursecat::instance($params['categoryid']);
1353          self::validate_context($categorycontext);
1354  
1355          // Course to be duplicated.
1356          $coursecontext = context_course::instance($course->id);
1357          self::validate_context($coursecontext);
1358  
1359          $backupdefaults = array(
1360              'activities' => 1,
1361              'blocks' => 1,
1362              'filters' => 1,
1363              'users' => 0,
1364              'enrolments' => backup::ENROL_WITHUSERS,
1365              'role_assignments' => 0,
1366              'comments' => 0,
1367              'userscompletion' => 0,
1368              'logs' => 0,
1369              'grade_histories' => 0
1370          );
1371  
1372          $backupsettings = array();
1373          // Check for backup and restore options.
1374          if (!empty($params['options'])) {
1375              foreach ($params['options'] as $option) {
1376  
1377                  // Strict check for a correct value (allways 1 or 0, true or false).
1378                  $value = clean_param($option['value'], PARAM_INT);
1379  
1380                  if ($value !== 0 and $value !== 1) {
1381                      throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
1382                  }
1383  
1384                  if (!isset($backupdefaults[$option['name']])) {
1385                      throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
1386                  }
1387  
1388                  $backupsettings[$option['name']] = $value;
1389              }
1390          }
1391  
1392          // Capability checking.
1393  
1394          // The backup controller check for this currently, this may be redundant.
1395          require_capability('moodle/course:create', $categorycontext);
1396          require_capability('moodle/restore:restorecourse', $categorycontext);
1397          require_capability('moodle/backup:backupcourse', $coursecontext);
1398  
1399          if (!empty($backupsettings['users'])) {
1400              require_capability('moodle/backup:userinfo', $coursecontext);
1401              require_capability('moodle/restore:userinfo', $categorycontext);
1402          }
1403  
1404          // Check if the shortname is used.
1405          if ($foundcourses = $DB->get_records('course', array('shortname'=>$shortname))) {
1406              foreach ($foundcourses as $foundcourse) {
1407                  $foundcoursenames[] = $foundcourse->fullname;
1408              }
1409  
1410              $foundcoursenamestring = implode(',', $foundcoursenames);
1411              throw new moodle_exception('shortnametaken', '', '', $foundcoursenamestring);
1412          }
1413  
1414          // Backup the course.
1415  
1416          $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
1417          backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $USER->id);
1418  
1419          foreach ($backupsettings as $name => $value) {
1420              if ($setting = $bc->get_plan()->get_setting($name)) {
1421                  $bc->get_plan()->get_setting($name)->set_value($value);
1422              }
1423          }
1424  
1425          $backupid       = $bc->get_backupid();
1426          $backupbasepath = $bc->get_plan()->get_basepath();
1427  
1428          $bc->execute_plan();
1429          $results = $bc->get_results();
1430          $file = $results['backup_destination'];
1431  
1432          $bc->destroy();
1433  
1434          // Restore the backup immediately.
1435  
1436          // Check if we need to unzip the file because the backup temp dir does not contains backup files.
1437          if (!file_exists($backupbasepath . "/moodle_backup.xml")) {
1438              $file->extract_to_pathname(get_file_packer('application/vnd.moodle.backup'), $backupbasepath);
1439          }
1440  
1441          // Create new course.
1442          $newcourseid = restore_dbops::create_new_course($params['fullname'], $params['shortname'], $params['categoryid']);
1443  
1444          $rc = new restore_controller($backupid, $newcourseid,
1445                  backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $USER->id, backup::TARGET_NEW_COURSE);
1446  
1447          foreach ($backupsettings as $name => $value) {
1448              $setting = $rc->get_plan()->get_setting($name);
1449              if ($setting->get_status() == backup_setting::NOT_LOCKED) {
1450                  $setting->set_value($value);
1451              }
1452          }
1453  
1454          if (!$rc->execute_precheck()) {
1455              $precheckresults = $rc->get_precheck_results();
1456              if (is_array($precheckresults) && !empty($precheckresults['errors'])) {
1457                  if (empty($CFG->keeptempdirectoriesonbackup)) {
1458                      fulldelete($backupbasepath);
1459                  }
1460  
1461                  $errorinfo = '';
1462  
1463                  foreach ($precheckresults['errors'] as $error) {
1464                      $errorinfo .= $error;
1465                  }
1466  
1467                  if (array_key_exists('warnings', $precheckresults)) {
1468                      foreach ($precheckresults['warnings'] as $warning) {
1469                          $errorinfo .= $warning;
1470                      }
1471                  }
1472  
1473                  throw new moodle_exception('backupprecheckerrors', 'webservice', '', $errorinfo);
1474              }
1475          }
1476  
1477          $rc->execute_plan();
1478          $rc->destroy();
1479  
1480          $course = $DB->get_record('course', array('id' => $newcourseid), '*', MUST_EXIST);
1481          $course->fullname = $params['fullname'];
1482          $course->shortname = $params['shortname'];
1483          $course->visible = $params['visible'];
1484  
1485          // Set shortname and fullname back.
1486          $DB->update_record('course', $course);
1487  
1488          if (empty($CFG->keeptempdirectoriesonbackup)) {
1489              fulldelete($backupbasepath);
1490          }
1491  
1492          // Delete the course backup file created by this WebService. Originally located in the course backups area.
1493          $file->delete();
1494  
1495          return array('id' => $course->id, 'shortname' => $course->shortname);
1496      }
1497  
1498      /**
1499       * Returns description of method result value
1500       *
1501       * @return external_description
1502       * @since Moodle 2.3
1503       */
1504      public static function duplicate_course_returns() {
1505          return new external_single_structure(
1506              array(
1507                  'id'       => new external_value(PARAM_INT, 'course id'),
1508                  'shortname' => new external_value(PARAM_RAW, 'short name'),
1509              )
1510          );
1511      }
1512  
1513      /**
1514       * Returns description of method parameters for import_course
1515       *
1516       * @return external_function_parameters
1517       * @since Moodle 2.4
1518       */
1519      public static function import_course_parameters() {
1520          return new external_function_parameters(
1521              array(
1522                  'importfrom' => new external_value(PARAM_INT, 'the id of the course we are importing from'),
1523                  'importto' => new external_value(PARAM_INT, 'the id of the course we are importing to'),
1524                  'deletecontent' => new external_value(PARAM_INT, 'whether to delete the course content where we are importing to (default to 0 = No)', VALUE_DEFAULT, 0),
1525                  'options' => new external_multiple_structure(
1526                      new external_single_structure(
1527                          array(
1528                                  'name' => new external_value(PARAM_ALPHA, 'The backup option name:
1529                                              "activities" (int) Include course activites (default to 1 that is equal to yes),
1530                                              "blocks" (int) Include course blocks (default to 1 that is equal to yes),
1531                                              "filters" (int) Include course filters  (default to 1 that is equal to yes)'
1532                                              ),
1533                                  'value' => new external_value(PARAM_RAW, 'the value for the option 1 (yes) or 0 (no)'
1534                              )
1535                          )
1536                      ), 'Course import options', VALUE_DEFAULT, array()
1537                  ),
1538              )
1539          );
1540      }
1541  
1542      /**
1543       * Imports a course
1544       *
1545       * @param int $importfrom The id of the course we are importing from
1546       * @param int $importto The id of the course we are importing to
1547       * @param bool $deletecontent Whether to delete the course we are importing to content
1548       * @param array $options List of backup options
1549       * @return null
1550       * @since Moodle 2.4
1551       */
1552      public static function import_course($importfrom, $importto, $deletecontent = 0, $options = array()) {
1553          global $CFG, $USER, $DB;
1554          require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
1555          require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
1556  
1557          // Parameter validation.
1558          $params = self::validate_parameters(
1559              self::import_course_parameters(),
1560              array(
1561                  'importfrom' => $importfrom,
1562                  'importto' => $importto,
1563                  'deletecontent' => $deletecontent,
1564                  'options' => $options
1565              )
1566          );
1567  
1568          if ($params['deletecontent'] !== 0 and $params['deletecontent'] !== 1) {
1569              throw new moodle_exception('invalidextparam', 'webservice', '', $params['deletecontent']);
1570          }
1571  
1572          // Context validation.
1573  
1574          if (! ($importfrom = $DB->get_record('course', array('id'=>$params['importfrom'])))) {
1575              throw new moodle_exception('invalidcourseid', 'error');
1576          }
1577  
1578          if (! ($importto = $DB->get_record('course', array('id'=>$params['importto'])))) {
1579              throw new moodle_exception('invalidcourseid', 'error');
1580          }
1581  
1582          $importfromcontext = context_course::instance($importfrom->id);
1583          self::validate_context($importfromcontext);
1584  
1585          $importtocontext = context_course::instance($importto->id);
1586          self::validate_context($importtocontext);
1587  
1588          $backupdefaults = array(
1589              'activities' => 1,
1590              'blocks' => 1,
1591              'filters' => 1
1592          );
1593  
1594          $backupsettings = array();
1595  
1596          // Check for backup and restore options.
1597          if (!empty($params['options'])) {
1598              foreach ($params['options'] as $option) {
1599  
1600                  // Strict check for a correct value (allways 1 or 0, true or false).
1601                  $value = clean_param($option['value'], PARAM_INT);
1602  
1603                  if ($value !== 0 and $value !== 1) {
1604                      throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
1605                  }
1606  
1607                  if (!isset($backupdefaults[$option['name']])) {
1608                      throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
1609                  }
1610  
1611                  $backupsettings[$option['name']] = $value;
1612              }
1613          }
1614  
1615          // Capability checking.
1616  
1617          require_capability('moodle/backup:backuptargetimport', $importfromcontext);
1618          require_capability('moodle/restore:restoretargetimport', $importtocontext);
1619  
1620          $bc = new backup_controller(backup::TYPE_1COURSE, $importfrom->id, backup::FORMAT_MOODLE,
1621                  backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id);
1622  
1623          foreach ($backupsettings as $name => $value) {
1624              $bc->get_plan()->get_setting($name)->set_value($value);
1625          }
1626  
1627          $backupid       = $bc->get_backupid();
1628          $backupbasepath = $bc->get_plan()->get_basepath();
1629  
1630          $bc->execute_plan();
1631          $bc->destroy();
1632  
1633          // Restore the backup immediately.
1634  
1635          // Check if we must delete the contents of the destination course.
1636          if ($params['deletecontent']) {
1637              $restoretarget = backup::TARGET_EXISTING_DELETING;
1638          } else {
1639              $restoretarget = backup::TARGET_EXISTING_ADDING;
1640          }
1641  
1642          $rc = new restore_controller($backupid, $importto->id,
1643                  backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id, $restoretarget);
1644  
1645          foreach ($backupsettings as $name => $value) {
1646              $rc->get_plan()->get_setting($name)->set_value($value);
1647          }
1648  
1649          if (!$rc->execute_precheck()) {
1650              $precheckresults = $rc->get_precheck_results();
1651              if (is_array($precheckresults) && !empty($precheckresults['errors'])) {
1652                  if (empty($CFG->keeptempdirectoriesonbackup)) {
1653                      fulldelete($backupbasepath);
1654                  }
1655  
1656                  $errorinfo = '';
1657  
1658                  foreach ($precheckresults['errors'] as $error) {
1659                      $errorinfo .= $error;
1660                  }
1661  
1662                  if (array_key_exists('warnings', $precheckresults)) {
1663                      foreach ($precheckresults['warnings'] as $warning) {
1664                          $errorinfo .= $warning;
1665                      }
1666                  }
1667  
1668                  throw new moodle_exception('backupprecheckerrors', 'webservice', '', $errorinfo);
1669              }
1670          } else {
1671              if ($restoretarget == backup::TARGET_EXISTING_DELETING) {
1672                  restore_dbops::delete_course_content($importto->id);
1673              }
1674          }
1675  
1676          $rc->execute_plan();
1677          $rc->destroy();
1678  
1679          if (empty($CFG->keeptempdirectoriesonbackup)) {
1680              fulldelete($backupbasepath);
1681          }
1682  
1683          return null;
1684      }
1685  
1686      /**
1687       * Returns description of method result value
1688       *
1689       * @return external_description
1690       * @since Moodle 2.4
1691       */
1692      public static function import_course_returns() {
1693          return null;
1694      }
1695  
1696      /**
1697       * Returns description of method parameters
1698       *
1699       * @return external_function_parameters
1700       * @since Moodle 2.3
1701       */
1702      public static function get_categories_parameters() {
1703          return new external_function_parameters(
1704              array(
1705                  'criteria' => new external_multiple_structure(
1706                      new external_single_structure(
1707                          array(
1708                              'key' => new external_value(PARAM_ALPHA,
1709                                           'The category column to search, expected keys (value format) are:'.
1710                                           '"id" (int) the category id,'.
1711                                           '"ids" (string) category ids separated by commas,'.
1712                                           '"name" (string) the category name,'.
1713                                           '"parent" (int) the parent category id,'.
1714                                           '"idnumber" (string) category idnumber'.
1715                                           ' - user must have \'moodle/category:manage\' to search on idnumber,'.
1716                                           '"visible" (int) whether the returned categories must be visible or hidden. If the key is not passed,
1717                                               then the function return all categories that the user can see.'.
1718                                           ' - user must have \'moodle/category:manage\' or \'moodle/category:viewhiddencategories\' to search on visible,'.
1719                                           '"theme" (string) only return the categories having this theme'.
1720                                           ' - user must have \'moodle/category:manage\' to search on theme'),
1721                              'value' => new external_value(PARAM_RAW, 'the value to match')
1722                          )
1723                      ), 'criteria', VALUE_DEFAULT, array()
1724                  ),
1725                  'addsubcategories' => new external_value(PARAM_BOOL, 'return the sub categories infos
1726                                            (1 - default) otherwise only the category info (0)', VALUE_DEFAULT, 1)
1727              )
1728          );
1729      }
1730  
1731      /**
1732       * Get categories
1733       *
1734       * @param array $criteria Criteria to match the results
1735       * @param booln $addsubcategories obtain only the category (false) or its subcategories (true - default)
1736       * @return array list of categories
1737       * @since Moodle 2.3
1738       */
1739      public static function get_categories($criteria = array(), $addsubcategories = true) {
1740          global $CFG, $DB;
1741          require_once($CFG->dirroot . "/course/lib.php");
1742  
1743          // Validate parameters.
1744          $params = self::validate_parameters(self::get_categories_parameters(),
1745                  array('criteria' => $criteria, 'addsubcategories' => $addsubcategories));
1746  
1747          // Retrieve the categories.
1748          $categories = array();
1749          if (!empty($params['criteria'])) {
1750  
1751              $conditions = array();
1752              $wheres = array();
1753              foreach ($params['criteria'] as $crit) {
1754                  $key = trim($crit['key']);
1755  
1756                  // Trying to avoid duplicate keys.
1757                  if (!isset($conditions[$key])) {
1758  
1759                      $context = context_system::instance();
1760                      $value = null;
1761                      switch ($key) {
1762                          case 'id':
1763                              $value = clean_param($crit['value'], PARAM_INT);
1764                              $conditions[$key] = $value;
1765                              $wheres[] = $key . " = :" . $key;
1766                              break;
1767  
1768                          case 'ids':
1769                              $value = clean_param($crit['value'], PARAM_SEQUENCE);
1770                              $ids = explode(',', $value);
1771                              list($sqlids, $paramids) = $DB->get_in_or_equal($ids, SQL_PARAMS_NAMED);
1772                              $conditions = array_merge($conditions, $paramids);
1773                              $wheres[] = 'id ' . $sqlids;
1774                              break;
1775  
1776                          case 'idnumber':
1777                              if (has_capability('moodle/category:manage', $context)) {
1778                                  $value = clean_param($crit['value'], PARAM_RAW);
1779                                  $conditions[$key] = $value;
1780                                  $wheres[] = $key . " = :" . $key;
1781                              } else {
1782                                  // We must throw an exception.
1783                                  // Otherwise the dev client would think no idnumber exists.
1784                                  throw new moodle_exception('criteriaerror',
1785                                          'webservice', '', null,
1786                                          'You don\'t have the permissions to search on the "idnumber" field.');
1787                              }
1788                              break;
1789  
1790                          case 'name':
1791                              $value = clean_param($crit['value'], PARAM_TEXT);
1792                              $conditions[$key] = $value;
1793                              $wheres[] = $key . " = :" . $key;
1794                              break;
1795  
1796                          case 'parent':
1797                              $value = clean_param($crit['value'], PARAM_INT);
1798                              $conditions[$key] = $value;
1799                              $wheres[] = $key . " = :" . $key;
1800                              break;
1801  
1802                          case 'visible':
1803                              if (has_capability('moodle/category:viewhiddencategories', $context)) {
1804                                  $value = clean_param($crit['value'], PARAM_INT);
1805                                  $conditions[$key] = $value;
1806                                  $wheres[] = $key . " = :" . $key;
1807                              } else {
1808                                  throw new moodle_exception('criteriaerror',
1809                                          'webservice', '', null,
1810                                          'You don\'t have the permissions to search on the "visible" field.');
1811                              }
1812                              break;
1813  
1814                          case 'theme':
1815                              if (has_capability('moodle/category:manage', $context)) {
1816                                  $value = clean_param($crit['value'], PARAM_THEME);
1817                                  $conditions[$key] = $value;
1818                                  $wheres[] = $key . " = :" . $key;
1819                              } else {
1820                                  throw new moodle_exception('criteriaerror',
1821                                          'webservice', '', null,
1822                                          'You don\'t have the permissions to search on the "theme" field.');
1823                              }
1824                              break;
1825  
1826                          default:
1827                              throw new moodle_exception('criteriaerror',
1828                                      'webservice', '', null,
1829                                      'You can not search on this criteria: ' . $key);
1830                      }
1831                  }
1832              }
1833  
1834              if (!empty($wheres)) {
1835                  $wheres = implode(" AND ", $wheres);
1836  
1837                  $categories = $DB->get_records_select('course_categories', $wheres, $conditions);
1838  
1839                  // Retrieve its sub subcategories (all levels).
1840                  if ($categories and !empty($params['addsubcategories'])) {
1841                      $newcategories = array();
1842  
1843                      // Check if we required visible/theme checks.
1844                      $additionalselect = '';
1845                      $additionalparams = array();
1846                      if (isset($conditions['visible'])) {
1847                          $additionalselect .= ' AND visible = :visible';
1848                          $additionalparams['visible'] = $conditions['visible'];
1849                      }
1850                      if (isset($conditions['theme'])) {
1851                          $additionalselect .= ' AND theme= :theme';
1852                          $additionalparams['theme'] = $conditions['theme'];
1853                      }
1854  
1855                      foreach ($categories as $category) {
1856                          $sqlselect = $DB->sql_like('path', ':path') . $additionalselect;
1857                          $sqlparams = array('path' => $category->path.'/%') + $additionalparams; // It will NOT include the specified category.
1858                          $subcategories = $DB->get_records_select('course_categories', $sqlselect, $sqlparams);
1859                          $newcategories = $newcategories + $subcategories;   // Both arrays have integer as keys.
1860                      }
1861                      $categories = $categories + $newcategories;
1862                  }
1863              }
1864  
1865          } else {
1866              // Retrieve all categories in the database.
1867              $categories = $DB->get_records('course_categories');
1868          }
1869  
1870          // The not returned categories. key => category id, value => reason of exclusion.
1871          $excludedcats = array();
1872  
1873          // The returned categories.
1874          $categoriesinfo = array();
1875  
1876          // We need to sort the categories by path.
1877          // The parent cats need to be checked by the algo first.
1878          usort($categories, "core_course_external::compare_categories_by_path");
1879  
1880          foreach ($categories as $category) {
1881  
1882              // Check if the category is a child of an excluded category, if yes exclude it too (excluded => do not return).
1883              $parents = explode('/', $category->path);
1884              unset($parents[0]); // First key is always empty because path start with / => /1/2/4.
1885              foreach ($parents as $parentid) {
1886                  // Note: when the parent exclusion was due to the context,
1887                  // the sub category could still be returned.
1888                  if (isset($excludedcats[$parentid]) and $excludedcats[$parentid] != 'context') {
1889                      $excludedcats[$category->id] = 'parent';
1890                  }
1891              }
1892  
1893              // Check the user can use the category context.
1894              $context = context_coursecat::instance($category->id);
1895              try {
1896                  self::validate_context($context);
1897              } catch (Exception $e) {
1898                  $excludedcats[$category->id] = 'context';
1899  
1900                  // If it was the requested category then throw an exception.
1901                  if (isset($params['categoryid']) && $category->id == $params['categoryid']) {
1902                      $exceptionparam = new stdClass();
1903                      $exceptionparam->message = $e->getMessage();
1904                      $exceptionparam->catid = $category->id;
1905                      throw new moodle_exception('errorcatcontextnotvalid', 'webservice', '', $exceptionparam);
1906                  }
1907              }
1908  
1909              // Return the category information.
1910              if (!isset($excludedcats[$category->id])) {
1911  
1912                  // Final check to see if the category is visible to the user.
1913                  if (core_course_category::can_view_category($category)) {
1914  
1915                      $categoryinfo = array();
1916                      $categoryinfo['id'] = $category->id;
1917                      $categoryinfo['name'] = external_format_string($category->name, $context);
1918                      list($categoryinfo['description'], $categoryinfo['descriptionformat']) =
1919                          external_format_text($category->description, $category->descriptionformat,
1920                                  $context->id, 'coursecat', 'description', null);
1921                      $categoryinfo['parent'] = $category->parent;
1922                      $categoryinfo['sortorder'] = $category->sortorder;
1923                      $categoryinfo['coursecount'] = $category->coursecount;
1924                      $categoryinfo['depth'] = $category->depth;
1925                      $categoryinfo['path'] = $category->path;
1926  
1927                      // Some fields only returned for admin.
1928                      if (has_capability('moodle/category:manage', $context)) {
1929                          $categoryinfo['idnumber'] = $category->idnumber;
1930                          $categoryinfo['visible'] = $category->visible;
1931                          $categoryinfo['visibleold'] = $category->visibleold;
1932                          $categoryinfo['timemodified'] = $category->timemodified;
1933                          $categoryinfo['theme'] = clean_param($category->theme, PARAM_THEME);
1934                      }
1935  
1936                      $categoriesinfo[] = $categoryinfo;
1937                  } else {
1938                      $excludedcats[$category->id] = 'visibility';
1939                  }
1940              }
1941          }
1942  
1943          // Sorting the resulting array so it looks a bit better for the client developer.
1944          usort($categoriesinfo, "core_course_external::compare_categories_by_sortorder");
1945  
1946          return $categoriesinfo;
1947      }
1948  
1949      /**
1950       * Sort categories array by path
1951       * private function: only used by get_categories
1952       *
1953       * @param array $category1
1954       * @param array $category2
1955       * @return int result of strcmp
1956       * @since Moodle 2.3
1957       */
1958      private static function compare_categories_by_path($category1, $category2) {
1959          return strcmp($category1->path, $category2->path);
1960      }
1961  
1962      /**
1963       * Sort categories array by sortorder
1964       * private function: only used by get_categories
1965       *
1966       * @param array $category1
1967       * @param array $category2
1968       * @return int result of strcmp
1969       * @since Moodle 2.3
1970       */
1971      private static function compare_categories_by_sortorder($category1, $category2) {
1972          return strcmp($category1['sortorder'], $category2['sortorder']);
1973      }
1974  
1975      /**
1976       * Returns description of method result value
1977       *
1978       * @return external_description
1979       * @since Moodle 2.3
1980       */
1981      public static function get_categories_returns() {
1982          return new external_multiple_structure(
1983              new external_single_structure(
1984                  array(
1985                      'id' => new external_value(PARAM_INT, 'category id'),
1986                      'name' => new external_value(PARAM_RAW, 'category name'),
1987                      'idnumber' => new external_value(PARAM_RAW, 'category id number', VALUE_OPTIONAL),
1988                      'description' => new external_value(PARAM_RAW, 'category description'),
1989                      'descriptionformat' => new external_format_value('description'),
1990                      'parent' => new external_value(PARAM_INT, 'parent category id'),
1991                      'sortorder' => new external_value(PARAM_INT, 'category sorting order'),
1992                      'coursecount' => new external_value(PARAM_INT, 'number of courses in this category'),
1993                      'visible' => new external_value(PARAM_INT, '1: available, 0:not available', VALUE_OPTIONAL),
1994                      'visibleold' => new external_value(PARAM_INT, '1: available, 0:not available', VALUE_OPTIONAL),
1995                      'timemodified' => new external_value(PARAM_INT, 'timestamp', VALUE_OPTIONAL),
1996                      'depth' => new external_value(PARAM_INT, 'category depth'),
1997                      'path' => new external_value(PARAM_TEXT, 'category path'),
1998                      'theme' => new external_value(PARAM_THEME, 'category theme', VALUE_OPTIONAL),
1999                  ), 'List of categories'
2000              )
2001          );
2002      }
2003  
2004      /**
2005       * Returns description of method parameters
2006       *
2007       * @return external_function_parameters
2008       * @since Moodle 2.3
2009       */
2010      public static function create_categories_parameters() {
2011          return new external_function_parameters(
2012              array(
2013                  'categories' => new external_multiple_structure(
2014                          new external_single_structure(
2015                              array(
2016                                  'name' => new external_value(PARAM_TEXT, 'new category name'),
2017                                  'parent' => new external_value(PARAM_INT,
2018                                          'the parent category id inside which the new category will be created
2019                                           - set to 0 for a root category',
2020                                          VALUE_DEFAULT, 0),
2021                                  'idnumber' => new external_value(PARAM_RAW,
2022                                          'the new category idnumber', VALUE_OPTIONAL),
2023                                  'description' => new external_value(PARAM_RAW,
2024                                          'the new category description', VALUE_OPTIONAL),
2025                                  'descriptionformat' => new external_format_value('description', VALUE_DEFAULT),
2026                                  'theme' => new external_value(PARAM_THEME,
2027                                          'the new category theme. This option must be enabled on moodle',
2028                                          VALUE_OPTIONAL),
2029                          )
2030                      )
2031                  )
2032              )
2033          );
2034      }
2035  
2036      /**
2037       * Create categories
2038       *
2039       * @param array $categories - see create_categories_parameters() for the array structure
2040       * @return array - see create_categories_returns() for the array structure
2041       * @since Moodle 2.3
2042       */
2043      public static function create_categories($categories) {
2044          global $DB;
2045  
2046          $params = self::validate_parameters(self::create_categories_parameters(),
2047                          array('categories' => $categories));
2048  
2049          $transaction = $DB->start_delegated_transaction();
2050  
2051          $createdcategories = array();
2052          foreach ($params['categories'] as $category) {
2053              if ($category['parent']) {
2054                  if (!$DB->record_exists('course_categories', array('id' => $category['parent']))) {
2055                      throw new moodle_exception('unknowcategory');
2056                  }
2057                  $context = context_coursecat::instance($category['parent']);
2058              } else {
2059                  $context = context_system::instance();
2060              }
2061              self::validate_context($context);
2062              require_capability('moodle/category:manage', $context);
2063  
2064              // this will validate format and throw an exception if there are errors
2065              external_validate_format($category['descriptionformat']);
2066  
2067              $newcategory = core_course_category::create($category);
2068              $context = context_coursecat::instance($newcategory->id);
2069  
2070              $createdcategories[] = array(
2071                  'id' => $newcategory->id,
2072                  'name' => external_format_string($newcategory->name, $context),
2073              );
2074          }
2075  
2076          $transaction->allow_commit();
2077  
2078          return $createdcategories;
2079      }
2080  
2081      /**
2082       * Returns description of method parameters
2083       *
2084       * @return external_function_parameters
2085       * @since Moodle 2.3
2086       */
2087      public static function create_categories_returns() {
2088          return new external_multiple_structure(
2089              new external_single_structure(
2090                  array(
2091                      'id' => new external_value(PARAM_INT, 'new category id'),
2092                      'name' => new external_value(PARAM_RAW, 'new category name'),
2093                  )
2094              )
2095          );
2096      }
2097  
2098      /**
2099       * Returns description of method parameters
2100       *
2101       * @return external_function_parameters
2102       * @since Moodle 2.3
2103       */
2104      public static function update_categories_parameters() {
2105          return new external_function_parameters(
2106              array(
2107                  'categories' => new external_multiple_structure(
2108                      new external_single_structure(
2109                          array(
2110                              'id'       => new external_value(PARAM_INT, 'course id'),
2111                              'name' => new external_value(PARAM_TEXT, 'category name', VALUE_OPTIONAL),
2112                              'idnumber' => new external_value(PARAM_RAW, 'category id number', VALUE_OPTIONAL),
2113                              'parent' => new external_value(PARAM_INT, 'parent category id', VALUE_OPTIONAL),
2114                              'description' => new external_value(PARAM_RAW, 'category description', VALUE_OPTIONAL),
2115                              'descriptionformat' => new external_format_value('description', VALUE_DEFAULT),
2116                              'theme' => new external_value(PARAM_THEME,
2117                                      'the category theme. This option must be enabled on moodle', VALUE_OPTIONAL),
2118                          )
2119                      )
2120                  )
2121              )
2122          );
2123      }
2124  
2125      /**
2126       * Update categories
2127       *
2128       * @param array $categories The list of categories to update
2129       * @return null
2130       * @since Moodle 2.3
2131       */
2132      public static function update_categories($categories) {
2133          global $DB;
2134  
2135          // Validate parameters.
2136          $params = self::validate_parameters(self::update_categories_parameters(), array('categories' => $categories));
2137  
2138          $transaction = $DB->start_delegated_transaction();
2139  
2140          foreach ($params['categories'] as $cat) {
2141              $category = core_course_category::get($cat['id']);
2142  
2143              $categorycontext = context_coursecat::instance($cat['id']);
2144              self::validate_context($categorycontext);
2145              require_capability('moodle/category:manage', $categorycontext);
2146  
2147              // If the category parent is being changed, check for capability in the new parent category
2148              if (isset($cat['parent']) && ($cat['parent'] !== $category->parent)) {
2149                  if ($cat['parent'] == 0) {
2150                      // Creating a top level category requires capability in the system context
2151                      $parentcontext = context_system::instance();
2152                  } else {
2153                      // Category context
2154                      $parentcontext = context_coursecat::instance($cat['parent']);
2155                  }
2156                  self::validate_context($parentcontext);
2157                  require_capability('moodle/category:manage', $parentcontext);
2158              }
2159  
2160              // this will throw an exception if descriptionformat is not valid
2161              external_validate_format($cat['descriptionformat']);
2162  
2163              $category->update($cat);
2164          }
2165  
2166          $transaction->allow_commit();
2167      }
2168  
2169      /**
2170       * Returns description of method result value
2171       *
2172       * @return external_description
2173       * @since Moodle 2.3
2174       */
2175      public static function update_categories_returns() {
2176          return null;
2177      }
2178  
2179      /**
2180       * Returns description of method parameters
2181       *
2182       * @return external_function_parameters
2183       * @since Moodle 2.3
2184       */
2185      public static function delete_categories_parameters() {
2186          return new external_function_parameters(
2187              array(
2188                  'categories' => new external_multiple_structure(
2189                      new external_single_structure(
2190                          array(
2191                              'id' => new external_value(PARAM_INT, 'category id to delete'),
2192                              'newparent' => new external_value(PARAM_INT,
2193                                  'the parent category to move the contents to, if specified', VALUE_OPTIONAL),
2194                              'recursive' => new external_value(PARAM_BOOL, '1: recursively delete all contents inside this
2195                                  category, 0 (default): move contents to newparent or current parent category (except if parent is root)', VALUE_DEFAULT, 0)
2196                          )
2197                      )
2198                  )
2199              )
2200          );
2201      }
2202  
2203      /**
2204       * Delete categories
2205       *
2206       * @param array $categories A list of category ids
2207       * @return array
2208       * @since Moodle 2.3
2209       */
2210      public static function delete_categories($categories) {
2211          global $CFG, $DB;
2212          require_once($CFG->dirroot . "/course/lib.php");
2213  
2214          // Validate parameters.
2215          $params = self::validate_parameters(self::delete_categories_parameters(), array('categories' => $categories));
2216  
2217          $transaction = $DB->start_delegated_transaction();
2218  
2219          foreach ($params['categories'] as $category) {
2220              $deletecat = core_course_category::get($category['id'], MUST_EXIST);
2221              $context = context_coursecat::instance($deletecat->id);
2222              require_capability('moodle/category:manage', $context);
2223              self::validate_context($context);
2224              self::validate_context(get_category_or_system_context($deletecat->parent));
2225  
2226              if ($category['recursive']) {
2227                  // If recursive was specified, then we recursively delete the category's contents.
2228                  if ($deletecat->can_delete_full()) {
2229                      $deletecat->delete_full(false);
2230                  } else {
2231                      throw new moodle_exception('youcannotdeletecategory', '', '', $deletecat->get_formatted_name());
2232                  }
2233              } else {
2234                  // In this situation, we don't delete the category's contents, we either move it to newparent or parent.
2235                  // If the parent is the root, moving is not supported (because a course must always be inside a category).
2236                  // We must move to an existing category.
2237                  if (!empty($category['newparent'])) {
2238                      $newparentcat = core_course_category::get($category['newparent']);
2239                  } else {
2240                      $newparentcat = core_course_category::get($deletecat->parent);
2241                  }
2242  
2243                  // This operation is not allowed. We must move contents to an existing category.
2244                  if (!$newparentcat->id) {
2245                      throw new moodle_exception('movecatcontentstoroot');
2246                  }
2247  
2248                  self::validate_context(context_coursecat::instance($newparentcat->id));
2249                  if ($deletecat->can_move_content_to($newparentcat->id)) {
2250                      $deletecat->delete_move($newparentcat->id, false);
2251                  } else {
2252                      throw new moodle_exception('youcannotdeletecategory', '', '', $deletecat->get_formatted_name());
2253                  }
2254              }
2255          }
2256  
2257          $transaction->allow_commit();
2258      }
2259  
2260      /**
2261       * Returns description of method parameters
2262       *
2263       * @return external_function_parameters
2264       * @since Moodle 2.3
2265       */
2266      public static function delete_categories_returns() {
2267          return null;
2268      }
2269  
2270      /**
2271       * Describes the parameters for delete_modules.
2272       *
2273       * @return external_function_parameters
2274       * @since Moodle 2.5
2275       */
2276      public static function delete_modules_parameters() {
2277          return new external_function_parameters (
2278              array(
2279                  'cmids' => new external_multiple_structure(new external_value(PARAM_INT, 'course module ID',
2280                          VALUE_REQUIRED, '', NULL_NOT_ALLOWED), 'Array of course module IDs'),
2281              )
2282          );
2283      }
2284  
2285      /**
2286       * Deletes a list of provided module instances.
2287       *
2288       * @param array $cmids the course module ids
2289       * @since Moodle 2.5
2290       */
2291      public static function delete_modules($cmids) {
2292          global $CFG, $DB;
2293  
2294          // Require course file containing the course delete module function.
2295          require_once($CFG->dirroot . "/course/lib.php");
2296  
2297          // Clean the parameters.
2298          $params = self::validate_parameters(self::delete_modules_parameters(), array('cmids' => $cmids));
2299  
2300          // Keep track of the course ids we have performed a capability check on to avoid repeating.
2301          $arrcourseschecked = array();
2302  
2303          foreach ($params['cmids'] as $cmid) {
2304              // Get the course module.
2305              $cm = $DB->get_record('course_modules', array('id' => $cmid), '*', MUST_EXIST);
2306  
2307              // Check if we have not yet confirmed they have permission in this course.
2308              if (!in_array($cm->course, $arrcourseschecked)) {
2309                  // Ensure the current user has required permission in this course.
2310                  $context = context_course::instance($cm->course);
2311                  self::validate_context($context);
2312                  // Add to the array.
2313                  $arrcourseschecked[] = $cm->course;
2314              }
2315  
2316              // Ensure they can delete this module.
2317              $modcontext = context_module::instance($cm->id);
2318              require_capability('moodle/course:manageactivities', $modcontext);
2319  
2320              // Delete the module.
2321              course_delete_module($cm->id);
2322          }
2323      }
2324  
2325      /**
2326       * Describes the delete_modules return value.
2327       *
2328       * @return external_single_structure
2329       * @since Moodle 2.5
2330       */
2331      public static function delete_modules_returns() {
2332          return null;
2333      }
2334  
2335      /**
2336       * Returns description of method parameters
2337       *
2338       * @return external_function_parameters
2339       * @since Moodle 2.9
2340       */
2341      public static function view_course_parameters() {
2342          return new external_function_parameters(
2343              array(
2344                  'courseid' => new external_value(PARAM_INT, 'id of the course'),
2345                  'sectionnumber' => new external_value(PARAM_INT, 'section number', VALUE_DEFAULT, 0)
2346              )
2347          );
2348      }
2349  
2350      /**
2351       * Trigger the course viewed event.
2352       *
2353       * @param int $courseid id of course
2354       * @param int $sectionnumber sectionnumber (0, 1, 2...)
2355       * @return array of warnings and status result
2356       * @since Moodle 2.9
2357       * @throws moodle_exception
2358       */
2359      public static function view_course($courseid, $sectionnumber = 0) {
2360          global $CFG;
2361          require_once($CFG->dirroot . "/course/lib.php");
2362  
2363          $params = self::validate_parameters(self::view_course_parameters(),
2364                                              array(
2365                                                  'courseid' => $courseid,
2366                                                  'sectionnumber' => $sectionnumber
2367                                              ));
2368  
2369          $warnings = array();
2370  
2371          $course = get_course($params['courseid']);
2372          $context = context_course::instance($course->id);
2373          self::validate_context($context);
2374  
2375          if (!empty($params['sectionnumber'])) {
2376  
2377              // Get section details and check it exists.
2378              $modinfo = get_fast_modinfo($course);
2379              $coursesection = $modinfo->get_section_info($params['sectionnumber'], MUST_EXIST);
2380  
2381              // Check user is allowed to see it.
2382              if (!$coursesection->uservisible) {
2383                  require_capability('moodle/course:viewhiddensections', $context);
2384              }
2385          }
2386  
2387          course_view($context, $params['sectionnumber']);
2388  
2389          $result = array();
2390          $result['status'] = true;
2391          $result['warnings'] = $warnings;
2392          return $result;
2393      }
2394  
2395      /**
2396       * Returns description of method result value
2397       *
2398       * @return external_description
2399       * @since Moodle 2.9
2400       */
2401      public static function view_course_returns() {
2402          return new external_single_structure(
2403              array(
2404                  'status' => new external_value(PARAM_BOOL, 'status: true if success'),
2405                  'warnings' => new external_warnings()
2406              )
2407          );
2408      }
2409  
2410      /**
2411       * Returns description of method parameters
2412       *
2413       * @return external_function_parameters
2414       * @since Moodle 3.0
2415       */
2416      public static function search_courses_parameters() {
2417          return new external_function_parameters(
2418              array(
2419                  'criterianame'  => new external_value(PARAM_ALPHA, 'criteria name
2420                                                          (search, modulelist (only admins), blocklist (only admins), tagid)'),
2421                  'criteriavalue' => new external_value(PARAM_RAW, 'criteria value'),
2422                  'page'          => new external_value(PARAM_INT, 'page number (0 based)', VALUE_DEFAULT, 0),
2423                  'perpage'       => new external_value(PARAM_INT, 'items per page', VALUE_DEFAULT, 0),
2424                  'requiredcapabilities' => new external_multiple_structure(
2425                      new external_value(PARAM_CAPABILITY, 'Capability string used to filter courses by permission'),
2426                      'Optional list of required capabilities (used to filter the list)', VALUE_DEFAULT, array()
2427                  ),
2428                  'limittoenrolled' => new external_value(PARAM_BOOL, 'limit to enrolled courses', VALUE_DEFAULT, 0),
2429                  'onlywithcompletion' => new external_value(PARAM_BOOL, 'limit to courses where completion is enabled',
2430                      VALUE_DEFAULT, 0),
2431              )
2432          );
2433      }
2434  
2435      /**
2436       * Return the course information that is public (visible by every one)
2437       *
2438       * @param  core_course_list_element $course        course in list object
2439       * @param  stdClass       $coursecontext course context object
2440       * @return array the course information
2441       * @since  Moodle 3.2
2442       */
2443      protected static function get_course_public_information(core_course_list_element $course, $coursecontext) {
2444  
2445          static $categoriescache = array();
2446  
2447          // Category information.
2448          if (!array_key_exists($course->category, $categoriescache)) {
2449              $categoriescache[$course->category] = core_course_category::get($course->category, IGNORE_MISSING);
2450          }
2451          $category = $categoriescache[$course->category];
2452  
2453          // Retrieve course overview used files.
2454          $files = array();
2455          foreach ($course->get_course_overviewfiles() as $file) {
2456              $fileurl = moodle_url::make_webservice_pluginfile_url($file->get_contextid(), $file->get_component(),
2457                                                                      $file->get_filearea(), null, $file->get_filepath(),
2458                                                                      $file->get_filename())->out(false);
2459              $files[] = array(
2460                  'filename' => $file->get_filename(),
2461                  'fileurl' => $fileurl,
2462                  'filesize' => $file->get_filesize(),
2463                  'filepath' => $file->get_filepath(),
2464                  'mimetype' => $file->get_mimetype(),
2465                  'timemodified' => $file->get_timemodified(),
2466              );
2467          }
2468  
2469          // Retrieve the course contacts,
2470          // we need here the users fullname since if we are not enrolled can be difficult to obtain them via other Web Services.
2471          $coursecontacts = array();
2472          foreach ($course->get_course_contacts() as $contact) {
2473               $coursecontacts[] = array(
2474                  'id' => $contact['user']->id,
2475                  'fullname' => $contact['username'],
2476                  'roles' => array_map(function($role){
2477                      return array('id' => $role->id, 'name' => $role->displayname);
2478                  }, $contact['roles']),
2479                  'role' => array('id' => $contact['role']->id, 'name' => $contact['role']->displayname),
2480                  'rolename' => $contact['rolename']
2481               );
2482          }
2483  
2484          // Allowed enrolment methods (maybe we can self-enrol).
2485          $enroltypes = array();
2486          $instances = enrol_get_instances($course->id, true);
2487          foreach ($instances as $instance) {
2488              $enroltypes[] = $instance->enrol;
2489          }
2490  
2491          // Format summary.
2492          list($summary, $summaryformat) =
2493              external_format_text($course->summary, $course->summaryformat, $coursecontext->id, 'course', 'summary', null);
2494  
2495          $categoryname = '';
2496          if (!empty($category)) {
2497              $categoryname = external_format_string($category->name, $category->get_context());
2498          }
2499  
2500          $displayname = get_course_display_name_for_list($course);
2501          $coursereturns = array();
2502          $coursereturns['id']                = $course->id;
2503          $coursereturns['fullname']          = external_format_string($course->fullname, $coursecontext->id);
2504          $coursereturns['displayname']       = external_format_string($displayname, $coursecontext->id);
2505          $coursereturns['shortname']         = external_format_string($course->shortname, $coursecontext->id);
2506          $coursereturns['categoryid']        = $course->category;
2507          $coursereturns['categoryname']      = $categoryname;
2508          $coursereturns['summary']           = $summary;
2509          $coursereturns['summaryformat']     = $summaryformat;
2510          $coursereturns['summaryfiles']      = external_util::get_area_files($coursecontext->id, 'course', 'summary', false, false);
2511          $coursereturns['overviewfiles']     = $files;
2512          $coursereturns['contacts']          = $coursecontacts;
2513          $coursereturns['enrollmentmethods'] = $enroltypes;
2514          $coursereturns['sortorder']         = $course->sortorder;
2515          $coursereturns['showactivitydates'] = $course->showactivitydates;
2516          $coursereturns['showcompletionconditions'] = $course->showcompletionconditions;
2517  
2518          $handler = core_course\customfield\course_handler::create();
2519          if ($customfields = $handler->export_instance_data($course->id)) {
2520              $coursereturns['customfields'] = [];
2521              foreach ($customfields as $data) {
2522                  $coursereturns['customfields'][] = [
2523                      'type' => $data->get_type(),
2524                      'value' => $data->get_value(),
2525                      'valueraw' => $data->get_data_controller()->get_value(),
2526                      'name' => $data->get_name(),
2527                      'shortname' => $data->get_shortname()
2528                  ];
2529              }
2530          }
2531  
2532          return $coursereturns;
2533      }
2534  
2535      /**
2536       * Search courses following the specified criteria.
2537       *
2538       * @param string $criterianame  Criteria name (search, modulelist (only admins), blocklist (only admins), tagid)
2539       * @param string $criteriavalue Criteria value
2540       * @param int $page             Page number (for pagination)
2541       * @param int $perpage          Items per page
2542       * @param array $requiredcapabilities Optional list of required capabilities (used to filter the list).
2543       * @param int $limittoenrolled  Limit to only enrolled courses
2544       * @param int onlywithcompletion Limit to only courses where completion is enabled
2545       * @return array of course objects and warnings
2546       * @since Moodle 3.0
2547       * @throws moodle_exception
2548       */
2549      public static function search_courses($criterianame,
2550                                            $criteriavalue,
2551                                            $page=0,
2552                                            $perpage=0,
2553                                            $requiredcapabilities=array(),
2554                                            $limittoenrolled=0,
2555                                            $onlywithcompletion=0) {
2556          global $CFG;
2557  
2558          $warnings = array();
2559  
2560          $parameters = array(
2561              'criterianame'  => $criterianame,
2562              'criteriavalue' => $criteriavalue,
2563              'page'          => $page,
2564              'perpage'       => $perpage,
2565              'requiredcapabilities' => $requiredcapabilities,
2566              'limittoenrolled' => $limittoenrolled,
2567              'onlywithcompletion' => $onlywithcompletion
2568          );
2569          $params = self::validate_parameters(self::search_courses_parameters(), $parameters);
2570          self::validate_context(context_system::instance());
2571  
2572          $allowedcriterianames = array('search', 'modulelist', 'blocklist', 'tagid');
2573          if (!in_array($params['criterianame'], $allowedcriterianames)) {
2574              throw new invalid_parameter_exception('Invalid value for criterianame parameter (value: '.$params['criterianame'].'),' .
2575                  'allowed values are: '.implode(',', $allowedcriterianames));
2576          }
2577  
2578          if ($params['criterianame'] == 'modulelist' or $params['criterianame'] == 'blocklist') {
2579              require_capability('moodle/site:config', context_system::instance());
2580          }
2581  
2582          $paramtype = array(
2583              'search' => PARAM_RAW,
2584              'modulelist' => PARAM_PLUGIN,
2585              'blocklist' => PARAM_INT,
2586              'tagid' => PARAM_INT
2587          );
2588          $params['criteriavalue'] = clean_param($params['criteriavalue'], $paramtype[$params['criterianame']]);
2589  
2590          // Prepare the search API options.
2591          $searchcriteria = array();
2592          $searchcriteria[$params['criterianame']] = $params['criteriavalue'];
2593          if ($params['onlywithcompletion']) {
2594              $searchcriteria['onlywithcompletion'] = true;
2595          }
2596  
2597          $options = array();
2598          if ($params['perpage'] != 0) {
2599              $offset = $params['page'] * $params['perpage'];
2600              $options = array('offset' => $offset, 'limit' => $params['perpage']);
2601          }
2602  
2603          // Search the courses.
2604          $courses = core_course_category::search_courses($searchcriteria, $options, $params['requiredcapabilities']);
2605          $totalcount = core_course_category::search_courses_count($searchcriteria, $options, $params['requiredcapabilities']);
2606  
2607          if (!empty($limittoenrolled)) {
2608              // Get the courses where the current user has access.
2609              $enrolled = enrol_get_my_courses(array('id', 'cacherev'));
2610          }
2611  
2612          $finalcourses = array();
2613          $categoriescache = array();
2614  
2615          foreach ($courses as $course) {
2616              if (!empty($limittoenrolled)) {
2617                  // Filter out not enrolled courses.
2618                  if (!isset($enrolled[$course->id])) {
2619                      $totalcount--;
2620                      continue;
2621                  }
2622              }
2623  
2624              $coursecontext = context_course::instance($course->id);
2625  
2626              $finalcourses[] = self::get_course_public_information($course, $coursecontext);
2627          }
2628  
2629          return array(
2630              'total' => $totalcount,
2631              'courses' => $finalcourses,
2632              'warnings' => $warnings
2633          );
2634      }
2635  
2636      /**
2637       * Returns a course structure definition
2638       *
2639       * @param  boolean $onlypublicdata set to true, to retrieve only fields viewable by anyone when the course is visible
2640       * @return array the course structure
2641       * @since  Moodle 3.2
2642       */
2643      protected static function get_course_structure($onlypublicdata = true) {
2644          $coursestructure = array(
2645              'id' => new external_value(PARAM_INT, 'course id'),
2646              'fullname' => new external_value(PARAM_RAW, 'course full name'),
2647              'displayname' => new external_value(PARAM_RAW, 'course display name'),
2648              'shortname' => new external_value(PARAM_RAW, 'course short name'),
2649              'categoryid' => new external_value(PARAM_INT, 'category id'),
2650              'categoryname' => new external_value(PARAM_RAW, 'category name'),
2651              'sortorder' => new external_value(PARAM_INT, 'Sort order in the category', VALUE_OPTIONAL),
2652              'summary' => new external_value(PARAM_RAW, 'summary'),
2653              'summaryformat' => new external_format_value('summary'),
2654              'summaryfiles' => new external_files('summary files in the summary field', VALUE_OPTIONAL),
2655              'overviewfiles' => new external_files('additional overview files attached to this course'),
2656              'showactivitydates' => new external_value(PARAM_BOOL, 'Whether the activity dates are shown or not'),
2657              'showcompletionconditions' => new external_value(PARAM_BOOL,
2658                  'Whether the activity completion conditions are shown or not'),
2659              'contacts' => new external_multiple_structure(
2660                  new external_single_structure(
2661                      array(
2662                          'id' => new external_value(PARAM_INT, 'contact user id'),
2663                          'fullname'  => new external_value(PARAM_NOTAGS, 'contact user fullname'),
2664                      )
2665                  ),
2666                  'contact users'
2667              ),
2668              'enrollmentmethods' => new external_multiple_structure(
2669                  new external_value(PARAM_PLUGIN, 'enrollment method'),
2670                  'enrollment methods list'
2671              ),
2672              'customfields' => new external_multiple_structure(
2673                  new external_single_structure(
2674                      array(
2675                          'name' => new external_value(PARAM_RAW, 'The name of the custom field'),
2676                          'shortname' => new external_value(PARAM_RAW,
2677                              'The shortname of the custom field - to be able to build the field class in the code'),
2678                          'type'  => new external_value(PARAM_ALPHANUMEXT,
2679                              'The type of the custom field - text field, checkbox...'),
2680                          'valueraw' => new external_value(PARAM_RAW, 'The raw value of the custom field'),
2681                          'value' => new external_value(PARAM_RAW, 'The value of the custom field'),
2682                      )
2683                  ), 'Custom fields', VALUE_OPTIONAL),
2684          );
2685  
2686          if (!$onlypublicdata) {
2687              $extra = array(
2688                  'idnumber' => new external_value(PARAM_RAW, 'Id number', VALUE_OPTIONAL),
2689                  'format' => new external_value(PARAM_PLUGIN, 'Course format: weeks, topics, social, site,..', VALUE_OPTIONAL),
2690                  'showgrades' => new external_value(PARAM_INT, '1 if grades are shown, otherwise 0', VALUE_OPTIONAL),
2691                  'newsitems' => new external_value(PARAM_INT, 'Number of recent items appearing on the course page', VALUE_OPTIONAL),
2692                  'startdate' => new external_value(PARAM_INT, 'Timestamp when the course start', VALUE_OPTIONAL),
2693                  'enddate' => new external_value(PARAM_INT, 'Timestamp when the course end', VALUE_OPTIONAL),
2694                  'maxbytes' => new external_value(PARAM_INT, 'Largest size of file that can be uploaded into', VALUE_OPTIONAL),
2695                  'showreports' => new external_value(PARAM_INT, 'Are activity report shown (yes = 1, no =0)', VALUE_OPTIONAL),
2696                  'visible' => new external_value(PARAM_INT, '1: available to student, 0:not available', VALUE_OPTIONAL),
2697                  'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible', VALUE_OPTIONAL),
2698                  'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no', VALUE_OPTIONAL),
2699                  'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id', VALUE_OPTIONAL),
2700                  'enablecompletion' => new external_value(PARAM_INT, 'Completion enabled? 1: yes 0: no', VALUE_OPTIONAL),
2701                  'completionnotify' => new external_value(PARAM_INT, '1: yes 0: no', VALUE_OPTIONAL),
2702                  'lang' => new external_value(PARAM_SAFEDIR, 'Forced course language', VALUE_OPTIONAL),
2703                  'theme' => new external_value(PARAM_PLUGIN, 'Fame of the forced theme', VALUE_OPTIONAL),
2704                  'marker' => new external_value(PARAM_INT, 'Current course marker', VALUE_OPTIONAL),
2705                  'legacyfiles' => new external_value(PARAM_INT, 'If legacy files are enabled', VALUE_OPTIONAL),
2706                  'calendartype' => new external_value(PARAM_PLUGIN, 'Calendar type', VALUE_OPTIONAL),
2707                  'timecreated' => new external_value(PARAM_INT, 'Time when the course was created', VALUE_OPTIONAL),
2708                  'timemodified' => new external_value(PARAM_INT, 'Last time  the course was updated', VALUE_OPTIONAL),
2709                  'requested' => new external_value(PARAM_INT, 'If is a requested course', VALUE_OPTIONAL),
2710                  'cacherev' => new external_value(PARAM_INT, 'Cache revision number', VALUE_OPTIONAL),
2711                  'filters' => new external_multiple_structure(
2712                      new external_single_structure(
2713                          array(
2714                              'filter'  => new external_value(PARAM_PLUGIN, 'Filter plugin name'),
2715                              'localstate' => new external_value(PARAM_INT, 'Filter state: 1 for on, -1 for off, 0 if inherit'),
2716                              'inheritedstate' => new external_value(PARAM_INT, '1 or 0 to use when localstate is set to inherit'),
2717                          )
2718                      ),
2719                      'Course filters', VALUE_OPTIONAL
2720                  ),
2721                  'courseformatoptions' => new external_multiple_structure(
2722                      new external_single_structure(
2723                          array(
2724                              'name' => new external_value(PARAM_RAW, 'Course format option name.'),
2725                              'value' => new external_value(PARAM_RAW, 'Course format option value.'),
2726                          )
2727                      ),
2728                      'Additional options for particular course format.', VALUE_OPTIONAL
2729                  ),
2730              );
2731              $coursestructure = array_merge($coursestructure, $extra);
2732          }
2733          return new external_single_structure($coursestructure);
2734      }
2735  
2736      /**
2737       * Returns description of method result value
2738       *
2739       * @return external_description
2740       * @since Moodle 3.0
2741       */
2742      public static function search_courses_returns() {
2743          return new external_single_structure(
2744              array(
2745                  'total' => new external_value(PARAM_INT, 'total course count'),
2746                  'courses' => new external_multiple_structure(self::get_course_structure(), 'course'),
2747                  'warnings' => new external_warnings()
2748              )
2749          );
2750      }
2751  
2752      /**
2753       * Returns description of method parameters
2754       *
2755       * @return external_function_parameters
2756       * @since Moodle 3.0
2757       */
2758      public static function get_course_module_parameters() {
2759          return new external_function_parameters(
2760              array(
2761                  'cmid' => new external_value(PARAM_INT, 'The course module id')
2762              )
2763          );
2764      }
2765  
2766      /**
2767       * Return information about a course module.
2768       *
2769       * @param int $cmid the course module id
2770       * @return array of warnings and the course module
2771       * @since Moodle 3.0
2772       * @throws moodle_exception
2773       */
2774      public static function get_course_module($cmid) {
2775          global $CFG, $DB;
2776  
2777          $params = self::validate_parameters(self::get_course_module_parameters(), array('cmid' => $cmid));
2778          $warnings = array();
2779  
2780          $cm = get_coursemodule_from_id(null, $params['cmid'], 0, true, MUST_EXIST);
2781          $context = context_module::instance($cm->id);
2782          self::validate_context($context);
2783  
2784          // If the user has permissions to manage the activity, return all the information.
2785          if (has_capability('moodle/course:manageactivities', $context)) {
2786              require_once($CFG->dirroot . '/course/modlib.php');
2787              require_once($CFG->libdir . '/gradelib.php');
2788  
2789              $info = $cm;
2790              // Get the extra information: grade, advanced grading and outcomes data.
2791              $course = get_course($cm->course);
2792              list($newcm, $newcontext, $module, $extrainfo, $cw) = get_moduleinfo_data($cm, $course);
2793              // Grades.
2794              $gradeinfo = array('grade', 'gradepass', 'gradecat');
2795              foreach ($gradeinfo as $gfield) {
2796                  if (isset($extrainfo->{$gfield})) {
2797                      $info->{$gfield} = $extrainfo->{$gfield};
2798                  }
2799              }
2800              if (isset($extrainfo->grade) and $extrainfo->grade < 0) {
2801                  $info->scale = $DB->get_field('scale', 'scale', array('id' => abs($extrainfo->grade)));
2802              }
2803              // Advanced grading.
2804              if (isset($extrainfo->_advancedgradingdata)) {
2805                  $info->advancedgrading = array();
2806                  foreach ($extrainfo as $key => $val) {
2807                      if (strpos($key, 'advancedgradingmethod_') === 0) {
2808                          $info->advancedgrading[] = array(
2809                              'area' => str_replace('advancedgradingmethod_', '', $key),
2810                              'method' => $val
2811                          );
2812                      }
2813                  }
2814              }
2815              // Outcomes.
2816              foreach ($extrainfo as $key => $val) {
2817                  if (strpos($key, 'outcome_') === 0) {
2818                      if (!isset($info->outcomes)) {
2819                          $info->outcomes = array();
2820                      }
2821                      $id = str_replace('outcome_', '', $key);
2822                      $outcome = grade_outcome::fetch(array('id' => $id));
2823                      $scaleitems = $outcome->load_scale();
2824                      $info->outcomes[] = array(
2825                          'id' => $id,
2826                          'name' => external_format_string($outcome->get_name(), $context->id),
2827                          'scale' => $scaleitems->scale
2828                      );
2829                  }
2830              }
2831          } else {
2832              // Return information is safe to show to any user.
2833              $info = new stdClass();
2834              $info->id = $cm->id;
2835              $info->course = $cm->course;
2836              $info->module = $cm->module;
2837              $info->modname = $cm->modname;
2838              $info->instance = $cm->instance;
2839              $info->section = $cm->section;
2840              $info->sectionnum = $cm->sectionnum;
2841              $info->groupmode = $cm->groupmode;
2842              $info->groupingid = $cm->groupingid;
2843              $info->completion = $cm->completion;
2844              $info->downloadcontent = $cm->downloadcontent;
2845          }
2846          // Format name.
2847          $info->name = external_format_string($cm->name, $context->id);
2848          $result = array();
2849          $result['cm'] = $info;
2850          $result['warnings'] = $warnings;
2851          return $result;
2852      }
2853  
2854      /**
2855       * Returns description of method result value
2856       *
2857       * @return external_description
2858       * @since Moodle 3.0
2859       */
2860      public static function get_course_module_returns() {
2861          return new external_single_structure(
2862              array(
2863                  'cm' => new external_single_structure(
2864                      array(
2865                          'id' => new external_value(PARAM_INT, 'The course module id'),
2866                          'course' => new external_value(PARAM_INT, 'The course id'),
2867                          'module' => new external_value(PARAM_INT, 'The module type id'),
2868                          'name' => new external_value(PARAM_RAW, 'The activity name'),
2869                          'modname' => new external_value(PARAM_COMPONENT, 'The module component name (forum, assign, etc..)'),
2870                          'instance' => new external_value(PARAM_INT, 'The activity instance id'),
2871                          'section' => new external_value(PARAM_INT, 'The module section id'),
2872                          'sectionnum' => new external_value(PARAM_INT, 'The module section number'),
2873                          'groupmode' => new external_value(PARAM_INT, 'Group mode'),
2874                          'groupingid' => new external_value(PARAM_INT, 'Grouping id'),
2875                          'completion' => new external_value(PARAM_INT, 'If completion is enabled'),
2876                          'idnumber' => new external_value(PARAM_RAW, 'Module id number', VALUE_OPTIONAL),
2877                          'added' => new external_value(PARAM_INT, 'Time added', VALUE_OPTIONAL),
2878                          'score' => new external_value(PARAM_INT, 'Score', VALUE_OPTIONAL),
2879                          'indent' => new external_value(PARAM_INT, 'Indentation', VALUE_OPTIONAL),
2880                          'visible' => new external_value(PARAM_INT, 'If visible', VALUE_OPTIONAL),
2881                          'visibleoncoursepage' => new external_value(PARAM_INT, 'If visible on course page', VALUE_OPTIONAL),
2882                          'visibleold' => new external_value(PARAM_INT, 'Visible old', VALUE_OPTIONAL),
2883                          'completiongradeitemnumber' => new external_value(PARAM_INT, 'Completion grade item', VALUE_OPTIONAL),
2884                          'completionpassgrade' => new external_value(PARAM_INT, 'Completion pass grade setting', VALUE_OPTIONAL),
2885                          'completionview' => new external_value(PARAM_INT, 'Completion view setting', VALUE_OPTIONAL),
2886                          'completionexpected' => new external_value(PARAM_INT, 'Completion time expected', VALUE_OPTIONAL),
2887                          'showdescription' => new external_value(PARAM_INT, 'If the description is showed', VALUE_OPTIONAL),
2888                          'downloadcontent' => new external_value(PARAM_INT, 'The download content value', VALUE_OPTIONAL),
2889                          'availability' => new external_value(PARAM_RAW, 'Availability settings', VALUE_OPTIONAL),
2890                          'grade' => new external_value(PARAM_FLOAT, 'Grade (max value or scale id)', VALUE_OPTIONAL),
2891                          'scale' => new external_value(PARAM_TEXT, 'Scale items (if used)', VALUE_OPTIONAL),
2892                          'gradepass' => new external_value(PARAM_RAW, 'Grade to pass (float)', VALUE_OPTIONAL),
2893                          'gradecat' => new external_value(PARAM_INT, 'Grade category', VALUE_OPTIONAL),
2894                          'advancedgrading' => new external_multiple_structure(
2895                              new external_single_structure(
2896                                  array(
2897                                      'area' => new external_value(PARAM_AREA, 'Gradable area name'),
2898                                      'method'  => new external_value(PARAM_COMPONENT, 'Grading method'),
2899                                  )
2900                              ),
2901                              'Advanced grading settings', VALUE_OPTIONAL
2902                          ),
2903                          'outcomes' => new external_multiple_structure(
2904                              new external_single_structure(
2905                                  array(
2906                                      'id' => new external_value(PARAM_ALPHANUMEXT, 'Outcome id'),
2907                                      'name'  => new external_value(PARAM_RAW, 'Outcome full name'),
2908                                      'scale' => new external_value(PARAM_TEXT, 'Scale items')
2909                                  )
2910                              ),
2911                              'Outcomes information', VALUE_OPTIONAL
2912                          ),
2913                      )
2914                  ),
2915                  'warnings' => new external_warnings()
2916              )
2917          );
2918      }
2919  
2920      /**
2921       * Returns description of method parameters
2922       *
2923       * @return external_function_parameters
2924       * @since Moodle 3.0
2925       */
2926      public static function get_course_module_by_instance_parameters() {
2927          return new external_function_parameters(
2928              array(
2929                  'module' => new external_value(PARAM_COMPONENT, 'The module name'),
2930                  'instance' => new external_value(PARAM_INT, 'The module instance id')
2931              )
2932          );
2933      }
2934  
2935      /**
2936       * Return information about a course module.
2937       *
2938       * @param string $module the module name
2939       * @param int $instance the activity instance id
2940       * @return array of warnings and the course module
2941       * @since Moodle 3.0
2942       * @throws moodle_exception
2943       */
2944      public static function get_course_module_by_instance($module, $instance) {
2945  
2946          $params = self::validate_parameters(self::get_course_module_by_instance_parameters(),
2947                                              array(
2948                                                  'module' => $module,
2949                                                  'instance' => $instance,
2950                                              ));
2951  
2952          $warnings = array();
2953          $cm = get_coursemodule_from_instance($params['module'], $params['instance'], 0, false, MUST_EXIST);
2954  
2955          return self::get_course_module($cm->id);
2956      }
2957  
2958      /**
2959       * Returns description of method result value
2960       *
2961       * @return external_description
2962       * @since Moodle 3.0
2963       */
2964      public static function get_course_module_by_instance_returns() {
2965          return self::get_course_module_returns();
2966      }
2967  
2968      /**
2969       * Returns description of method parameters
2970       *
2971       * @return external_function_parameters
2972       * @since Moodle 3.2
2973       */
2974      public static function get_user_navigation_options_parameters() {
2975          return new external_function_parameters(
2976              array(
2977                  'courseids' => new external_multiple_structure(new external_value(PARAM_INT, 'Course id.')),
2978              )
2979          );
2980      }
2981  
2982      /**
2983       * Return a list of navigation options in a set of courses that are avaialable or not for the current user.
2984       *
2985       * @param array $courseids a list of course ids
2986       * @return array of warnings and the options availability
2987       * @since Moodle 3.2
2988       * @throws moodle_exception
2989       */
2990      public static function get_user_navigation_options($courseids) {
2991          global $CFG;
2992          require_once($CFG->dirroot . '/course/lib.php');
2993  
2994          // Parameter validation.
2995          $params = self::validate_parameters(self::get_user_navigation_options_parameters(), array('courseids' => $courseids));
2996          $courseoptions = array();
2997  
2998          list($courses, $warnings) = external_util::validate_courses($params['courseids'], array(), true);
2999  
3000          if (!empty($courses)) {
3001              foreach ($courses as $course) {
3002                  // Fix the context for the frontpage.
3003                  if ($course->id == SITEID) {
3004                      $course->context = context_system::instance();
3005                  }
3006                  $navoptions = course_get_user_navigation_options($course->context, $course);
3007                  $options = array();
3008                  foreach ($navoptions as $name => $available) {
3009                      $options[] = array(
3010                          'name' => $name,
3011                          'available' => $available,
3012                      );
3013                  }
3014  
3015                  $courseoptions[] = array(
3016                      'id' => $course->id,
3017                      'options' => $options
3018                  );
3019              }
3020          }
3021  
3022          $result = array(
3023              'courses' => $courseoptions,
3024              'warnings' => $warnings
3025          );
3026          return $result;
3027      }
3028  
3029      /**
3030       * Returns description of method result value
3031       *
3032       * @return external_description
3033       * @since Moodle 3.2
3034       */
3035      public static function get_user_navigation_options_returns() {
3036          return new external_single_structure(
3037              array(
3038                  'courses' => new external_multiple_structure(
3039                      new external_single_structure(
3040                          array(
3041                              'id' => new external_value(PARAM_INT, 'Course id'),
3042                              'options' => new external_multiple_structure(
3043                                  new external_single_structure(
3044                                      array(
3045                                          'name' => new external_value(PARAM_ALPHANUMEXT, 'Option name'),
3046                                          'available' => new external_value(PARAM_BOOL, 'Whether the option is available or not'),
3047                                      )
3048                                  )
3049                              )
3050                          )
3051                      ), 'List of courses'
3052                  ),
3053                  'warnings' => new external_warnings()
3054              )
3055          );
3056      }
3057  
3058      /**
3059       * Returns description of method parameters
3060       *
3061       * @return external_function_parameters
3062       * @since Moodle 3.2
3063       */
3064      public static function get_user_administration_options_parameters() {
3065          return new external_function_parameters(
3066              array(
3067                  'courseids' => new external_multiple_structure(new external_value(PARAM_INT, 'Course id.')),
3068              )
3069          );
3070      }
3071  
3072      /**
3073       * Return a list of administration options in a set of courses that are available or not for the current user.
3074       *
3075       * @param array $courseids a list of course ids
3076       * @return array of warnings and the options availability
3077       * @since Moodle 3.2
3078       * @throws moodle_exception
3079       */
3080      public static function get_user_administration_options($courseids) {
3081          global $CFG;
3082          require_once($CFG->dirroot . '/course/lib.php');
3083  
3084          // Parameter validation.
3085          $params = self::validate_parameters(self::get_user_administration_options_parameters(), array('courseids' => $courseids));
3086          $courseoptions = array();
3087  
3088          list($courses, $warnings) = external_util::validate_courses($params['courseids'], array(), true);
3089  
3090          if (!empty($courses)) {
3091              foreach ($courses as $course) {
3092                  $adminoptions = course_get_user_administration_options($course, $course->context);
3093                  $options = array();
3094                  foreach ($adminoptions as $name => $available) {
3095                      $options[] = array(
3096                          'name' => $name,
3097                          'available' => $available,
3098                      );
3099                  }
3100  
3101                  $courseoptions[] = array(
3102                      'id' => $course->id,
3103                      'options' => $options
3104                  );
3105              }
3106          }
3107  
3108          $result = array(
3109              'courses' => $courseoptions,
3110              'warnings' => $warnings
3111          );
3112          return $result;
3113      }
3114  
3115      /**
3116       * Returns description of method result value
3117       *
3118       * @return external_description
3119       * @since Moodle 3.2
3120       */
3121      public static function get_user_administration_options_returns() {
3122          return self::get_user_navigation_options_returns();
3123      }
3124  
3125      /**
3126       * Returns description of method parameters
3127       *
3128       * @return external_function_parameters
3129       * @since Moodle 3.2
3130       */
3131      public static function get_courses_by_field_parameters() {
3132          return new external_function_parameters(
3133              array(
3134                  'field' => new external_value(PARAM_ALPHA, 'The field to search can be left empty for all courses or:
3135                      id: course id
3136                      ids: comma separated course ids
3137                      shortname: course short name
3138                      idnumber: course id number
3139                      category: category id the course belongs to
3140                  ', VALUE_DEFAULT, ''),
3141                  'value' => new external_value(PARAM_RAW, 'The value to match', VALUE_DEFAULT, '')
3142              )
3143          );
3144      }
3145  
3146  
3147      /**
3148       * Get courses matching a specific field (id/s, shortname, idnumber, category)
3149       *
3150       * @param  string $field field name to search, or empty for all courses
3151       * @param  string $value value to search
3152       * @return array list of courses and warnings
3153       * @throws  invalid_parameter_exception
3154       * @since Moodle 3.2
3155       */
3156      public static function get_courses_by_field($field = '', $value = '') {
3157          global $DB, $CFG;
3158          require_once($CFG->dirroot . '/course/lib.php');
3159          require_once($CFG->libdir . '/filterlib.php');
3160  
3161          $params = self::validate_parameters(self::get_courses_by_field_parameters(),
3162              array(
3163                  'field' => $field,
3164                  'value' => $value,
3165              )
3166          );
3167          $warnings = array();
3168  
3169          if (empty($params['field'])) {
3170              $courses = $DB->get_records('course', null, 'id ASC');
3171          } else {
3172              switch ($params['field']) {
3173                  case 'id':
3174                  case 'category':
3175                      $value = clean_param($params['value'], PARAM_INT);
3176                      break;
3177                  case 'ids':
3178                      $value = clean_param($params['value'], PARAM_SEQUENCE);
3179                      break;
3180                  case 'shortname':
3181                      $value = clean_param($params['value'], PARAM_TEXT);
3182                      break;
3183                  case 'idnumber':
3184                      $value = clean_param($params['value'], PARAM_RAW);
3185                      break;
3186                  default:
3187                      throw new invalid_parameter_exception('Invalid field name');
3188              }
3189  
3190              if ($params['field'] === 'ids') {
3191                  // Preload categories to avoid loading one at a time.
3192                  $courseids = explode(',', $value);
3193                  list ($listsql, $listparams) = $DB->get_in_or_equal($courseids);
3194                  $categoryids = $DB->get_fieldset_sql("
3195                          SELECT DISTINCT cc.id
3196                            FROM {course} c
3197                            JOIN {course_categories} cc ON cc.id = c.category
3198                           WHERE c.id $listsql", $listparams);
3199                  core_course_category::get_many($categoryids);
3200  
3201                  // Load and validate all courses. This is called because it loads the courses
3202                  // more efficiently.
3203                  list ($courses, $warnings) = external_util::validate_courses($courseids, [],
3204                          false, true);
3205              } else {
3206                  $courses = $DB->get_records('course', array($params['field'] => $value), 'id ASC');
3207              }
3208          }
3209  
3210          $coursesdata = array();
3211          foreach ($courses as $course) {
3212              $context = context_course::instance($course->id);
3213              $canupdatecourse = has_capability('moodle/course:update', $context);
3214              $canviewhiddencourses = has_capability('moodle/course:viewhiddencourses', $context);
3215  
3216              // Check if the course is visible in the site for the user.
3217              if (!$course->visible and !$canviewhiddencourses and !$canupdatecourse) {
3218                  continue;
3219              }
3220              // Get the public course information, even if we are not enrolled.
3221              $courseinlist = new core_course_list_element($course);
3222  
3223              // Now, check if we have access to the course, unless it was already checked.
3224              try {
3225                  if (empty($course->contextvalidated)) {
3226                      self::validate_context($context);
3227                  }
3228              } catch (Exception $e) {
3229                  // User can not access the course, check if they can see the public information about the course and return it.
3230                  if (core_course_category::can_view_course_info($course)) {
3231                      $coursesdata[$course->id] = self::get_course_public_information($courseinlist, $context);
3232                  }
3233                  continue;
3234              }
3235              $coursesdata[$course->id] = self::get_course_public_information($courseinlist, $context);
3236              // Return information for any user that can access the course.
3237              $coursefields = array('format', 'showgrades', 'newsitems', 'startdate', 'enddate', 'maxbytes', 'showreports', 'visible',
3238                  'groupmode', 'groupmodeforce', 'defaultgroupingid', 'enablecompletion', 'completionnotify', 'lang', 'theme',
3239                  'marker');
3240  
3241              // Course filters.
3242              $coursesdata[$course->id]['filters'] = filter_get_available_in_context($context);
3243  
3244              // Information for managers only.
3245              if ($canupdatecourse) {
3246                  $managerfields = array('idnumber', 'legacyfiles', 'calendartype', 'timecreated', 'timemodified', 'requested',
3247                      'cacherev');
3248                  $coursefields = array_merge($coursefields, $managerfields);
3249              }
3250  
3251              // Populate fields.
3252              foreach ($coursefields as $field) {
3253                  $coursesdata[$course->id][$field] = $course->{$field};
3254              }
3255  
3256              // Clean lang and auth fields for external functions (it may content uninstalled themes or language packs).
3257              if (isset($coursesdata[$course->id]['theme'])) {
3258                  $coursesdata[$course->id]['theme'] = clean_param($coursesdata[$course->id]['theme'], PARAM_THEME);
3259              }
3260              if (isset($coursesdata[$course->id]['lang'])) {
3261                  $coursesdata[$course->id]['lang'] = clean_param($coursesdata[$course->id]['lang'], PARAM_LANG);
3262              }
3263  
3264              $courseformatoptions = course_get_format($course)->get_config_for_external();
3265              foreach ($courseformatoptions as $key => $value) {
3266                  $coursesdata[$course->id]['courseformatoptions'][] = array(
3267                      'name' => $key,
3268                      'value' => $value
3269                  );
3270              }
3271          }
3272  
3273          return array(
3274              'courses' => $coursesdata,
3275              'warnings' => $warnings
3276          );
3277      }
3278  
3279      /**
3280       * Returns description of method result value
3281       *
3282       * @return external_description
3283       * @since Moodle 3.2
3284       */
3285      public static function get_courses_by_field_returns() {
3286          // Course structure, including not only public viewable fields.
3287          return new external_single_structure(
3288              array(
3289                  'courses' => new external_multiple_structure(self::get_course_structure(false), 'Course'),
3290                  'warnings' => new external_warnings()
3291              )
3292          );
3293      }
3294  
3295      /**
3296       * Returns description of method parameters
3297       *
3298       * @return external_function_parameters
3299       * @since Moodle 3.2
3300       */
3301      public static function check_updates_parameters() {
3302          return new external_function_parameters(
3303              array(
3304                  'courseid' => new external_value(PARAM_INT, 'Course id to check'),
3305                  'tocheck' => new external_multiple_structure(
3306                      new external_single_structure(
3307                          array(
3308                              'contextlevel' => new external_value(PARAM_ALPHA, 'The context level for the file location.
3309                                                                                  Only module supported right now.'),
3310                              'id' => new external_value(PARAM_INT, 'Context instance id'),
3311                              'since' => new external_value(PARAM_INT, 'Check updates since this time stamp'),
3312                          )
3313                      ),
3314                      'Instances to check'
3315                  ),
3316                  'filter' => new external_multiple_structure(
3317                      new external_value(PARAM_ALPHANUM, 'Area name: configuration, fileareas, completion, ratings, comments,
3318                                                          gradeitems, outcomes'),
3319                      'Check only for updates in these areas', VALUE_DEFAULT, array()
3320                  )
3321              )
3322          );
3323      }
3324  
3325      /**
3326       * Check if there is updates affecting the user for the given course and contexts.
3327       * Right now only modules are supported.
3328       * This WS calls mod_check_updates_since for each module to check if there is any update the user should we aware of.
3329       *
3330       * @param int $courseid the list of modules to check
3331       * @param array $tocheck the list of modules to check
3332       * @param array $filter check only for updates in these areas
3333       * @return array list of updates and warnings
3334       * @throws moodle_exception
3335       * @since Moodle 3.2
3336       */
3337      public static function check_updates($courseid, $tocheck, $filter = array()) {
3338          global $CFG, $DB;
3339          require_once($CFG->dirroot . "/course/lib.php");
3340  
3341          $params = self::validate_parameters(
3342              self::check_updates_parameters(),
3343              array(
3344                  'courseid' => $courseid,
3345                  'tocheck' => $tocheck,
3346                  'filter' => $filter,
3347              )
3348          );
3349  
3350          $course = get_course($params['courseid']);
3351          $context = context_course::instance($course->id);
3352          self::validate_context($context);
3353  
3354          list($instances, $warnings) = course_check_updates($course, $params['tocheck'], $filter);
3355  
3356          $instancesformatted = array();
3357          foreach ($instances as $instance) {
3358              $updates = array();
3359              foreach ($instance['updates'] as $name => $data) {
3360                  if (empty($data->updated)) {
3361                      continue;
3362                  }
3363                  $updatedata = array(
3364                      'name' => $name,
3365                  );
3366                  if (!empty($data->timeupdated)) {
3367                      $updatedata['timeupdated'] = $data->timeupdated;
3368                  }
3369                  if (!empty($data->itemids)) {
3370                      $updatedata['itemids'] = $data->itemids;
3371                  }
3372                  $updates[] = $updatedata;
3373              }
3374              if (!empty($updates)) {
3375                  $instancesformatted[] = array(
3376                      'contextlevel' => $instance['contextlevel'],
3377                      'id' => $instance['id'],
3378                      'updates' => $updates
3379                  );
3380              }
3381          }
3382  
3383          return array(
3384              'instances' => $instancesformatted,
3385              'warnings' => $warnings
3386          );
3387      }
3388  
3389      /**
3390       * Returns description of method result value
3391       *
3392       * @return external_description
3393       * @since Moodle 3.2
3394       */
3395      public static function check_updates_returns() {
3396          return new external_single_structure(
3397              array(
3398                  'instances' => new external_multiple_structure(
3399                      new external_single_structure(
3400                          array(
3401                              'contextlevel' => new external_value(PARAM_ALPHA, 'The context level'),
3402                              'id' => new external_value(PARAM_INT, 'Instance id'),
3403                              'updates' => new external_multiple_structure(
3404                                  new external_single_structure(
3405                                      array(
3406                                          'name' => new external_value(PARAM_ALPHANUMEXT, 'Name of the area updated.'),
3407                                          'timeupdated' => new external_value(PARAM_INT, 'Last time was updated', VALUE_OPTIONAL),
3408                                          'itemids' => new external_multiple_structure(
3409                                              new external_value(PARAM_INT, 'Instance id'),
3410                                              'The ids of the items updated',
3411                                              VALUE_OPTIONAL
3412                                          )
3413                                      )
3414                                  )
3415                              )
3416                          )
3417                      )
3418                  ),
3419                  'warnings' => new external_warnings()
3420              )
3421          );
3422      }
3423  
3424      /**
3425       * Returns description of method parameters
3426       *
3427       * @return external_function_parameters
3428       * @since Moodle 3.3
3429       */
3430      public static function get_updates_since_parameters() {
3431          return new external_function_parameters(
3432              array(
3433                  'courseid' => new external_value(PARAM_INT, 'Course id to check'),
3434                  'since' => new external_value(PARAM_INT, 'Check updates since this time stamp'),
3435                  'filter' => new external_multiple_structure(
3436                      new external_value(PARAM_ALPHANUM, 'Area name: configuration, fileareas, completion, ratings, comments,
3437                                                          gradeitems, outcomes'),
3438                      'Check only for updates in these areas', VALUE_DEFAULT, array()
3439                  )
3440              )
3441          );
3442      }
3443  
3444      /**
3445       * Check if there are updates affecting the user for the given course since the given time stamp.
3446       *
3447       * This function is a wrapper of self::check_updates for retrieving all the updates since a given time for all the activities.
3448       *
3449       * @param int $courseid the list of modules to check
3450       * @param int $since check updates since this time stamp
3451       * @param array $filter check only for updates in these areas
3452       * @return array list of updates and warnings
3453       * @throws moodle_exception
3454       * @since Moodle 3.3
3455       */
3456      public static function get_updates_since($courseid, $since, $filter = array()) {
3457          global $CFG, $DB;
3458  
3459          $params = self::validate_parameters(
3460              self::get_updates_since_parameters(),
3461              array(
3462                  'courseid' => $courseid,
3463                  'since' => $since,
3464                  'filter' => $filter,
3465              )
3466          );
3467  
3468          $course = get_course($params['courseid']);
3469          $modinfo = get_fast_modinfo($course);
3470          $tocheck = array();
3471  
3472          // Retrieve all the visible course modules for the current user.
3473          $cms = $modinfo->get_cms();
3474          foreach ($cms as $cm) {
3475              if (!$cm->uservisible) {
3476                  continue;
3477              }
3478              $tocheck[] = array(
3479                  'id' => $cm->id,
3480                  'contextlevel' => 'module',
3481                  'since' => $params['since'],
3482              );
3483          }
3484  
3485          return self::check_updates($course->id, $tocheck, $params['filter']);
3486      }
3487  
3488      /**
3489       * Returns description of method result value
3490       *
3491       * @return external_description
3492       * @since Moodle 3.3
3493       */
3494      public static function get_updates_since_returns() {
3495          return self::check_updates_returns();
3496      }
3497  
3498      /**
3499       * Parameters for function edit_module()
3500       *
3501       * @since Moodle 3.3
3502       * @return external_function_parameters
3503       */
3504      public static function edit_module_parameters() {
3505          return new external_function_parameters(
3506              array(
3507                  'action' => new external_value(PARAM_ALPHA,
3508                      'action: hide, show, stealth, duplicate, delete, moveleft, moveright, group...', VALUE_REQUIRED),
3509                  'id' => new external_value(PARAM_INT, 'course module id', VALUE_REQUIRED),
3510                  'sectionreturn' => new external_value(PARAM_INT, 'section to return to', VALUE_DEFAULT, null),
3511              ));
3512      }
3513  
3514      /**
3515       * Performs one of the edit module actions and return new html for AJAX
3516       *
3517       * Returns html to replace the current module html with, for example:
3518       * - empty string for "delete" action,
3519       * - two modules html for "duplicate" action
3520       * - updated module html for everything else
3521       *
3522       * Throws exception if operation is not permitted/possible
3523       *
3524       * @since Moodle 3.3
3525       * @param string $action
3526       * @param int $id
3527       * @param null|int $sectionreturn
3528       * @return string
3529       */
3530      public static function edit_module($action, $id, $sectionreturn = null) {
3531          global $PAGE, $DB;
3532          // Validate and normalize parameters.
3533          $params = self::validate_parameters(self::edit_module_parameters(),
3534              array('action' => $action, 'id' => $id, 'sectionreturn' => $sectionreturn));
3535          $action = $params['action'];
3536          $id = $params['id'];
3537          $sectionreturn = $params['sectionreturn'];
3538  
3539          // Set of permissions an editing user may have.
3540          $contextarray = [
3541                  'moodle/course:update',
3542                  'moodle/course:manageactivities',
3543                  'moodle/course:activityvisibility',
3544                  'moodle/course:sectionvisibility',
3545                  'moodle/course:movesections',
3546                  'moodle/course:setcurrentsection',
3547          ];
3548          $PAGE->set_other_editing_capability($contextarray);
3549  
3550          list($course, $cm) = get_course_and_cm_from_cmid($id);
3551          $modcontext = context_module::instance($cm->id);
3552          $coursecontext = context_course::instance($course->id);
3553          self::validate_context($modcontext);
3554          $format = course_get_format($course);
3555          if ($sectionreturn) {
3556              $format->set_section_number($sectionreturn);
3557          }
3558          $renderer = $format->get_renderer($PAGE);
3559  
3560          switch($action) {
3561              case 'hide':
3562              case 'show':
3563              case 'stealth':
3564                  require_capability('moodle/course:activityvisibility', $modcontext);
3565                  $visible = ($action === 'hide') ? 0 : 1;
3566                  $visibleoncoursepage = ($action === 'stealth') ? 0 : 1;
3567                  set_coursemodule_visible($id, $visible, $visibleoncoursepage);
3568                  \core\event\course_module_updated::create_from_cm($cm, $modcontext)->trigger();
3569                  break;
3570              case 'duplicate':
3571                  require_capability('moodle/course:manageactivities', $coursecontext);
3572                  require_capability('moodle/backup:backuptargetimport', $coursecontext);
3573                  require_capability('moodle/restore:restoretargetimport', $coursecontext);
3574                  if (!course_allowed_module($course, $cm->modname)) {
3575                      throw new moodle_exception('No permission to create that activity');
3576                  }
3577                  if ($newcm = duplicate_module($course, $cm)) {
3578  
3579                      $modinfo = $format->get_modinfo();
3580                      $section = $modinfo->get_section_info($newcm->sectionnum);
3581                      $cm = $modinfo->get_cm($id);
3582  
3583                      // Get both original and new element html.
3584                      $result = $renderer->course_section_updated_cm_item($format, $section, $cm);
3585                      $result .= $renderer->course_section_updated_cm_item($format, $section, $newcm);
3586                      return $result;
3587                  }
3588                  break;
3589              case 'groupsseparate':
3590              case 'groupsvisible':
3591              case 'groupsnone':
3592                  require_capability('moodle/course:manageactivities', $modcontext);
3593                  if ($action === 'groupsseparate') {
3594                      $newgroupmode = SEPARATEGROUPS;
3595                  } else if ($action === 'groupsvisible') {
3596                      $newgroupmode = VISIBLEGROUPS;
3597                  } else {
3598                      $newgroupmode = NOGROUPS;
3599                  }
3600                  if (set_coursemodule_groupmode($cm->id, $newgroupmode)) {
3601                      \core\event\course_module_updated::create_from_cm($cm, $modcontext)->trigger();
3602                  }
3603                  break;
3604              case 'moveleft':
3605              case 'moveright':
3606                  require_capability('moodle/course:manageactivities', $modcontext);
3607                  $indent = $cm->indent + (($action === 'moveright') ? 1 : -1);
3608                  if ($cm->indent >= 0) {
3609                      $DB->update_record('course_modules', array('id' => $cm->id, 'indent' => $indent));
3610                      rebuild_course_cache($cm->course);
3611                  }
3612                  break;
3613              case 'delete':
3614                  require_capability('moodle/course:manageactivities', $modcontext);
3615                  course_delete_module($cm->id, true);
3616                  return '';
3617              default:
3618                  throw new coding_exception('Unrecognised action');
3619          }
3620  
3621          $modinfo = $format->get_modinfo();
3622          $section = $modinfo->get_section_info($cm->sectionnum);
3623          $cm = $modinfo->get_cm($id);
3624          return $renderer->course_section_updated_cm_item($format, $section, $cm);
3625      }
3626  
3627      /**
3628       * Return structure for edit_module()
3629       *
3630       * @since Moodle 3.3
3631       * @return external_description
3632       */
3633      public static function edit_module_returns() {
3634          return new external_value(PARAM_RAW, 'html to replace the current module with');
3635      }
3636  
3637      /**
3638       * Parameters for function get_module()
3639       *
3640       * @since Moodle 3.3
3641       * @return external_function_parameters
3642       */
3643      public static function get_module_parameters() {
3644          return new external_function_parameters(
3645              array(
3646                  'id' => new external_value(PARAM_INT, 'course module id', VALUE_REQUIRED),
3647                  'sectionreturn' => new external_value(PARAM_INT, 'section to return to', VALUE_DEFAULT, null),
3648              ));
3649      }
3650  
3651      /**
3652       * Returns html for displaying one activity module on course page
3653       *
3654       * @since Moodle 3.3
3655       * @param int $id
3656       * @param null|int $sectionreturn
3657       * @return string
3658       */
3659      public static function get_module($id, $sectionreturn = null) {
3660          global $PAGE;
3661          // Validate and normalize parameters.
3662          $params = self::validate_parameters(self::get_module_parameters(),
3663              array('id' => $id, 'sectionreturn' => $sectionreturn));
3664          $id = $params['id'];
3665          $sectionreturn = $params['sectionreturn'];
3666  
3667          // Set of permissions an editing user may have.
3668          $contextarray = [
3669              'moodle/course:update',
3670              'moodle/course:manageactivities',
3671              'moodle/course:activityvisibility',
3672              'moodle/course:sectionvisibility',
3673              'moodle/course:movesections',
3674              'moodle/course:setcurrentsection',
3675          ];
3676          $PAGE->set_other_editing_capability($contextarray);
3677  
3678          // Validate access to the course (note, this is html for the course view page, we don't validate access to the module).
3679          list($course, $cm) = get_course_and_cm_from_cmid($id);
3680          self::validate_context(context_course::instance($course->id));
3681  
3682          $format = course_get_format($course);
3683          if ($sectionreturn) {
3684              $format->set_section_number($sectionreturn);
3685          }
3686          $renderer = $format->get_renderer($PAGE);
3687  
3688          $modinfo = $format->get_modinfo();
3689          $section = $modinfo->get_section_info($cm->sectionnum);
3690          return $renderer->course_section_updated_cm_item($format, $section, $cm);
3691      }
3692  
3693      /**
3694       * Return structure for get_module()
3695       *
3696       * @since Moodle 3.3
3697       * @return external_description
3698       */
3699      public static function get_module_returns() {
3700          return new external_value(PARAM_RAW, 'html to replace the current module with');
3701      }
3702  
3703      /**
3704       * Parameters for function edit_section()
3705       *
3706       * @since Moodle 3.3
3707       * @return external_function_parameters
3708       */
3709      public static function edit_section_parameters() {
3710          return new external_function_parameters(
3711              array(
3712                  'action' => new external_value(PARAM_ALPHA, 'action: hide, show, stealth, setmarker, removemarker', VALUE_REQUIRED),
3713                  'id' => new external_value(PARAM_INT, 'course section id', VALUE_REQUIRED),
3714                  'sectionreturn' => new external_value(PARAM_INT, 'section to return to', VALUE_DEFAULT, null),
3715              ));
3716      }
3717  
3718      /**
3719       * Performs one of the edit section actions
3720       *
3721       * @since Moodle 3.3
3722       * @param string $action
3723       * @param int $id section id
3724       * @param int $sectionreturn section to return to
3725       * @return string
3726       */
3727      public static function edit_section($action, $id, $sectionreturn) {
3728          global $DB;
3729          // Validate and normalize parameters.
3730          $params = self::validate_parameters(self::edit_section_parameters(),
3731              array('action' => $action, 'id' => $id, 'sectionreturn' => $sectionreturn));
3732          $action = $params['action'];
3733          $id = $params['id'];
3734          $sr = $params['sectionreturn'];
3735  
3736          $section = $DB->get_record('course_sections', array('id' => $id), '*', MUST_EXIST);
3737          $coursecontext = context_course::instance($section->course);
3738          self::validate_context($coursecontext);
3739  
3740          $rv = course_get_format($section->course)->section_action($section, $action, $sectionreturn);
3741          if ($rv) {
3742              return json_encode($rv);
3743          } else {
3744              return null;
3745          }
3746      }
3747  
3748      /**
3749       * Return structure for edit_section()
3750       *
3751       * @since Moodle 3.3
3752       * @return external_description
3753       */
3754      public static function edit_section_returns() {
3755          return new external_value(PARAM_RAW, 'Additional data for javascript (JSON-encoded string)');
3756      }
3757  
3758      /**
3759       * Returns description of method parameters
3760       *
3761       * @return external_function_parameters
3762       */
3763      public static function get_enrolled_courses_by_timeline_classification_parameters() {
3764          return new external_function_parameters(
3765              array(
3766                  'classification' => new external_value(PARAM_ALPHA, 'future, inprogress, or past'),
3767                  'limit' => new external_value(PARAM_INT, 'Result set limit', VALUE_DEFAULT, 0),
3768                  'offset' => new external_value(PARAM_INT, 'Result set offset', VALUE_DEFAULT, 0),
3769                  'sort' => new external_value(PARAM_TEXT, 'Sort string', VALUE_DEFAULT, null),
3770                  'customfieldname' => new external_value(PARAM_ALPHANUMEXT, 'Used when classification = customfield',
3771                      VALUE_DEFAULT, null),
3772                  'customfieldvalue' => new external_value(PARAM_RAW, 'Used when classification = customfield',
3773                      VALUE_DEFAULT, null),
3774                  'searchvalue' => new external_value(PARAM_RAW, 'The value a user wishes to search against',
3775                      VALUE_DEFAULT, null),
3776              )
3777          );
3778      }
3779  
3780      /**
3781       * Get courses matching the given timeline classification.
3782       *
3783       * NOTE: The offset applies to the unfiltered full set of courses before the classification
3784       * filtering is done.
3785       * E.g.
3786       * If the user is enrolled in 5 courses:
3787       * c1, c2, c3, c4, and c5
3788       * And c4 and c5 are 'future' courses
3789       *
3790       * If a request comes in for future courses with an offset of 1 it will mean that
3791       * c1 is skipped (because the offset applies *before* the classification filtering)
3792       * and c4 and c5 will be return.
3793       *
3794       * @param  string $classification past, inprogress, or future
3795       * @param  int $limit Result set limit
3796       * @param  int $offset Offset the full course set before timeline classification is applied
3797       * @param  string $sort SQL sort string for results
3798       * @param  string $customfieldname
3799       * @param  string $customfieldvalue
3800       * @param  string $searchvalue
3801       * @return array list of courses and warnings
3802       * @throws  invalid_parameter_exception
3803       */
3804      public static function get_enrolled_courses_by_timeline_classification(
3805          string $classification,
3806          int $limit = 0,
3807          int $offset = 0,
3808          string $sort = null,
3809          string $customfieldname = null,
3810          string $customfieldvalue = null,
3811          string $searchvalue = null
3812      ) {
3813          global $CFG, $PAGE, $USER;
3814          require_once($CFG->dirroot . '/course/lib.php');
3815  
3816          $params = self::validate_parameters(self::get_enrolled_courses_by_timeline_classification_parameters(),
3817              array(
3818                  'classification' => $classification,
3819                  'limit' => $limit,
3820                  'offset' => $offset,
3821                  'sort' => $sort,
3822                  'customfieldvalue' => $customfieldvalue,
3823                  'searchvalue' => $searchvalue,
3824              )
3825          );
3826  
3827          $classification = $params['classification'];
3828          $limit = $params['limit'];
3829          $offset = $params['offset'];
3830          $sort = $params['sort'];
3831          $customfieldvalue = $params['customfieldvalue'];
3832          $searchvalue = clean_param($params['searchvalue'], PARAM_TEXT);
3833  
3834          switch($classification) {
3835              case COURSE_TIMELINE_ALLINCLUDINGHIDDEN:
3836                  break;
3837              case COURSE_TIMELINE_ALL:
3838                  break;
3839              case COURSE_TIMELINE_PAST:
3840                  break;
3841              case COURSE_TIMELINE_INPROGRESS:
3842                  break;
3843              case COURSE_TIMELINE_FUTURE:
3844                  break;
3845              case COURSE_FAVOURITES:
3846                  break;
3847              case COURSE_TIMELINE_HIDDEN:
3848                  break;
3849              case COURSE_TIMELINE_SEARCH:
3850                  break;
3851              case COURSE_CUSTOMFIELD:
3852                  break;
3853              default:
3854                  throw new invalid_parameter_exception('Invalid classification');
3855          }
3856  
3857          self::validate_context(context_user::instance($USER->id));
3858  
3859          $requiredproperties = course_summary_exporter::define_properties();
3860          $fields = join(',', array_keys($requiredproperties));
3861          $hiddencourses = get_hidden_courses_on_timeline();
3862          $courses = [];
3863  
3864          // If the timeline requires really all courses, get really all courses.
3865          if ($classification == COURSE_TIMELINE_ALLINCLUDINGHIDDEN) {
3866              $courses = course_get_enrolled_courses_for_logged_in_user(0, $offset, $sort, $fields, COURSE_DB_QUERY_LIMIT);
3867  
3868              // Otherwise if the timeline requires the hidden courses then restrict the result to only $hiddencourses.
3869          } else if ($classification == COURSE_TIMELINE_HIDDEN) {
3870              $courses = course_get_enrolled_courses_for_logged_in_user(0, $offset, $sort, $fields,
3871                  COURSE_DB_QUERY_LIMIT, $hiddencourses);
3872  
3873              // Otherwise get the requested courses and exclude the hidden courses.
3874          } else if ($classification == COURSE_TIMELINE_SEARCH) {
3875              // Prepare the search API options.
3876              $searchcriteria['search'] = $searchvalue;
3877              $options = ['idonly' => true];
3878              $courses = course_get_enrolled_courses_for_logged_in_user_from_search(
3879                  0,
3880                  $offset,
3881                  $sort,
3882                  $fields,
3883                  COURSE_DB_QUERY_LIMIT,
3884                  $searchcriteria,
3885                  $options
3886              );
3887          } else {
3888              $courses = course_get_enrolled_courses_for_logged_in_user(0, $offset, $sort, $fields,
3889                  COURSE_DB_QUERY_LIMIT, [], $hiddencourses);
3890          }
3891  
3892          $favouritecourseids = [];
3893          $ufservice = \core_favourites\service_factory::get_service_for_user_context(\context_user::instance($USER->id));
3894          $favourites = $ufservice->find_favourites_by_type('core_course', 'courses');
3895  
3896          if ($favourites) {
3897              $favouritecourseids = array_map(
3898                  function($favourite) {
3899                      return $favourite->itemid;
3900                  }, $favourites);
3901          }
3902  
3903          if ($classification == COURSE_FAVOURITES) {
3904              list($filteredcourses, $processedcount) = course_filter_courses_by_favourites(
3905                  $courses,
3906                  $favouritecourseids,
3907                  $limit
3908              );
3909          } else if ($classification == COURSE_CUSTOMFIELD) {
3910              list($filteredcourses, $processedcount) = course_filter_courses_by_customfield(
3911                  $courses,
3912                  $customfieldname,
3913                  $customfieldvalue,
3914                  $limit
3915              );
3916          } else {
3917              list($filteredcourses, $processedcount) = course_filter_courses_by_timeline_classification(
3918                  $courses,
3919                  $classification,
3920                  $limit
3921              );
3922          }
3923  
3924          $renderer = $PAGE->get_renderer('core');
3925          $formattedcourses = array_map(function($course) use ($renderer, $favouritecourseids) {
3926              if ($course == null) {
3927                  return;
3928              }
3929              context_helper::preload_from_record($course);
3930              $context = context_course::instance($course->id);
3931              $isfavourite = false;
3932              if (in_array($course->id, $favouritecourseids)) {
3933                  $isfavourite = true;
3934              }
3935              $exporter = new course_summary_exporter($course, ['context' => $context, 'isfavourite' => $isfavourite]);
3936              return $exporter->export($renderer);
3937          }, $filteredcourses);
3938  
3939          $formattedcourses = array_filter($formattedcourses, function($course) {
3940              if ($course != null) {
3941                  return $course;
3942              }
3943          });
3944  
3945          return [
3946              'courses' => $formattedcourses,
3947              'nextoffset' => $offset + $processedcount
3948          ];
3949      }
3950  
3951      /**
3952       * Returns description of method result value
3953       *
3954       * @return external_description
3955       */
3956      public static function get_enrolled_courses_by_timeline_classification_returns() {
3957          return new external_single_structure(
3958              array(
3959                  'courses' => new external_multiple_structure(course_summary_exporter::get_read_structure(), 'Course'),
3960                  'nextoffset' => new external_value(PARAM_INT, 'Offset for the next request')
3961              )
3962          );
3963      }
3964  
3965      /**
3966       * Returns description of method parameters
3967       *
3968       * @return external_function_parameters
3969       */
3970      public static function set_favourite_courses_parameters() {
3971          return new external_function_parameters(
3972              array(
3973                  'courses' => new external_multiple_structure(
3974                      new external_single_structure(
3975                          array(
3976                              'id' => new external_value(PARAM_INT, 'course ID'),
3977                              'favourite' => new external_value(PARAM_BOOL, 'favourite status')
3978                          )
3979                      )
3980                  )
3981              )
3982          );
3983      }
3984  
3985      /**
3986       * Set the course favourite status for an array of courses.
3987       *
3988       * @param  array $courses List with course id's and favourite status.
3989       * @return array Array with an array of favourite courses.
3990       */
3991      public static function set_favourite_courses(
3992          array $courses
3993      ) {
3994          global $USER;
3995  
3996          $params = self::validate_parameters(self::set_favourite_courses_parameters(),
3997              array(
3998                  'courses' => $courses
3999              )
4000          );
4001  
4002          $warnings = [];
4003  
4004          $ufservice = \core_favourites\service_factory::get_service_for_user_context(\context_user::instance($USER->id));
4005  
4006          foreach ($params['courses'] as $course) {
4007  
4008              $warning = [];
4009  
4010              $favouriteexists = $ufservice->favourite_exists('core_course', 'courses', $course['id'],
4011                      \context_course::instance($course['id']));
4012  
4013              if ($course['favourite']) {
4014                  if (!$favouriteexists) {
4015                      try {
4016                          $ufservice->create_favourite('core_course', 'courses', $course['id'],
4017                                  \context_course::instance($course['id']));
4018                      } catch (Exception $e) {
4019                          $warning['courseid'] = $course['id'];
4020                          if ($e instanceof moodle_exception) {
4021                              $warning['warningcode'] = $e->errorcode;
4022                          } else {
4023                              $warning['warningcode'] = $e->getCode();
4024                          }
4025                          $warning['message'] = $e->getMessage();
4026                          $warnings[] = $warning;
4027                          $warnings[] = $warning;
4028                      }
4029                  } else {
4030                      $warning['courseid'] = $course['id'];
4031                      $warning['warningcode'] = 'coursealreadyfavourited';
4032                      $warning['message'] = 'Course already favourited';
4033                      $warnings[] = $warning;
4034                  }
4035              } else {
4036                  if ($favouriteexists) {
4037                      try {
4038                          $ufservice->delete_favourite('core_course', 'courses', $course['id'],
4039                                  \context_course::instance($course['id']));
4040                      } catch (Exception $e) {
4041                          $warning['courseid'] = $course['id'];
4042                          if ($e instanceof moodle_exception) {
4043                              $warning['warningcode'] = $e->errorcode;
4044                          } else {
4045                              $warning['warningcode'] = $e->getCode();
4046                          }
4047                          $warning['message'] = $e->getMessage();
4048                          $warnings[] = $warning;
4049                          $warnings[] = $warning;
4050                      }
4051                  } else {
4052                      $warning['courseid'] = $course['id'];
4053                      $warning['warningcode'] = 'cannotdeletefavourite';
4054                      $warning['message'] = 'Could not delete favourite status for course';
4055                      $warnings[] = $warning;
4056                  }
4057              }
4058          }
4059  
4060          return [
4061              'warnings' => $warnings
4062          ];
4063      }
4064  
4065      /**
4066       * Returns description of method result value
4067       *
4068       * @return external_description
4069       */
4070      public static function set_favourite_courses_returns() {
4071          return new external_single_structure(
4072              array(
4073                  'warnings' => new external_warnings()
4074              )
4075          );
4076      }
4077  
4078      /**
4079       * Returns description of method parameters
4080       *
4081       * @return external_function_parameters
4082       * @since Moodle 3.6
4083       */
4084      public static function get_recent_courses_parameters() {
4085          return new external_function_parameters(
4086              array(
4087                  'userid' => new external_value(PARAM_INT, 'id of the user, default to current user', VALUE_DEFAULT, 0),
4088                  'limit' => new external_value(PARAM_INT, 'result set limit', VALUE_DEFAULT, 0),
4089                  'offset' => new external_value(PARAM_INT, 'Result set offset', VALUE_DEFAULT, 0),
4090                  'sort' => new external_value(PARAM_TEXT, 'Sort string', VALUE_DEFAULT, null)
4091              )
4092          );
4093      }
4094  
4095      /**
4096       * Get last accessed courses adding additional course information like images.
4097       *
4098       * @param int $userid User id from which the courses will be obtained
4099       * @param int $limit Restrict result set to this amount
4100       * @param int $offset Skip this number of records from the start of the result set
4101       * @param string|null $sort SQL string for sorting
4102       * @return array List of courses
4103       * @throws  invalid_parameter_exception
4104       */
4105      public static function get_recent_courses(int $userid = 0, int $limit = 0, int $offset = 0, string $sort = null) {
4106          global $USER, $PAGE;
4107  
4108          if (empty($userid)) {
4109              $userid = $USER->id;
4110          }
4111  
4112          $params = self::validate_parameters(self::get_recent_courses_parameters(),
4113              array(
4114                  'userid' => $userid,
4115                  'limit' => $limit,
4116                  'offset' => $offset,
4117                  'sort' => $sort
4118              )
4119          );
4120  
4121          $userid = $params['userid'];
4122          $limit = $params['limit'];
4123          $offset = $params['offset'];
4124          $sort = $params['sort'];
4125  
4126          $usercontext = context_user::instance($userid);
4127  
4128          self::validate_context($usercontext);
4129  
4130          if ($userid != $USER->id and !has_capability('moodle/user:viewdetails', $usercontext)) {
4131              return array();
4132          }
4133  
4134          $courses = course_get_recent_courses($userid, $limit, $offset, $sort);
4135  
4136          $renderer = $PAGE->get_renderer('core');
4137  
4138          $recentcourses = array_map(function($course) use ($renderer) {
4139              context_helper::preload_from_record($course);
4140              $context = context_course::instance($course->id);
4141              $isfavourite = !empty($course->component);
4142              $exporter = new course_summary_exporter($course, ['context' => $context, 'isfavourite' => $isfavourite]);
4143              return $exporter->export($renderer);
4144          }, $courses);
4145  
4146          return $recentcourses;
4147      }
4148  
4149      /**
4150       * Returns description of method result value
4151       *
4152       * @return external_description
4153       * @since Moodle 3.6
4154       */
4155      public static function get_recent_courses_returns() {
4156          return new external_multiple_structure(course_summary_exporter::get_read_structure(), 'Courses');
4157      }
4158  
4159      /**
4160       * Returns description of method parameters
4161       *
4162       * @return external_function_parameters
4163       */
4164      public static function get_enrolled_users_by_cmid_parameters() {
4165          return new external_function_parameters([
4166              'cmid' => new external_value(PARAM_INT, 'id of the course module', VALUE_REQUIRED),
4167              'groupid' => new external_value(PARAM_INT, 'id of the group', VALUE_DEFAULT, 0),
4168              'onlyactive' => new external_value(PARAM_BOOL, 'whether to return only active users or all.',
4169                  VALUE_DEFAULT, false),
4170          ]);
4171      }
4172  
4173      /**
4174       * Get all users in a course for a given cmid.
4175       *
4176       * @param int $cmid Course Module id from which the users will be obtained
4177       * @param int $groupid Group id from which the users will be obtained
4178       * @param bool $onlyactive Whether to return only the active enrolled users or all enrolled users in the course.
4179       * @return array List of users
4180       * @throws invalid_parameter_exception
4181       */
4182      public static function get_enrolled_users_by_cmid(int $cmid, int $groupid = 0, bool $onlyactive = false) {
4183      global $PAGE;
4184          $warnings = [];
4185  
4186          self::validate_parameters(self::get_enrolled_users_by_cmid_parameters(), [
4187                  'cmid' => $cmid,
4188                  'groupid' => $groupid,
4189                  'onlyactive' => $onlyactive,
4190          ]);
4191  
4192          list($course, $cm) = get_course_and_cm_from_cmid($cmid);
4193          $coursecontext = context_course::instance($course->id);
4194          self::validate_context($coursecontext);
4195  
4196          $enrolledusers = get_enrolled_users($coursecontext, '', $groupid, 'u.*', null, 0, 0, $onlyactive);
4197  
4198          $users = array_map(function ($user) use ($PAGE) {
4199              $user->fullname = fullname($user);
4200              $userpicture = new user_picture($user);
4201              $userpicture->size = 1;
4202              $user->profileimage = $userpicture->get_url($PAGE)->out(false);
4203              return $user;
4204          }, $enrolledusers);
4205          sort($users);
4206  
4207          return [
4208              'users' => $users,
4209              'warnings' => $warnings,
4210          ];
4211      }
4212  
4213      /**
4214       * Returns description of method result value
4215       *
4216       * @return external_description
4217       */
4218      public static function get_enrolled_users_by_cmid_returns() {
4219          return new external_single_structure([
4220              'users' => new external_multiple_structure(self::user_description()),
4221              'warnings' => new external_warnings(),
4222          ]);
4223      }
4224  
4225      /**
4226       * Create user return value description.
4227       *
4228       * @return external_description
4229       */
4230      public static function user_description() {
4231          $userfields = array(
4232              'id'    => new external_value(core_user::get_property_type('id'), 'ID of the user'),
4233              'profileimage' => new external_value(PARAM_URL, 'The location of the users larger image', VALUE_OPTIONAL),
4234              'fullname' => new external_value(PARAM_TEXT, 'The full name of the user', VALUE_OPTIONAL),
4235              'firstname'   => new external_value(
4236                      core_user::get_property_type('firstname'),
4237                          'The first name(s) of the user',
4238                          VALUE_OPTIONAL),
4239              'lastname'    => new external_value(
4240                      core_user::get_property_type('lastname'),
4241                          'The family name of the user',
4242                          VALUE_OPTIONAL),
4243          );
4244          return new external_single_structure($userfields);
4245      }
4246  
4247      /**
4248       * Returns description of method parameters.
4249       *
4250       * @return external_function_parameters
4251       */
4252      public static function add_content_item_to_user_favourites_parameters() {
4253          return new external_function_parameters([
4254              'componentname' => new external_value(PARAM_TEXT,
4255                  'frankenstyle name of the component to which the content item belongs', VALUE_REQUIRED),
4256              'contentitemid' => new external_value(PARAM_INT, 'id of the content item', VALUE_REQUIRED, '', NULL_NOT_ALLOWED)
4257          ]);
4258      }
4259  
4260      /**
4261       * Add a content item to a user's favourites.
4262       *
4263       * @param string $componentname the name of the component from which this content item originates.
4264       * @param int $contentitemid the id of the content item.
4265       * @return stdClass the exporter content item.
4266       */
4267      public static function add_content_item_to_user_favourites(string $componentname, int $contentitemid) {
4268          global $USER;
4269  
4270          [
4271              'componentname' => $componentname,
4272              'contentitemid' => $contentitemid,
4273          ] = self::validate_parameters(self::add_content_item_to_user_favourites_parameters(),
4274              [
4275                  'componentname' => $componentname,
4276                  'contentitemid' => $contentitemid,
4277              ]
4278          );
4279  
4280          self::validate_context(context_user::instance($USER->id));
4281  
4282          $contentitemservice = \core_course\local\factory\content_item_service_factory::get_content_item_service();
4283  
4284          return $contentitemservice->add_to_user_favourites($USER, $componentname, $contentitemid);
4285      }
4286  
4287      /**
4288       * Returns description of method result value.
4289       *
4290       * @return external_description
4291       */
4292      public static function add_content_item_to_user_favourites_returns() {
4293          return \core_course\local\exporters\course_content_item_exporter::get_read_structure();
4294      }
4295  
4296      /**
4297       * Returns description of method parameters.
4298       *
4299       * @return external_function_parameters
4300       */
4301      public static function remove_content_item_from_user_favourites_parameters() {
4302          return new external_function_parameters([
4303              'componentname' => new external_value(PARAM_TEXT,
4304                  'frankenstyle name of the component to which the content item belongs', VALUE_REQUIRED),
4305              'contentitemid' => new external_value(PARAM_INT, 'id of the content item', VALUE_REQUIRED, '', NULL_NOT_ALLOWED),
4306          ]);
4307      }
4308  
4309      /**
4310       * Remove a content item from a user's favourites.
4311       *
4312       * @param string $componentname the name of the component from which this content item originates.
4313       * @param int $contentitemid the id of the content item.
4314       * @return stdClass the exported content item.
4315       */
4316      public static function remove_content_item_from_user_favourites(string $componentname, int $contentitemid) {
4317          global $USER;
4318  
4319          [
4320              'componentname' => $componentname,
4321              'contentitemid' => $contentitemid,
4322          ] = self::validate_parameters(self::remove_content_item_from_user_favourites_parameters(),
4323              [
4324                  'componentname' => $componentname,
4325                  'contentitemid' => $contentitemid,
4326              ]
4327          );
4328  
4329          self::validate_context(context_user::instance($USER->id));
4330  
4331          $contentitemservice = \core_course\local\factory\content_item_service_factory::get_content_item_service();
4332  
4333          return $contentitemservice->remove_from_user_favourites($USER, $componentname, $contentitemid);
4334      }
4335  
4336      /**
4337       * Returns description of method result value.
4338       *
4339       * @return external_description
4340       */
4341      public static function remove_content_item_from_user_favourites_returns() {
4342          return \core_course\local\exporters\course_content_item_exporter::get_read_structure();
4343      }
4344  
4345      /**
4346       * Returns description of method result value
4347       *
4348       * @return external_description
4349       */
4350      public static function get_course_content_items_returns() {
4351          return new external_single_structure([
4352              'content_items' => new external_multiple_structure(
4353                  \core_course\local\exporters\course_content_item_exporter::get_read_structure()
4354              ),
4355          ]);
4356      }
4357  
4358      /**
4359       * Returns description of method parameters
4360       *
4361       * @return external_function_parameters
4362       */
4363      public static function get_course_content_items_parameters() {
4364          return new external_function_parameters([
4365              'courseid' => new external_value(PARAM_INT, 'ID of the course', VALUE_REQUIRED),
4366          ]);
4367      }
4368  
4369      /**
4370       * Given a course ID fetch all accessible modules for that course
4371       *
4372       * @param int $courseid The course we want to fetch the modules for
4373       * @return array Contains array of modules and their metadata
4374       */
4375      public static function get_course_content_items(int $courseid) {
4376          global $USER;
4377  
4378          [
4379              'courseid' => $courseid,
4380          ] = self::validate_parameters(self::get_course_content_items_parameters(), [
4381              'courseid' => $courseid,
4382          ]);
4383  
4384          $coursecontext = context_course::instance($courseid);
4385          self::validate_context($coursecontext);
4386          $course = get_course($courseid);
4387  
4388          $contentitemservice = \core_course\local\factory\content_item_service_factory::get_content_item_service();
4389  
4390          $contentitems = $contentitemservice->get_content_items_for_user_in_course($USER, $course);
4391          return ['content_items' => $contentitems];
4392      }
4393  
4394      /**
4395       * Returns description of method parameters.
4396       *
4397       * @return external_function_parameters
4398       */
4399      public static function toggle_activity_recommendation_parameters() {
4400          return new external_function_parameters([
4401              'area' => new external_value(PARAM_TEXT, 'The favourite area (itemtype)', VALUE_REQUIRED),
4402              'id' => new external_value(PARAM_INT, 'id of the activity or whatever', VALUE_REQUIRED),
4403          ]);
4404      }
4405  
4406      /**
4407       * Update the recommendation for an activity item.
4408       *
4409       * @param  string $area identifier for this activity.
4410       * @param  int $id Associated id. This is needed in conjunction with the area to find the recommendation.
4411       * @return array some warnings or something.
4412       */
4413      public static function toggle_activity_recommendation(string $area, int $id): array {
4414          ['area' => $area, 'id' => $id] = self::validate_parameters(self::toggle_activity_recommendation_parameters(),
4415                  ['area' => $area, 'id' => $id]);
4416  
4417          $context = context_system::instance();
4418          self::validate_context($context);
4419  
4420          require_capability('moodle/course:recommendactivity', $context);
4421  
4422          $manager = \core_course\local\factory\content_item_service_factory::get_content_item_service();
4423  
4424          $status = $manager->toggle_recommendation($area, $id);
4425          return ['id' => $id, 'area' => $area, 'status' => $status];
4426      }
4427  
4428      /**
4429       * Returns warnings.
4430       *
4431       * @return external_description
4432       */
4433      public static function toggle_activity_recommendation_returns() {
4434          return new external_single_structure(
4435              [
4436                  'id' => new external_value(PARAM_INT, 'id of the activity or whatever'),
4437                  'area' => new external_value(PARAM_TEXT, 'The favourite area (itemtype)'),
4438                  'status' => new external_value(PARAM_BOOL, 'If created or deleted'),
4439              ]
4440          );
4441      }
4442  
4443      /**
4444       * Returns description of method parameters
4445       *
4446       * @return external_function_parameters
4447       */
4448      public static function get_activity_chooser_footer_parameters() {
4449          return new external_function_parameters([
4450              'courseid' => new external_value(PARAM_INT, 'ID of the course', VALUE_REQUIRED),
4451              'sectionid' => new external_value(PARAM_INT, 'ID of the section', VALUE_REQUIRED),
4452          ]);
4453      }
4454  
4455      /**
4456       * Given a course ID we need to build up a footre for the chooser.
4457       *
4458       * @param int $courseid The course we want to fetch the modules for
4459       * @param int $sectionid The section we want to fetch the modules for
4460       * @return array
4461       */
4462      public static function get_activity_chooser_footer(int $courseid, int $sectionid) {
4463          [
4464              'courseid' => $courseid,
4465              'sectionid' => $sectionid,
4466          ] = self::validate_parameters(self::get_activity_chooser_footer_parameters(), [
4467              'courseid' => $courseid,
4468              'sectionid' => $sectionid,
4469          ]);
4470  
4471          $coursecontext = context_course::instance($courseid);
4472          self::validate_context($coursecontext);
4473  
4474          $activeplugin = get_config('core', 'activitychooseractivefooter');
4475  
4476          if ($activeplugin !== COURSE_CHOOSER_FOOTER_NONE) {
4477              $footerdata = component_callback($activeplugin, 'custom_chooser_footer', [$courseid, $sectionid]);
4478              return [
4479                  'footer' => true,
4480                  'customfooterjs' => $footerdata->get_footer_js_file(),
4481                  'customfootertemplate' => $footerdata->get_footer_template(),
4482                  'customcarouseltemplate' => $footerdata->get_carousel_template(),
4483              ];
4484          } else {
4485              return [
4486                  'footer' => false,
4487              ];
4488          }
4489      }
4490  
4491      /**
4492       * Returns description of method result value
4493       *
4494       * @return external_description
4495       */
4496      public static function get_activity_chooser_footer_returns() {
4497          return new external_single_structure(
4498              [
4499                  'footer' => new external_value(PARAM_BOOL, 'Is a footer being return by this request?', VALUE_REQUIRED),
4500                  'customfooterjs' => new external_value(PARAM_RAW, 'The path to the plugin JS file', VALUE_OPTIONAL),
4501                  'customfootertemplate' => new external_value(PARAM_RAW, 'The prerendered footer', VALUE_OPTIONAL),
4502                  'customcarouseltemplate' => new external_value(PARAM_RAW, 'Either "" or the prerendered carousel page',
4503                      VALUE_OPTIONAL),
4504              ]
4505          );
4506      }
4507  }