Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

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

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