Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

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