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 311 and 403] [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              // If the category parent is being changed, check for capability in the new parent category
2148              if (isset($cat['parent']) && ($cat['parent'] !== $category->parent)) {
2149                  if ($cat['parent'] == 0) {
2150                      // Creating a top level category requires capability in the system context
2151                      $parentcontext = context_system::instance();
2152                  } else {
2153                      // Category context
2154                      $parentcontext = context_coursecat::instance($cat['parent']);
2155                  }
2156                  self::validate_context($parentcontext);
2157                  require_capability('moodle/category:manage', $parentcontext);
2158              }
2159  
2160              // this will throw an exception if descriptionformat is not valid
2161              external_validate_format($cat['descriptionformat']);
2162  
2163              $category->update($cat);
2164          }
2165  
2166          $transaction->allow_commit();
2167      }
2168  
2169      /**
2170       * Returns description of method result value
2171       *
2172       * @return external_description
2173       * @since Moodle 2.3
2174       */
2175      public static function update_categories_returns() {
2176          return null;
2177      }
2178  
2179      /**
2180       * Returns description of method parameters
2181       *
2182       * @return external_function_parameters
2183       * @since Moodle 2.3
2184       */
2185      public static function delete_categories_parameters() {
2186          return new external_function_parameters(
2187              array(
2188                  'categories' => new external_multiple_structure(
2189                      new external_single_structure(
2190                          array(
2191                              'id' => new external_value(PARAM_INT, 'category id to delete'),
2192                              'newparent' => new external_value(PARAM_INT,
2193                                  'the parent category to move the contents to, if specified', VALUE_OPTIONAL),
2194                              'recursive' => new external_value(PARAM_BOOL, '1: recursively delete all contents inside this
2195                                  category, 0 (default): move contents to newparent or current parent category (except if parent is root)', VALUE_DEFAULT, 0)
2196                          )
2197                      )
2198                  )
2199              )
2200          );
2201      }
2202  
2203      /**
2204       * Delete categories
2205       *
2206       * @param array $categories A list of category ids
2207       * @return array
2208       * @since Moodle 2.3
2209       */
2210      public static function delete_categories($categories) {
2211          global $CFG, $DB;
2212          require_once($CFG->dirroot . "/course/lib.php");
2213  
2214          // Validate parameters.
2215          $params = self::validate_parameters(self::delete_categories_parameters(), array('categories' => $categories));
2216  
2217          $transaction = $DB->start_delegated_transaction();
2218  
2219          foreach ($params['categories'] as $category) {
2220              $deletecat = core_course_category::get($category['id'], MUST_EXIST);
2221              $context = context_coursecat::instance($deletecat->id);
2222              require_capability('moodle/category:manage', $context);
2223              self::validate_context($context);
2224              self::validate_context(get_category_or_system_context($deletecat->parent));
2225  
2226              if ($category['recursive']) {
2227                  // If recursive was specified, then we recursively delete the category's contents.
2228                  if ($deletecat->can_delete_full()) {
2229                      $deletecat->delete_full(false);
2230                  } else {
2231                      throw new moodle_exception('youcannotdeletecategory', '', '', $deletecat->get_formatted_name());
2232                  }
2233              } else {
2234                  // In this situation, we don't delete the category's contents, we either move it to newparent or parent.
2235                  // If the parent is the root, moving is not supported (because a course must always be inside a category).
2236                  // We must move to an existing category.
2237                  if (!empty($category['newparent'])) {
2238                      $newparentcat = core_course_category::get($category['newparent']);
2239                  } else {
2240                      $newparentcat = core_course_category::get($deletecat->parent);
2241                  }
2242  
2243                  // This operation is not allowed. We must move contents to an existing category.
2244                  if (!$newparentcat->id) {
2245                      throw new moodle_exception('movecatcontentstoroot');
2246                  }
2247  
2248                  self::validate_context(context_coursecat::instance($newparentcat->id));
2249                  if ($deletecat->can_move_content_to($newparentcat->id)) {
2250                      $deletecat->delete_move($newparentcat->id, false);
2251                  } else {
2252                      throw new moodle_exception('youcannotdeletecategory', '', '', $deletecat->get_formatted_name());
2253                  }
2254              }
2255          }
2256  
2257          $transaction->allow_commit();
2258      }
2259  
2260      /**
2261       * Returns description of method parameters
2262       *
2263       * @return external_function_parameters
2264       * @since Moodle 2.3
2265       */
2266      public static function delete_categories_returns() {
2267          return null;
2268      }
2269  
2270      /**
2271       * Describes the parameters for delete_modules.
2272       *
2273       * @return external_function_parameters
2274       * @since Moodle 2.5
2275       */
2276      public static function delete_modules_parameters() {
2277          return new external_function_parameters (
2278              array(
2279                  'cmids' => new external_multiple_structure(new external_value(PARAM_INT, 'course module ID',
2280                          VALUE_REQUIRED, '', NULL_NOT_ALLOWED), 'Array of course module IDs'),
2281              )
2282          );
2283      }
2284  
2285      /**
2286       * Deletes a list of provided module instances.
2287       *
2288       * @param array $cmids the course module ids
2289       * @since Moodle 2.5
2290       */
2291      public static function delete_modules($cmids) {
2292          global $CFG, $DB;
2293  
2294          // Require course file containing the course delete module function.
2295          require_once($CFG->dirroot . "/course/lib.php");
2296  
2297          // Clean the parameters.
2298          $params = self::validate_parameters(self::delete_modules_parameters(), array('cmids' => $cmids));
2299  
2300          // Keep track of the course ids we have performed a capability check on to avoid repeating.
2301          $arrcourseschecked = array();
2302  
2303          foreach ($params['cmids'] as $cmid) {
2304              // Get the course module.
2305              $cm = $DB->get_record('course_modules', array('id' => $cmid), '*', MUST_EXIST);
2306  
2307              // Check if we have not yet confirmed they have permission in this course.
2308              if (!in_array($cm->course, $arrcourseschecked)) {
2309                  // Ensure the current user has required permission in this course.
2310                  $context = context_course::instance($cm->course);
2311                  self::validate_context($context);
2312                  // Add to the array.
2313                  $arrcourseschecked[] = $cm->course;
2314              }
2315  
2316              // Ensure they can delete this module.
2317              $modcontext = context_module::instance($cm->id);
2318              require_capability('moodle/course:manageactivities', $modcontext);
2319  
2320              // Delete the module.
2321              course_delete_module($cm->id);
2322          }
2323      }
2324  
2325      /**
2326       * Describes the delete_modules return value.
2327       *
2328       * @return external_single_structure
2329       * @since Moodle 2.5
2330       */
2331      public static function delete_modules_returns() {
2332          return null;
2333      }
2334  
2335      /**
2336       * Returns description of method parameters
2337       *
2338       * @return external_function_parameters
2339       * @since Moodle 2.9
2340       */
2341      public static function view_course_parameters() {
2342          return new external_function_parameters(
2343              array(
2344                  'courseid' => new external_value(PARAM_INT, 'id of the course'),
2345                  'sectionnumber' => new external_value(PARAM_INT, 'section number', VALUE_DEFAULT, 0)
2346              )
2347          );
2348      }
2349  
2350      /**
2351       * Trigger the course viewed event.
2352       *
2353       * @param int $courseid id of course
2354       * @param int $sectionnumber sectionnumber (0, 1, 2...)
2355       * @return array of warnings and status result
2356       * @since Moodle 2.9
2357       * @throws moodle_exception
2358       */
2359      public static function view_course($courseid, $sectionnumber = 0) {
2360          global $CFG;
2361          require_once($CFG->dirroot . "/course/lib.php");
2362  
2363          $params = self::validate_parameters(self::view_course_parameters(),
2364                                              array(
2365                                                  'courseid' => $courseid,
2366                                                  'sectionnumber' => $sectionnumber
2367                                              ));
2368  
2369          $warnings = array();
2370  
2371          $course = get_course($params['courseid']);
2372          $context = context_course::instance($course->id);
2373          self::validate_context($context);
2374  
2375          if (!empty($params['sectionnumber'])) {
2376  
2377              // Get section details and check it exists.
2378              $modinfo = get_fast_modinfo($course);
2379              $coursesection = $modinfo->get_section_info($params['sectionnumber'], MUST_EXIST);
2380  
2381              // Check user is allowed to see it.
2382              if (!$coursesection->uservisible) {
2383                  require_capability('moodle/course:viewhiddensections', $context);
2384              }
2385          }
2386  
2387          course_view($context, $params['sectionnumber']);
2388  
2389          $result = array();
2390          $result['status'] = true;
2391          $result['warnings'] = $warnings;
2392          return $result;
2393      }
2394  
2395      /**
2396       * Returns description of method result value
2397       *
2398       * @return external_description
2399       * @since Moodle 2.9
2400       */
2401      public static function view_course_returns() {
2402          return new external_single_structure(
2403              array(
2404                  'status' => new external_value(PARAM_BOOL, 'status: true if success'),
2405                  'warnings' => new external_warnings()
2406              )
2407          );
2408      }
2409  
2410      /**
2411       * Returns description of method parameters
2412       *
2413       * @return external_function_parameters
2414       * @since Moodle 3.0
2415       */
2416      public static function search_courses_parameters() {
2417          return new external_function_parameters(
2418              array(
2419                  'criterianame'  => new external_value(PARAM_ALPHA, 'criteria name
2420                                                          (search, modulelist (only admins), blocklist (only admins), tagid)'),
2421                  'criteriavalue' => new external_value(PARAM_RAW, 'criteria value'),
2422                  'page'          => new external_value(PARAM_INT, 'page number (0 based)', VALUE_DEFAULT, 0),
2423                  'perpage'       => new external_value(PARAM_INT, 'items per page', VALUE_DEFAULT, 0),
2424                  'requiredcapabilities' => new external_multiple_structure(
2425                      new external_value(PARAM_CAPABILITY, 'Capability string used to filter courses by permission'),
2426                      'Optional list of required capabilities (used to filter the list)', VALUE_DEFAULT, array()
2427                  ),
2428                  'limittoenrolled' => new external_value(PARAM_BOOL, 'limit to enrolled courses', VALUE_DEFAULT, 0),
2429                  'onlywithcompletion' => new external_value(PARAM_BOOL, 'limit to courses where completion is enabled',
2430                      VALUE_DEFAULT, 0),
2431              )
2432          );
2433      }
2434  
2435      /**
2436       * Return the course information that is public (visible by every one)
2437       *
2438       * @param  core_course_list_element $course        course in list object
2439       * @param  stdClass       $coursecontext course context object
2440       * @return array the course information
2441       * @since  Moodle 3.2
2442       */
2443      protected static function get_course_public_information(core_course_list_element $course, $coursecontext) {
2444  
2445          static $categoriescache = array();
2446  
2447          // Category information.
2448          if (!array_key_exists($course->category, $categoriescache)) {
2449              $categoriescache[$course->category] = core_course_category::get($course->category, IGNORE_MISSING);
2450          }
2451          $category = $categoriescache[$course->category];
2452  
2453          // Retrieve course overview used files.
2454          $files = array();
2455          foreach ($course->get_course_overviewfiles() as $file) {
2456              $fileurl = moodle_url::make_webservice_pluginfile_url($file->get_contextid(), $file->get_component(),
2457                                                                      $file->get_filearea(), null, $file->get_filepath(),
2458                                                                      $file->get_filename())->out(false);
2459              $files[] = array(
2460                  'filename' => $file->get_filename(),
2461                  'fileurl' => $fileurl,
2462                  'filesize' => $file->get_filesize(),
2463                  'filepath' => $file->get_filepath(),
2464                  'mimetype' => $file->get_mimetype(),
2465                  'timemodified' => $file->get_timemodified(),
2466              );
2467          }
2468  
2469          // Retrieve the course contacts,
2470          // we need here the users fullname since if we are not enrolled can be difficult to obtain them via other Web Services.
2471          $coursecontacts = array();
2472          foreach ($course->get_course_contacts() as $contact) {
2473               $coursecontacts[] = array(
2474                  'id' => $contact['user']->id,
2475                  'fullname' => $contact['username'],
2476                  'roles' => array_map(function($role){
2477                      return array('id' => $role->id, 'name' => $role->displayname);
2478                  }, $contact['roles']),
2479                  'role' => array('id' => $contact['role']->id, 'name' => $contact['role']->displayname),
2480                  'rolename' => $contact['rolename']
2481               );
2482          }
2483  
2484          // Allowed enrolment methods (maybe we can self-enrol).
2485          $enroltypes = array();
2486          $instances = enrol_get_instances($course->id, true);
2487          foreach ($instances as $instance) {
2488              $enroltypes[] = $instance->enrol;
2489          }
2490  
2491          // Format summary.
2492          list($summary, $summaryformat) =
2493              external_format_text($course->summary, $course->summaryformat, $coursecontext->id, 'course', 'summary', null);
2494  
2495          $categoryname = '';
2496          if (!empty($category)) {
2497              $categoryname = external_format_string($category->name, $category->get_context());
2498          }
2499  
2500          $displayname = get_course_display_name_for_list($course);
2501          $coursereturns = array();
2502          $coursereturns['id']                = $course->id;
2503          $coursereturns['fullname']          = external_format_string($course->fullname, $coursecontext->id);
2504          $coursereturns['displayname']       = external_format_string($displayname, $coursecontext->id);
2505          $coursereturns['shortname']         = external_format_string($course->shortname, $coursecontext->id);
2506          $coursereturns['categoryid']        = $course->category;
2507          $coursereturns['categoryname']      = $categoryname;
2508          $coursereturns['summary']           = $summary;
2509          $coursereturns['summaryformat']     = $summaryformat;
2510          $coursereturns['summaryfiles']      = external_util::get_area_files($coursecontext->id, 'course', 'summary', false, false);
2511          $coursereturns['overviewfiles']     = $files;
2512          $coursereturns['contacts']          = $coursecontacts;
2513          $coursereturns['enrollmentmethods'] = $enroltypes;
2514          $coursereturns['sortorder']         = $course->sortorder;
2515          $coursereturns['showactivitydates'] = $course->showactivitydates;
2516          $coursereturns['showcompletionconditions'] = $course->showcompletionconditions;
2517  
2518          $handler = core_course\customfield\course_handler::create();
2519          if ($customfields = $handler->export_instance_data($course->id)) {
2520              $coursereturns['customfields'] = [];
2521              foreach ($customfields as $data) {
2522                  $coursereturns['customfields'][] = [
2523                      'type' => $data->get_type(),
2524                      'value' => $data->get_value(),
2525                      'valueraw' => $data->get_data_controller()->get_value(),
2526                      'name' => $data->get_name(),
2527                      'shortname' => $data->get_shortname()
2528                  ];
2529              }
2530          }
2531  
2532          return $coursereturns;
2533      }
2534  
2535      /**
2536       * Search courses following the specified criteria.
2537       *
2538       * @param string $criterianame  Criteria name (search, modulelist (only admins), blocklist (only admins), tagid)
2539       * @param string $criteriavalue Criteria value
2540       * @param int $page             Page number (for pagination)
2541       * @param int $perpage          Items per page
2542       * @param array $requiredcapabilities Optional list of required capabilities (used to filter the list).
2543       * @param int $limittoenrolled  Limit to only enrolled courses
2544       * @param int onlywithcompletion Limit to only courses where completion is enabled
2545       * @return array of course objects and warnings
2546       * @since Moodle 3.0
2547       * @throws moodle_exception
2548       */
2549      public static function search_courses($criterianame,
2550                                            $criteriavalue,
2551                                            $page=0,
2552                                            $perpage=0,
2553                                            $requiredcapabilities=array(),
2554                                            $limittoenrolled=0,
2555                                            $onlywithcompletion=0) {
2556          global $CFG;
2557  
2558          $warnings = array();
2559  
2560          $parameters = array(
2561              'criterianame'  => $criterianame,
2562              'criteriavalue' => $criteriavalue,
2563              'page'          => $page,
2564              'perpage'       => $perpage,
2565              'requiredcapabilities' => $requiredcapabilities,
2566              'limittoenrolled' => $limittoenrolled,
2567              'onlywithcompletion' => $onlywithcompletion
2568          );
2569          $params = self::validate_parameters(self::search_courses_parameters(), $parameters);
2570          self::validate_context(context_system::instance());
2571  
2572          $allowedcriterianames = array('search', 'modulelist', 'blocklist', 'tagid');
2573          if (!in_array($params['criterianame'], $allowedcriterianames)) {
2574              throw new invalid_parameter_exception('Invalid value for criterianame parameter (value: '.$params['criterianame'].'),' .
2575                  'allowed values are: '.implode(',', $allowedcriterianames));
2576          }
2577  
2578          if ($params['criterianame'] == 'modulelist' or $params['criterianame'] == 'blocklist') {
2579              require_capability('moodle/site:config', context_system::instance());
2580          }
2581  
2582          $paramtype = array(
2583              'search' => PARAM_RAW,
2584              'modulelist' => PARAM_PLUGIN,
2585              'blocklist' => PARAM_INT,
2586              'tagid' => PARAM_INT
2587          );
2588          $params['criteriavalue'] = clean_param($params['criteriavalue'], $paramtype[$params['criterianame']]);
2589  
2590          // Prepare the search API options.
2591          $searchcriteria = array();
2592          $searchcriteria[$params['criterianame']] = $params['criteriavalue'];
2593          if ($params['onlywithcompletion']) {
2594              $searchcriteria['onlywithcompletion'] = true;
2595          }
2596  
2597          $options = array();
2598          if ($params['perpage'] != 0) {
2599              $offset = $params['page'] * $params['perpage'];
2600              $options = array('offset' => $offset, 'limit' => $params['perpage']);
2601          }
2602  
2603          // Search the courses.
2604          $courses = core_course_category::search_courses($searchcriteria, $options, $params['requiredcapabilities']);
2605          $totalcount = core_course_category::search_courses_count($searchcriteria, $options, $params['requiredcapabilities']);
2606  
2607          if (!empty($limittoenrolled)) {
2608              // Get the courses where the current user has access.
2609              $enrolled = enrol_get_my_courses(array('id', 'cacherev'));
2610          }
2611  
2612          $finalcourses = array();
2613          $categoriescache = array();
2614  
2615          foreach ($courses as $course) {
2616              if (!empty($limittoenrolled)) {
2617                  // Filter out not enrolled courses.
2618                  if (!isset($enrolled[$course->id])) {
2619                      $totalcount--;
2620                      continue;
2621                  }
2622              }
2623  
2624              $coursecontext = context_course::instance($course->id);
2625  
2626              $finalcourses[] = self::get_course_public_information($course, $coursecontext);
2627          }
2628  
2629          return array(
2630              'total' => $totalcount,
2631              'courses' => $finalcourses,
2632              'warnings' => $warnings
2633          );
2634      }
2635  
2636      /**
2637       * Returns a course structure definition
2638       *
2639       * @param  boolean $onlypublicdata set to true, to retrieve only fields viewable by anyone when the course is visible
2640       * @return array the course structure
2641       * @since  Moodle 3.2
2642       */
2643      protected static function get_course_structure($onlypublicdata = true) {
2644          $coursestructure = array(
2645              'id' => new external_value(PARAM_INT, 'course id'),
2646              'fullname' => new external_value(PARAM_RAW, 'course full name'),
2647              'displayname' => new external_value(PARAM_RAW, 'course display name'),
2648              'shortname' => new external_value(PARAM_RAW, 'course short name'),
2649              'categoryid' => new external_value(PARAM_INT, 'category id'),
2650              'categoryname' => new external_value(PARAM_RAW, 'category name'),
2651              'sortorder' => new external_value(PARAM_INT, 'Sort order in the category', VALUE_OPTIONAL),
2652              'summary' => new external_value(PARAM_RAW, 'summary'),
2653              'summaryformat' => new external_format_value('summary'),
2654              'summaryfiles' => new external_files('summary files in the summary field', VALUE_OPTIONAL),
2655              'overviewfiles' => new external_files('additional overview files attached to this course'),
2656              'showactivitydates' => new external_value(PARAM_BOOL, 'Whether the activity dates are shown or not'),
2657              'showcompletionconditions' => new external_value(PARAM_BOOL,
2658                  'Whether the activity completion conditions are shown or not'),
2659              'contacts' => new external_multiple_structure(
2660                  new external_single_structure(
2661                      array(
2662                          'id' => new external_value(PARAM_INT, 'contact user id'),
2663                          'fullname'  => new external_value(PARAM_NOTAGS, 'contact user fullname'),
2664                      )
2665                  ),
2666                  'contact users'
2667              ),
2668              'enrollmentmethods' => new external_multiple_structure(
2669                  new external_value(PARAM_PLUGIN, 'enrollment method'),
2670                  'enrollment methods list'
2671              ),
2672              'customfields' => new external_multiple_structure(
2673                  new external_single_structure(
2674                      array(
2675                          'name' => new external_value(PARAM_RAW, 'The name of the custom field'),
2676                          'shortname' => new external_value(PARAM_RAW,
2677                              'The shortname of the custom field - to be able to build the field class in the code'),
2678                          'type'  => new external_value(PARAM_ALPHANUMEXT,
2679                              'The type of the custom field - text field, checkbox...'),
2680                          'valueraw' => new external_value(PARAM_RAW, 'The raw value of the custom field'),
2681                          'value' => new external_value(PARAM_RAW, 'The value of the custom field'),
2682                      )
2683                  ), 'Custom fields', VALUE_OPTIONAL),
2684          );
2685  
2686          if (!$onlypublicdata) {
2687              $extra = array(
2688                  'idnumber' => new external_value(PARAM_RAW, 'Id number', VALUE_OPTIONAL),
2689                  'format' => new external_value(PARAM_PLUGIN, 'Course format: weeks, topics, social, site,..', VALUE_OPTIONAL),
2690                  'showgrades' => new external_value(PARAM_INT, '1 if grades are shown, otherwise 0', VALUE_OPTIONAL),
2691                  'newsitems' => new external_value(PARAM_INT, 'Number of recent items appearing on the course page', VALUE_OPTIONAL),
2692                  'startdate' => new external_value(PARAM_INT, 'Timestamp when the course start', VALUE_OPTIONAL),
2693                  'enddate' => new external_value(PARAM_INT, 'Timestamp when the course end', VALUE_OPTIONAL),
2694                  'maxbytes' => new external_value(PARAM_INT, 'Largest size of file that can be uploaded into', VALUE_OPTIONAL),
2695                  'showreports' => new external_value(PARAM_INT, 'Are activity report shown (yes = 1, no =0)', VALUE_OPTIONAL),
2696                  'visible' => new external_value(PARAM_INT, '1: available to student, 0:not available', VALUE_OPTIONAL),
2697                  'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible', VALUE_OPTIONAL),
2698                  'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no', VALUE_OPTIONAL),
2699                  'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id', VALUE_OPTIONAL),
2700                  'enablecompletion' => new external_value(PARAM_INT, 'Completion enabled? 1: yes 0: no', VALUE_OPTIONAL),
2701                  'completionnotify' => new external_value(PARAM_INT, '1: yes 0: no', VALUE_OPTIONAL),
2702                  'lang' => new external_value(PARAM_SAFEDIR, 'Forced course language', VALUE_OPTIONAL),
2703                  'theme' => new external_value(PARAM_PLUGIN, 'Fame of the forced theme', VALUE_OPTIONAL),
2704                  'marker' => new external_value(PARAM_INT, 'Current course marker', VALUE_OPTIONAL),
2705                  'legacyfiles' => new external_value(PARAM_INT, 'If legacy files are enabled', VALUE_OPTIONAL),
2706                  'calendartype' => new external_value(PARAM_PLUGIN, 'Calendar type', VALUE_OPTIONAL),
2707                  'timecreated' => new external_value(PARAM_INT, 'Time when the course was created', VALUE_OPTIONAL),
2708                  'timemodified' => new external_value(PARAM_INT, 'Last time  the course was updated', VALUE_OPTIONAL),
2709                  'requested' => new external_value(PARAM_INT, 'If is a requested course', VALUE_OPTIONAL),
2710                  'cacherev' => new external_value(PARAM_INT, 'Cache revision number', VALUE_OPTIONAL),
2711                  'filters' => new external_multiple_structure(
2712                      new external_single_structure(
2713                          array(
2714                              'filter'  => new external_value(PARAM_PLUGIN, 'Filter plugin name'),
2715                              'localstate' => new external_value(PARAM_INT, 'Filter state: 1 for on, -1 for off, 0 if inherit'),
2716                              'inheritedstate' => new external_value(PARAM_INT, '1 or 0 to use when localstate is set to inherit'),
2717                          )
2718                      ),
2719                      'Course filters', VALUE_OPTIONAL
2720                  ),
2721                  'courseformatoptions' => new external_multiple_structure(
2722                      new external_single_structure(
2723                          array(
2724                              'name' => new external_value(PARAM_RAW, 'Course format option name.'),
2725                              'value' => new external_value(PARAM_RAW, 'Course format option value.'),
2726                          )
2727                      ),
2728                      'Additional options for particular course format.', VALUE_OPTIONAL
2729                  ),
2730              );
2731              $coursestructure = array_merge($coursestructure, $extra);
2732          }
2733          return new external_single_structure($coursestructure);
2734      }
2735  
2736      /**
2737       * Returns description of method result value
2738       *
2739       * @return external_description
2740       * @since Moodle 3.0
2741       */
2742      public static function search_courses_returns() {
2743          return new external_single_structure(
2744              array(
2745                  'total' => new external_value(PARAM_INT, 'total course count'),
2746                  'courses' => new external_multiple_structure(self::get_course_structure(), 'course'),
2747                  'warnings' => new external_warnings()
2748              )
2749          );
2750      }
2751  
2752      /**
2753       * Returns description of method parameters
2754       *
2755       * @return external_function_parameters
2756       * @since Moodle 3.0
2757       */
2758      public static function get_course_module_parameters() {
2759          return new external_function_parameters(
2760              array(
2761                  'cmid' => new external_value(PARAM_INT, 'The course module id')
2762              )
2763          );
2764      }
2765  
2766      /**
2767       * Return information about a course module.
2768       *
2769       * @param int $cmid the course module id
2770       * @return array of warnings and the course module
2771       * @since Moodle 3.0
2772       * @throws moodle_exception
2773       */
2774      public static function get_course_module($cmid) {
2775          global $CFG, $DB;
2776  
2777          $params = self::validate_parameters(self::get_course_module_parameters(), array('cmid' => $cmid));
2778          $warnings = array();
2779  
2780          $cm = get_coursemodule_from_id(null, $params['cmid'], 0, true, MUST_EXIST);
2781          $context = context_module::instance($cm->id);
2782          self::validate_context($context);
2783  
2784          // If the user has permissions to manage the activity, return all the information.
2785          if (has_capability('moodle/course:manageactivities', $context)) {
2786              require_once($CFG->dirroot . '/course/modlib.php');
2787              require_once($CFG->libdir . '/gradelib.php');
2788  
2789              $info = $cm;
2790              // Get the extra information: grade, advanced grading and outcomes data.
2791              $course = get_course($cm->course);
2792              list($newcm, $newcontext, $module, $extrainfo, $cw) = get_moduleinfo_data($cm, $course);
2793              // Grades.
2794              $gradeinfo = array('grade', 'gradepass', 'gradecat');
2795              foreach ($gradeinfo as $gfield) {
2796                  if (isset($extrainfo->{$gfield})) {
2797                      $info->{$gfield} = $extrainfo->{$gfield};
2798                  }
2799              }
2800              if (isset($extrainfo->grade) and $extrainfo->grade < 0) {
2801                  $info->scale = $DB->get_field('scale', 'scale', array('id' => abs($extrainfo->grade)));
2802              }
2803              // Advanced grading.
2804              if (isset($extrainfo->_advancedgradingdata)) {
2805                  $info->advancedgrading = array();
2806                  foreach ($extrainfo as $key => $val) {
2807                      if (strpos($key, 'advancedgradingmethod_') === 0) {
2808                          $info->advancedgrading[] = array(
2809                              'area' => str_replace('advancedgradingmethod_', '', $key),
2810                              'method' => $val
2811                          );
2812                      }
2813                  }
2814              }
2815              // Outcomes.
2816              foreach ($extrainfo as $key => $val) {
2817                  if (strpos($key, 'outcome_') === 0) {
2818                      if (!isset($info->outcomes)) {
2819                          $info->outcomes = array();
2820                      }
2821                      $id = str_replace('outcome_', '', $key);
2822                      $outcome = grade_outcome::fetch(array('id' => $id));
2823                      $scaleitems = $outcome->load_scale();
2824                      $info->outcomes[] = array(
2825                          'id' => $id,
2826                          'name' => external_format_string($outcome->get_name(), $context->id),
2827                          'scale' => $scaleitems->scale
2828                      );
2829                  }
2830              }
2831          } else {
2832              // Return information is safe to show to any user.
2833              $info = new stdClass();
2834              $info->id = $cm->id;
2835              $info->course = $cm->course;
2836              $info->module = $cm->module;
2837              $info->modname = $cm->modname;
2838              $info->instance = $cm->instance;
2839              $info->section = $cm->section;
2840              $info->sectionnum = $cm->sectionnum;
2841              $info->groupmode = $cm->groupmode;
2842              $info->groupingid = $cm->groupingid;
2843              $info->completion = $cm->completion;
2844          }
2845          // Format name.
2846          $info->name = external_format_string($cm->name, $context->id);
2847          $result = array();
2848          $result['cm'] = $info;
2849          $result['warnings'] = $warnings;
2850          return $result;
2851      }
2852  
2853      /**
2854       * Returns description of method result value
2855       *
2856       * @return external_description
2857       * @since Moodle 3.0
2858       */
2859      public static function get_course_module_returns() {
2860          return new external_single_structure(
2861              array(
2862                  'cm' => new external_single_structure(
2863                      array(
2864                          'id' => new external_value(PARAM_INT, 'The course module id'),
2865                          'course' => new external_value(PARAM_INT, 'The course id'),
2866                          'module' => new external_value(PARAM_INT, 'The module type id'),
2867                          'name' => new external_value(PARAM_RAW, 'The activity name'),
2868                          'modname' => new external_value(PARAM_COMPONENT, 'The module component name (forum, assign, etc..)'),
2869                          'instance' => new external_value(PARAM_INT, 'The activity instance id'),
2870                          'section' => new external_value(PARAM_INT, 'The module section id'),
2871                          'sectionnum' => new external_value(PARAM_INT, 'The module section number'),
2872                          'groupmode' => new external_value(PARAM_INT, 'Group mode'),
2873                          'groupingid' => new external_value(PARAM_INT, 'Grouping id'),
2874                          'completion' => new external_value(PARAM_INT, 'If completion is enabled'),
2875                          'idnumber' => new external_value(PARAM_RAW, 'Module id number', VALUE_OPTIONAL),
2876                          'added' => new external_value(PARAM_INT, 'Time added', VALUE_OPTIONAL),
2877                          'score' => new external_value(PARAM_INT, 'Score', VALUE_OPTIONAL),
2878                          'indent' => new external_value(PARAM_INT, 'Indentation', VALUE_OPTIONAL),
2879                          'visible' => new external_value(PARAM_INT, 'If visible', VALUE_OPTIONAL),
2880                          'visibleoncoursepage' => new external_value(PARAM_INT, 'If visible on course page', VALUE_OPTIONAL),
2881                          'visibleold' => new external_value(PARAM_INT, 'Visible old', VALUE_OPTIONAL),
2882                          'completiongradeitemnumber' => new external_value(PARAM_INT, 'Completion grade item', VALUE_OPTIONAL),
2883                          'completionview' => new external_value(PARAM_INT, 'Completion view setting', VALUE_OPTIONAL),
2884                          'completionexpected' => new external_value(PARAM_INT, 'Completion time expected', VALUE_OPTIONAL),
2885                          'showdescription' => new external_value(PARAM_INT, 'If the description is showed', VALUE_OPTIONAL),
2886                          'availability' => new external_value(PARAM_RAW, 'Availability settings', VALUE_OPTIONAL),
2887                          'grade' => new external_value(PARAM_FLOAT, 'Grade (max value or scale id)', VALUE_OPTIONAL),
2888                          'scale' => new external_value(PARAM_TEXT, 'Scale items (if used)', VALUE_OPTIONAL),
2889                          'gradepass' => new external_value(PARAM_RAW, 'Grade to pass (float)', VALUE_OPTIONAL),
2890                          'gradecat' => new external_value(PARAM_INT, 'Grade category', VALUE_OPTIONAL),
2891                          'advancedgrading' => new external_multiple_structure(
2892                              new external_single_structure(
2893                                  array(
2894                                      'area' => new external_value(PARAM_AREA, 'Gradable area name'),
2895                                      'method'  => new external_value(PARAM_COMPONENT, 'Grading method'),
2896                                  )
2897                              ),
2898                              'Advanced grading settings', VALUE_OPTIONAL
2899                          ),
2900                          'outcomes' => new external_multiple_structure(
2901                              new external_single_structure(
2902                                  array(
2903                                      'id' => new external_value(PARAM_ALPHANUMEXT, 'Outcome id'),
2904                                      'name'  => new external_value(PARAM_RAW, 'Outcome full name'),
2905                                      'scale' => new external_value(PARAM_TEXT, 'Scale items')
2906                                  )
2907                              ),
2908                              'Outcomes information', VALUE_OPTIONAL
2909                          ),
2910                      )
2911                  ),
2912                  'warnings' => new external_warnings()
2913              )
2914          );
2915      }
2916  
2917      /**
2918       * Returns description of method parameters
2919       *
2920       * @return external_function_parameters
2921       * @since Moodle 3.0
2922       */
2923      public static function get_course_module_by_instance_parameters() {
2924          return new external_function_parameters(
2925              array(
2926                  'module' => new external_value(PARAM_COMPONENT, 'The module name'),
2927                  'instance' => new external_value(PARAM_INT, 'The module instance id')
2928              )
2929          );
2930      }
2931  
2932      /**
2933       * Return information about a course module.
2934       *
2935       * @param string $module the module name
2936       * @param int $instance the activity instance id
2937       * @return array of warnings and the course module
2938       * @since Moodle 3.0
2939       * @throws moodle_exception
2940       */
2941      public static function get_course_module_by_instance($module, $instance) {
2942  
2943          $params = self::validate_parameters(self::get_course_module_by_instance_parameters(),
2944                                              array(
2945                                                  'module' => $module,
2946                                                  'instance' => $instance,
2947                                              ));
2948  
2949          $warnings = array();
2950          $cm = get_coursemodule_from_instance($params['module'], $params['instance'], 0, false, MUST_EXIST);
2951  
2952          return self::get_course_module($cm->id);
2953      }
2954  
2955      /**
2956       * Returns description of method result value
2957       *
2958       * @return external_description
2959       * @since Moodle 3.0
2960       */
2961      public static function get_course_module_by_instance_returns() {
2962          return self::get_course_module_returns();
2963      }
2964  
2965      /**
2966       * Returns description of method parameters
2967       *
2968       * @return external_function_parameters
2969       * @since Moodle 3.2
2970       */
2971      public static function get_user_navigation_options_parameters() {
2972          return new external_function_parameters(
2973              array(
2974                  'courseids' => new external_multiple_structure(new external_value(PARAM_INT, 'Course id.')),
2975              )
2976          );
2977      }
2978  
2979      /**
2980       * Return a list of navigation options in a set of courses that are avaialable or not for the current user.
2981       *
2982       * @param array $courseids a list of course ids
2983       * @return array of warnings and the options availability
2984       * @since Moodle 3.2
2985       * @throws moodle_exception
2986       */
2987      public static function get_user_navigation_options($courseids) {
2988          global $CFG;
2989          require_once($CFG->dirroot . '/course/lib.php');
2990  
2991          // Parameter validation.
2992          $params = self::validate_parameters(self::get_user_navigation_options_parameters(), array('courseids' => $courseids));
2993          $courseoptions = array();
2994  
2995          list($courses, $warnings) = external_util::validate_courses($params['courseids'], array(), true);
2996  
2997          if (!empty($courses)) {
2998              foreach ($courses as $course) {
2999                  // Fix the context for the frontpage.
3000                  if ($course->id == SITEID) {
3001                      $course->context = context_system::instance();
3002                  }
3003                  $navoptions = course_get_user_navigation_options($course->context, $course);
3004                  $options = array();
3005                  foreach ($navoptions as $name => $available) {
3006                      $options[] = array(
3007                          'name' => $name,
3008                          'available' => $available,
3009                      );
3010                  }
3011  
3012                  $courseoptions[] = array(
3013                      'id' => $course->id,
3014                      'options' => $options
3015                  );
3016              }
3017          }
3018  
3019          $result = array(
3020              'courses' => $courseoptions,
3021              'warnings' => $warnings
3022          );
3023          return $result;
3024      }
3025  
3026      /**
3027       * Returns description of method result value
3028       *
3029       * @return external_description
3030       * @since Moodle 3.2
3031       */
3032      public static function get_user_navigation_options_returns() {
3033          return new external_single_structure(
3034              array(
3035                  'courses' => new external_multiple_structure(
3036                      new external_single_structure(
3037                          array(
3038                              'id' => new external_value(PARAM_INT, 'Course id'),
3039                              'options' => new external_multiple_structure(
3040                                  new external_single_structure(
3041                                      array(
3042                                          'name' => new external_value(PARAM_ALPHANUMEXT, 'Option name'),
3043                                          'available' => new external_value(PARAM_BOOL, 'Whether the option is available or not'),
3044                                      )
3045                                  )
3046                              )
3047                          )
3048                      ), 'List of courses'
3049                  ),
3050                  'warnings' => new external_warnings()
3051              )
3052          );
3053      }
3054  
3055      /**
3056       * Returns description of method parameters
3057       *
3058       * @return external_function_parameters
3059       * @since Moodle 3.2
3060       */
3061      public static function get_user_administration_options_parameters() {
3062          return new external_function_parameters(
3063              array(
3064                  'courseids' => new external_multiple_structure(new external_value(PARAM_INT, 'Course id.')),
3065              )
3066          );
3067      }
3068  
3069      /**
3070       * Return a list of administration options in a set of courses that are available or not for the current user.
3071       *
3072       * @param array $courseids a list of course ids
3073       * @return array of warnings and the options availability
3074       * @since Moodle 3.2
3075       * @throws moodle_exception
3076       */
3077      public static function get_user_administration_options($courseids) {
3078          global $CFG;
3079          require_once($CFG->dirroot . '/course/lib.php');
3080  
3081          // Parameter validation.
3082          $params = self::validate_parameters(self::get_user_administration_options_parameters(), array('courseids' => $courseids));
3083          $courseoptions = array();
3084  
3085          list($courses, $warnings) = external_util::validate_courses($params['courseids'], array(), true);
3086  
3087          if (!empty($courses)) {
3088              foreach ($courses as $course) {
3089                  $adminoptions = course_get_user_administration_options($course, $course->context);
3090                  $options = array();
3091                  foreach ($adminoptions as $name => $available) {
3092                      $options[] = array(
3093                          'name' => $name,
3094                          'available' => $available,
3095                      );
3096                  }
3097  
3098                  $courseoptions[] = array(
3099                      'id' => $course->id,
3100                      'options' => $options
3101                  );
3102              }
3103          }
3104  
3105          $result = array(
3106              'courses' => $courseoptions,
3107              'warnings' => $warnings
3108          );
3109          return $result;
3110      }
3111  
3112      /**
3113       * Returns description of method result value
3114       *
3115       * @return external_description
3116       * @since Moodle 3.2
3117       */
3118      public static function get_user_administration_options_returns() {
3119          return self::get_user_navigation_options_returns();
3120      }
3121  
3122      /**
3123       * Returns description of method parameters
3124       *
3125       * @return external_function_parameters
3126       * @since Moodle 3.2
3127       */
3128      public static function get_courses_by_field_parameters() {
3129          return new external_function_parameters(
3130              array(
3131                  'field' => new external_value(PARAM_ALPHA, 'The field to search can be left empty for all courses or:
3132                      id: course id
3133                      ids: comma separated course ids
3134                      shortname: course short name
3135                      idnumber: course id number
3136                      category: category id the course belongs to
3137                  ', VALUE_DEFAULT, ''),
3138                  'value' => new external_value(PARAM_RAW, 'The value to match', VALUE_DEFAULT, '')
3139              )
3140          );
3141      }
3142  
3143  
3144      /**
3145       * Get courses matching a specific field (id/s, shortname, idnumber, category)
3146       *
3147       * @param  string $field field name to search, or empty for all courses
3148       * @param  string $value value to search
3149       * @return array list of courses and warnings
3150       * @throws  invalid_parameter_exception
3151       * @since Moodle 3.2
3152       */
3153      public static function get_courses_by_field($field = '', $value = '') {
3154          global $DB, $CFG;
3155          require_once($CFG->dirroot . '/course/lib.php');
3156          require_once($CFG->libdir . '/filterlib.php');
3157  
3158          $params = self::validate_parameters(self::get_courses_by_field_parameters(),
3159              array(
3160                  'field' => $field,
3161                  'value' => $value,
3162              )
3163          );
3164          $warnings = array();
3165  
3166          if (empty($params['field'])) {
3167              $courses = $DB->get_records('course', null, 'id ASC');
3168          } else {
3169              switch ($params['field']) {
3170                  case 'id':
3171                  case 'category':
3172                      $value = clean_param($params['value'], PARAM_INT);
3173                      break;
3174                  case 'ids':
3175                      $value = clean_param($params['value'], PARAM_SEQUENCE);
3176                      break;
3177                  case 'shortname':
3178                      $value = clean_param($params['value'], PARAM_TEXT);
3179                      break;
3180                  case 'idnumber':
3181                      $value = clean_param($params['value'], PARAM_RAW);
3182                      break;
3183                  default:
3184                      throw new invalid_parameter_exception('Invalid field name');
3185              }
3186  
3187              if ($params['field'] === 'ids') {
3188                  // Preload categories to avoid loading one at a time.
3189                  $courseids = explode(',', $value);
3190                  list ($listsql, $listparams) = $DB->get_in_or_equal($courseids);
3191                  $categoryids = $DB->get_fieldset_sql("
3192                          SELECT DISTINCT cc.id
3193                            FROM {course} c
3194                            JOIN {course_categories} cc ON cc.id = c.category
3195                           WHERE c.id $listsql", $listparams);
3196                  core_course_category::get_many($categoryids);
3197  
3198                  // Load and validate all courses. This is called because it loads the courses
3199                  // more efficiently.
3200                  list ($courses, $warnings) = external_util::validate_courses($courseids, [],
3201                          false, true);
3202              } else {
3203                  $courses = $DB->get_records('course', array($params['field'] => $value), 'id ASC');
3204              }
3205          }
3206  
3207          $coursesdata = array();
3208          foreach ($courses as $course) {
3209              $context = context_course::instance($course->id);
3210              $canupdatecourse = has_capability('moodle/course:update', $context);
3211              $canviewhiddencourses = has_capability('moodle/course:viewhiddencourses', $context);
3212  
3213              // Check if the course is visible in the site for the user.
3214              if (!$course->visible and !$canviewhiddencourses and !$canupdatecourse) {
3215                  continue;
3216              }
3217              // Get the public course information, even if we are not enrolled.
3218              $courseinlist = new core_course_list_element($course);
3219  
3220              // Now, check if we have access to the course, unless it was already checked.
3221              try {
3222                  if (empty($course->contextvalidated)) {
3223                      self::validate_context($context);
3224                  }
3225              } catch (Exception $e) {
3226                  // User can not access the course, check if they can see the public information about the course and return it.
3227                  if (core_course_category::can_view_course_info($course)) {
3228                      $coursesdata[$course->id] = self::get_course_public_information($courseinlist, $context);
3229                  }
3230                  continue;
3231              }
3232              $coursesdata[$course->id] = self::get_course_public_information($courseinlist, $context);
3233              // Return information for any user that can access the course.
3234              $coursefields = array('format', 'showgrades', 'newsitems', 'startdate', 'enddate', 'maxbytes', 'showreports', 'visible',
3235                  'groupmode', 'groupmodeforce', 'defaultgroupingid', 'enablecompletion', 'completionnotify', 'lang', 'theme',
3236                  'marker');
3237  
3238              // Course filters.
3239              $coursesdata[$course->id]['filters'] = filter_get_available_in_context($context);
3240  
3241              // Information for managers only.
3242              if ($canupdatecourse) {
3243                  $managerfields = array('idnumber', 'legacyfiles', 'calendartype', 'timecreated', 'timemodified', 'requested',
3244                      'cacherev');
3245                  $coursefields = array_merge($coursefields, $managerfields);
3246              }
3247  
3248              // Populate fields.
3249              foreach ($coursefields as $field) {
3250                  $coursesdata[$course->id][$field] = $course->{$field};
3251              }
3252  
3253              // Clean lang and auth fields for external functions (it may content uninstalled themes or language packs).
3254              if (isset($coursesdata[$course->id]['theme'])) {
3255                  $coursesdata[$course->id]['theme'] = clean_param($coursesdata[$course->id]['theme'], PARAM_THEME);
3256              }
3257              if (isset($coursesdata[$course->id]['lang'])) {
3258                  $coursesdata[$course->id]['lang'] = clean_param($coursesdata[$course->id]['lang'], PARAM_LANG);
3259              }
3260  
3261              $courseformatoptions = course_get_format($course)->get_config_for_external();
3262              foreach ($courseformatoptions as $key => $value) {
3263                  $coursesdata[$course->id]['courseformatoptions'][] = array(
3264                      'name' => $key,
3265                      'value' => $value
3266                  );
3267              }
3268          }
3269  
3270          return array(
3271              'courses' => $coursesdata,
3272              'warnings' => $warnings
3273          );
3274      }
3275  
3276      /**
3277       * Returns description of method result value
3278       *
3279       * @return external_description
3280       * @since Moodle 3.2
3281       */
3282      public static function get_courses_by_field_returns() {
3283          // Course structure, including not only public viewable fields.
3284          return new external_single_structure(
3285              array(
3286                  'courses' => new external_multiple_structure(self::get_course_structure(false), 'Course'),
3287                  'warnings' => new external_warnings()
3288              )
3289          );
3290      }
3291  
3292      /**
3293       * Returns description of method parameters
3294       *
3295       * @return external_function_parameters
3296       * @since Moodle 3.2
3297       */
3298      public static function check_updates_parameters() {
3299          return new external_function_parameters(
3300              array(
3301                  'courseid' => new external_value(PARAM_INT, 'Course id to check'),
3302                  'tocheck' => new external_multiple_structure(
3303                      new external_single_structure(
3304                          array(
3305                              'contextlevel' => new external_value(PARAM_ALPHA, 'The context level for the file location.
3306                                                                                  Only module supported right now.'),
3307                              'id' => new external_value(PARAM_INT, 'Context instance id'),
3308                              'since' => new external_value(PARAM_INT, 'Check updates since this time stamp'),
3309                          )
3310                      ),
3311                      'Instances to check'
3312                  ),
3313                  'filter' => new external_multiple_structure(
3314                      new external_value(PARAM_ALPHANUM, 'Area name: configuration, fileareas, completion, ratings, comments,
3315                                                          gradeitems, outcomes'),
3316                      'Check only for updates in these areas', VALUE_DEFAULT, array()
3317                  )
3318              )
3319          );
3320      }
3321  
3322      /**
3323       * Check if there is updates affecting the user for the given course and contexts.
3324       * Right now only modules are supported.
3325       * This WS calls mod_check_updates_since for each module to check if there is any update the user should we aware of.
3326       *
3327       * @param int $courseid the list of modules to check
3328       * @param array $tocheck the list of modules to check
3329       * @param array $filter check only for updates in these areas
3330       * @return array list of updates and warnings
3331       * @throws moodle_exception
3332       * @since Moodle 3.2
3333       */
3334      public static function check_updates($courseid, $tocheck, $filter = array()) {
3335          global $CFG, $DB;
3336          require_once($CFG->dirroot . "/course/lib.php");
3337  
3338          $params = self::validate_parameters(
3339              self::check_updates_parameters(),
3340              array(
3341                  'courseid' => $courseid,
3342                  'tocheck' => $tocheck,
3343                  'filter' => $filter,
3344              )
3345          );
3346  
3347          $course = get_course($params['courseid']);
3348          $context = context_course::instance($course->id);
3349          self::validate_context($context);
3350  
3351          list($instances, $warnings) = course_check_updates($course, $params['tocheck'], $filter);
3352  
3353          $instancesformatted = array();
3354          foreach ($instances as $instance) {
3355              $updates = array();
3356              foreach ($instance['updates'] as $name => $data) {
3357                  if (empty($data->updated)) {
3358                      continue;
3359                  }
3360                  $updatedata = array(
3361                      'name' => $name,
3362                  );
3363                  if (!empty($data->timeupdated)) {
3364                      $updatedata['timeupdated'] = $data->timeupdated;
3365                  }
3366                  if (!empty($data->itemids)) {
3367                      $updatedata['itemids'] = $data->itemids;
3368                  }
3369                  $updates[] = $updatedata;
3370              }
3371              if (!empty($updates)) {
3372                  $instancesformatted[] = array(
3373                      'contextlevel' => $instance['contextlevel'],
3374                      'id' => $instance['id'],
3375                      'updates' => $updates
3376                  );
3377              }
3378          }
3379  
3380          return array(
3381              'instances' => $instancesformatted,
3382              'warnings' => $warnings
3383          );
3384      }
3385  
3386      /**
3387       * Returns description of method result value
3388       *
3389       * @return external_description
3390       * @since Moodle 3.2
3391       */
3392      public static function check_updates_returns() {
3393          return new external_single_structure(
3394              array(
3395                  'instances' => new external_multiple_structure(
3396                      new external_single_structure(
3397                          array(
3398                              'contextlevel' => new external_value(PARAM_ALPHA, 'The context level'),
3399                              'id' => new external_value(PARAM_INT, 'Instance id'),
3400                              'updates' => new external_multiple_structure(
3401                                  new external_single_structure(
3402                                      array(
3403                                          'name' => new external_value(PARAM_ALPHANUMEXT, 'Name of the area updated.'),
3404                                          'timeupdated' => new external_value(PARAM_INT, 'Last time was updated', VALUE_OPTIONAL),
3405                                          'itemids' => new external_multiple_structure(
3406                                              new external_value(PARAM_INT, 'Instance id'),
3407                                              'The ids of the items updated',
3408                                              VALUE_OPTIONAL
3409                                          )
3410                                      )
3411                                  )
3412                              )
3413                          )
3414                      )
3415                  ),
3416                  'warnings' => new external_warnings()
3417              )
3418          );
3419      }
3420  
3421      /**
3422       * Returns description of method parameters
3423       *
3424       * @return external_function_parameters
3425       * @since Moodle 3.3
3426       */
3427      public static function get_updates_since_parameters() {
3428          return new external_function_parameters(
3429              array(
3430                  'courseid' => new external_value(PARAM_INT, 'Course id to check'),
3431                  'since' => new external_value(PARAM_INT, 'Check updates since this time stamp'),
3432                  'filter' => new external_multiple_structure(
3433                      new external_value(PARAM_ALPHANUM, 'Area name: configuration, fileareas, completion, ratings, comments,
3434                                                          gradeitems, outcomes'),
3435                      'Check only for updates in these areas', VALUE_DEFAULT, array()
3436                  )
3437              )
3438          );
3439      }
3440  
3441      /**
3442       * Check if there are updates affecting the user for the given course since the given time stamp.
3443       *
3444       * This function is a wrapper of self::check_updates for retrieving all the updates since a given time for all the activities.
3445       *
3446       * @param int $courseid the list of modules to check
3447       * @param int $since check updates since this time stamp
3448       * @param array $filter check only for updates in these areas
3449       * @return array list of updates and warnings
3450       * @throws moodle_exception
3451       * @since Moodle 3.3
3452       */
3453      public static function get_updates_since($courseid, $since, $filter = array()) {
3454          global $CFG, $DB;
3455  
3456          $params = self::validate_parameters(
3457              self::get_updates_since_parameters(),
3458              array(
3459                  'courseid' => $courseid,
3460                  'since' => $since,
3461                  'filter' => $filter,
3462              )
3463          );
3464  
3465          $course = get_course($params['courseid']);
3466          $modinfo = get_fast_modinfo($course);
3467          $tocheck = array();
3468  
3469          // Retrieve all the visible course modules for the current user.
3470          $cms = $modinfo->get_cms();
3471          foreach ($cms as $cm) {
3472              if (!$cm->uservisible) {
3473                  continue;
3474              }
3475              $tocheck[] = array(
3476                  'id' => $cm->id,
3477                  'contextlevel' => 'module',
3478                  'since' => $params['since'],
3479              );
3480          }
3481  
3482          return self::check_updates($course->id, $tocheck, $params['filter']);
3483      }
3484  
3485      /**
3486       * Returns description of method result value
3487       *
3488       * @return external_description
3489       * @since Moodle 3.3
3490       */
3491      public static function get_updates_since_returns() {
3492          return self::check_updates_returns();
3493      }
3494  
3495      /**
3496       * Parameters for function edit_module()
3497       *
3498       * @since Moodle 3.3
3499       * @return external_function_parameters
3500       */
3501      public static function edit_module_parameters() {
3502          return new external_function_parameters(
3503              array(
3504                  'action' => new external_value(PARAM_ALPHA,
3505                      'action: hide, show, stealth, duplicate, delete, moveleft, moveright, group...', VALUE_REQUIRED),
3506                  'id' => new external_value(PARAM_INT, 'course module id', VALUE_REQUIRED),
3507                  'sectionreturn' => new external_value(PARAM_INT, 'section to return to', VALUE_DEFAULT, null),
3508              ));
3509      }
3510  
3511      /**
3512       * Performs one of the edit module actions and return new html for AJAX
3513       *
3514       * Returns html to replace the current module html with, for example:
3515       * - empty string for "delete" action,
3516       * - two modules html for "duplicate" action
3517       * - updated module html for everything else
3518       *
3519       * Throws exception if operation is not permitted/possible
3520       *
3521       * @since Moodle 3.3
3522       * @param string $action
3523       * @param int $id
3524       * @param null|int $sectionreturn
3525       * @return string
3526       */
3527      public static function edit_module($action, $id, $sectionreturn = null) {
3528          global $PAGE, $DB;
3529          // Validate and normalize parameters.
3530          $params = self::validate_parameters(self::edit_module_parameters(),
3531              array('action' => $action, 'id' => $id, 'sectionreturn' => $sectionreturn));
3532          $action = $params['action'];
3533          $id = $params['id'];
3534          $sectionreturn = $params['sectionreturn'];
3535  
3536          // Set of permissions an editing user may have.
3537          $contextarray = [
3538                  'moodle/course:update',
3539                  'moodle/course:manageactivities',
3540                  'moodle/course:activityvisibility',
3541                  'moodle/course:sectionvisibility',
3542                  'moodle/course:movesections',
3543                  'moodle/course:setcurrentsection',
3544          ];
3545          $PAGE->set_other_editing_capability($contextarray);
3546  
3547          list($course, $cm) = get_course_and_cm_from_cmid($id);
3548          $modcontext = context_module::instance($cm->id);
3549          $coursecontext = context_course::instance($course->id);
3550          self::validate_context($modcontext);
3551          $courserenderer = $PAGE->get_renderer('core', 'course');
3552          $completioninfo = new completion_info($course);
3553  
3554          switch($action) {
3555              case 'hide':
3556              case 'show':
3557              case 'stealth':
3558                  require_capability('moodle/course:activityvisibility', $modcontext);
3559                  $visible = ($action === 'hide') ? 0 : 1;
3560                  $visibleoncoursepage = ($action === 'stealth') ? 0 : 1;
3561                  set_coursemodule_visible($id, $visible, $visibleoncoursepage);
3562                  \core\event\course_module_updated::create_from_cm($cm, $modcontext)->trigger();
3563                  break;
3564              case 'duplicate':
3565                  require_capability('moodle/course:manageactivities', $coursecontext);
3566                  require_capability('moodle/backup:backuptargetimport', $coursecontext);
3567                  require_capability('moodle/restore:restoretargetimport', $coursecontext);
3568                  if (!course_allowed_module($course, $cm->modname)) {
3569                      throw new moodle_exception('No permission to create that activity');
3570                  }
3571                  if ($newcm = duplicate_module($course, $cm)) {
3572                      $cm = get_fast_modinfo($course)->get_cm($id);
3573                      $newcm = get_fast_modinfo($course)->get_cm($newcm->id);
3574                      return $courserenderer->course_section_cm_list_item($course, $completioninfo, $cm, $sectionreturn) .
3575                          $courserenderer->course_section_cm_list_item($course, $completioninfo, $newcm, $sectionreturn);
3576                  }
3577                  break;
3578              case 'groupsseparate':
3579              case 'groupsvisible':
3580              case 'groupsnone':
3581                  require_capability('moodle/course:manageactivities', $modcontext);
3582                  if ($action === 'groupsseparate') {
3583                      $newgroupmode = SEPARATEGROUPS;
3584                  } else if ($action === 'groupsvisible') {
3585                      $newgroupmode = VISIBLEGROUPS;
3586                  } else {
3587                      $newgroupmode = NOGROUPS;
3588                  }
3589                  if (set_coursemodule_groupmode($cm->id, $newgroupmode)) {
3590                      \core\event\course_module_updated::create_from_cm($cm, $modcontext)->trigger();
3591                  }
3592                  break;
3593              case 'moveleft':
3594              case 'moveright':
3595                  require_capability('moodle/course:manageactivities', $modcontext);
3596                  $indent = $cm->indent + (($action === 'moveright') ? 1 : -1);
3597                  if ($cm->indent >= 0) {
3598                      $DB->update_record('course_modules', array('id' => $cm->id, 'indent' => $indent));
3599                      rebuild_course_cache($cm->course);
3600                  }
3601                  break;
3602              case 'delete':
3603                  require_capability('moodle/course:manageactivities', $modcontext);
3604                  course_delete_module($cm->id, true);
3605                  return '';
3606              default:
3607                  throw new coding_exception('Unrecognised action');
3608          }
3609  
3610          $cm = get_fast_modinfo($course)->get_cm($id);
3611          return $courserenderer->course_section_cm_list_item($course, $completioninfo, $cm, $sectionreturn);
3612      }
3613  
3614      /**
3615       * Return structure for edit_module()
3616       *
3617       * @since Moodle 3.3
3618       * @return external_description
3619       */
3620      public static function edit_module_returns() {
3621          return new external_value(PARAM_RAW, 'html to replace the current module with');
3622      }
3623  
3624      /**
3625       * Parameters for function get_module()
3626       *
3627       * @since Moodle 3.3
3628       * @return external_function_parameters
3629       */
3630      public static function get_module_parameters() {
3631          return new external_function_parameters(
3632              array(
3633                  'id' => new external_value(PARAM_INT, 'course module id', VALUE_REQUIRED),
3634                  'sectionreturn' => new external_value(PARAM_INT, 'section to return to', VALUE_DEFAULT, null),
3635              ));
3636      }
3637  
3638      /**
3639       * Returns html for displaying one activity module on course page
3640       *
3641       * @since Moodle 3.3
3642       * @param int $id
3643       * @param null|int $sectionreturn
3644       * @return string
3645       */
3646      public static function get_module($id, $sectionreturn = null) {
3647          global $PAGE;
3648          // Validate and normalize parameters.
3649          $params = self::validate_parameters(self::get_module_parameters(),
3650              array('id' => $id, 'sectionreturn' => $sectionreturn));
3651          $id = $params['id'];
3652          $sectionreturn = $params['sectionreturn'];
3653  
3654          // Set of permissions an editing user may have.
3655          $contextarray = [
3656              'moodle/course:update',
3657              'moodle/course:manageactivities',
3658              'moodle/course:activityvisibility',
3659              'moodle/course:sectionvisibility',
3660              'moodle/course:movesections',
3661              'moodle/course:setcurrentsection',
3662          ];
3663          $PAGE->set_other_editing_capability($contextarray);
3664  
3665          // Validate access to the course (note, this is html for the course view page, we don't validate access to the module).
3666          list($course, $cm) = get_course_and_cm_from_cmid($id);
3667          self::validate_context(context_course::instance($course->id));
3668  
3669          $courserenderer = $PAGE->get_renderer('core', 'course');
3670          $completioninfo = new completion_info($course);
3671          return $courserenderer->course_section_cm_list_item($course, $completioninfo, $cm, $sectionreturn);
3672      }
3673  
3674      /**
3675       * Return structure for get_module()
3676       *
3677       * @since Moodle 3.3
3678       * @return external_description
3679       */
3680      public static function get_module_returns() {
3681          return new external_value(PARAM_RAW, 'html to replace the current module with');
3682      }
3683  
3684      /**
3685       * Parameters for function edit_section()
3686       *
3687       * @since Moodle 3.3
3688       * @return external_function_parameters
3689       */
3690      public static function edit_section_parameters() {
3691          return new external_function_parameters(
3692              array(
3693                  'action' => new external_value(PARAM_ALPHA, 'action: hide, show, stealth, setmarker, removemarker', VALUE_REQUIRED),
3694                  'id' => new external_value(PARAM_INT, 'course section id', VALUE_REQUIRED),
3695                  'sectionreturn' => new external_value(PARAM_INT, 'section to return to', VALUE_DEFAULT, null),
3696              ));
3697      }
3698  
3699      /**
3700       * Performs one of the edit section actions
3701       *
3702       * @since Moodle 3.3
3703       * @param string $action
3704       * @param int $id section id
3705       * @param int $sectionreturn section to return to
3706       * @return string
3707       */
3708      public static function edit_section($action, $id, $sectionreturn) {
3709          global $DB;
3710          // Validate and normalize parameters.
3711          $params = self::validate_parameters(self::edit_section_parameters(),
3712              array('action' => $action, 'id' => $id, 'sectionreturn' => $sectionreturn));
3713          $action = $params['action'];
3714          $id = $params['id'];
3715          $sr = $params['sectionreturn'];
3716  
3717          $section = $DB->get_record('course_sections', array('id' => $id), '*', MUST_EXIST);
3718          $coursecontext = context_course::instance($section->course);
3719          self::validate_context($coursecontext);
3720  
3721          $rv = course_get_format($section->course)->section_action($section, $action, $sectionreturn);
3722          if ($rv) {
3723              return json_encode($rv);
3724          } else {
3725              return null;
3726          }
3727      }
3728  
3729      /**
3730       * Return structure for edit_section()
3731       *
3732       * @since Moodle 3.3
3733       * @return external_description
3734       */
3735      public static function edit_section_returns() {
3736          return new external_value(PARAM_RAW, 'Additional data for javascript (JSON-encoded string)');
3737      }
3738  
3739      /**
3740       * Returns description of method parameters
3741       *
3742       * @return external_function_parameters
3743       */
3744      public static function get_enrolled_courses_by_timeline_classification_parameters() {
3745          return new external_function_parameters(
3746              array(
3747                  'classification' => new external_value(PARAM_ALPHA, 'future, inprogress, or past'),
3748                  'limit' => new external_value(PARAM_INT, 'Result set limit', VALUE_DEFAULT, 0),
3749                  'offset' => new external_value(PARAM_INT, 'Result set offset', VALUE_DEFAULT, 0),
3750                  'sort' => new external_value(PARAM_TEXT, 'Sort string', VALUE_DEFAULT, null),
3751                  'customfieldname' => new external_value(PARAM_ALPHANUMEXT, 'Used when classification = customfield',
3752                      VALUE_DEFAULT, null),
3753                  'customfieldvalue' => new external_value(PARAM_RAW, 'Used when classification = customfield',
3754                      VALUE_DEFAULT, null),
3755              )
3756          );
3757      }
3758  
3759      /**
3760       * Get courses matching the given timeline classification.
3761       *
3762       * NOTE: The offset applies to the unfiltered full set of courses before the classification
3763       * filtering is done.
3764       * E.g.
3765       * If the user is enrolled in 5 courses:
3766       * c1, c2, c3, c4, and c5
3767       * And c4 and c5 are 'future' courses
3768       *
3769       * If a request comes in for future courses with an offset of 1 it will mean that
3770       * c1 is skipped (because the offset applies *before* the classification filtering)
3771       * and c4 and c5 will be return.
3772       *
3773       * @param  string $classification past, inprogress, or future
3774       * @param  int $limit Result set limit
3775       * @param  int $offset Offset the full course set before timeline classification is applied
3776       * @param  string $sort SQL sort string for results
3777       * @param  string $customfieldname
3778       * @param  string $customfieldvalue
3779       * @return array list of courses and warnings
3780       * @throws  invalid_parameter_exception
3781       */
3782      public static function get_enrolled_courses_by_timeline_classification(
3783          string $classification,
3784          int $limit = 0,
3785          int $offset = 0,
3786          string $sort = null,
3787          string $customfieldname = null,
3788          string $customfieldvalue = null
3789      ) {
3790          global $CFG, $PAGE, $USER;
3791          require_once($CFG->dirroot . '/course/lib.php');
3792  
3793          $params = self::validate_parameters(self::get_enrolled_courses_by_timeline_classification_parameters(),
3794              array(
3795                  'classification' => $classification,
3796                  'limit' => $limit,
3797                  'offset' => $offset,
3798                  'sort' => $sort,
3799                  'customfieldvalue' => $customfieldvalue,
3800              )
3801          );
3802  
3803          $classification = $params['classification'];
3804          $limit = $params['limit'];
3805          $offset = $params['offset'];
3806          $sort = $params['sort'];
3807          $customfieldvalue = $params['customfieldvalue'];
3808  
3809          switch($classification) {
3810              case COURSE_TIMELINE_ALLINCLUDINGHIDDEN:
3811                  break;
3812              case COURSE_TIMELINE_ALL:
3813                  break;
3814              case COURSE_TIMELINE_PAST:
3815                  break;
3816              case COURSE_TIMELINE_INPROGRESS:
3817                  break;
3818              case COURSE_TIMELINE_FUTURE:
3819                  break;
3820              case COURSE_FAVOURITES:
3821                  break;
3822              case COURSE_TIMELINE_HIDDEN:
3823                  break;
3824              case COURSE_CUSTOMFIELD:
3825                  break;
3826              default:
3827                  throw new invalid_parameter_exception('Invalid classification');
3828          }
3829  
3830          self::validate_context(context_user::instance($USER->id));
3831  
3832          $requiredproperties = course_summary_exporter::define_properties();
3833          $fields = join(',', array_keys($requiredproperties));
3834          $hiddencourses = get_hidden_courses_on_timeline();
3835          $courses = [];
3836  
3837          // If the timeline requires really all courses, get really all courses.
3838          if ($classification == COURSE_TIMELINE_ALLINCLUDINGHIDDEN) {
3839              $courses = course_get_enrolled_courses_for_logged_in_user(0, $offset, $sort, $fields, COURSE_DB_QUERY_LIMIT);
3840  
3841              // Otherwise if the timeline requires the hidden courses then restrict the result to only $hiddencourses.
3842          } else if ($classification == COURSE_TIMELINE_HIDDEN) {
3843              $courses = course_get_enrolled_courses_for_logged_in_user(0, $offset, $sort, $fields,
3844                  COURSE_DB_QUERY_LIMIT, $hiddencourses);
3845  
3846              // Otherwise get the requested courses and exclude the hidden courses.
3847          } else {
3848              $courses = course_get_enrolled_courses_for_logged_in_user(0, $offset, $sort, $fields,
3849                  COURSE_DB_QUERY_LIMIT, [], $hiddencourses);
3850          }
3851  
3852          $favouritecourseids = [];
3853          $ufservice = \core_favourites\service_factory::get_service_for_user_context(\context_user::instance($USER->id));
3854          $favourites = $ufservice->find_favourites_by_type('core_course', 'courses');
3855  
3856          if ($favourites) {
3857              $favouritecourseids = array_map(
3858                  function($favourite) {
3859                      return $favourite->itemid;
3860                  }, $favourites);
3861          }
3862  
3863          if ($classification == COURSE_FAVOURITES) {
3864              list($filteredcourses, $processedcount) = course_filter_courses_by_favourites(
3865                  $courses,
3866                  $favouritecourseids,
3867                  $limit
3868              );
3869          } else if ($classification == COURSE_CUSTOMFIELD) {
3870              list($filteredcourses, $processedcount) = course_filter_courses_by_customfield(
3871                  $courses,
3872                  $customfieldname,
3873                  $customfieldvalue,
3874                  $limit
3875              );
3876          } else {
3877              list($filteredcourses, $processedcount) = course_filter_courses_by_timeline_classification(
3878                  $courses,
3879                  $classification,
3880                  $limit
3881              );
3882          }
3883  
3884          $renderer = $PAGE->get_renderer('core');
3885          $formattedcourses = array_map(function($course) use ($renderer, $favouritecourseids) {
3886              context_helper::preload_from_record($course);
3887              $context = context_course::instance($course->id);
3888              $isfavourite = false;
3889              if (in_array($course->id, $favouritecourseids)) {
3890                  $isfavourite = true;
3891              }
3892              $exporter = new course_summary_exporter($course, ['context' => $context, 'isfavourite' => $isfavourite]);
3893              return $exporter->export($renderer);
3894          }, $filteredcourses);
3895  
3896          return [
3897              'courses' => $formattedcourses,
3898              'nextoffset' => $offset + $processedcount
3899          ];
3900      }
3901  
3902      /**
3903       * Returns description of method result value
3904       *
3905       * @return external_description
3906       */
3907      public static function get_enrolled_courses_by_timeline_classification_returns() {
3908          return new external_single_structure(
3909              array(
3910                  'courses' => new external_multiple_structure(course_summary_exporter::get_read_structure(), 'Course'),
3911                  'nextoffset' => new external_value(PARAM_INT, 'Offset for the next request')
3912              )
3913          );
3914      }
3915  
3916      /**
3917       * Returns description of method parameters
3918       *
3919       * @return external_function_parameters
3920       */
3921      public static function set_favourite_courses_parameters() {
3922          return new external_function_parameters(
3923              array(
3924                  'courses' => new external_multiple_structure(
3925                      new external_single_structure(
3926                          array(
3927                              'id' => new external_value(PARAM_INT, 'course ID'),
3928                              'favourite' => new external_value(PARAM_BOOL, 'favourite status')
3929                          )
3930                      )
3931                  )
3932              )
3933          );
3934      }
3935  
3936      /**
3937       * Set the course favourite status for an array of courses.
3938       *
3939       * @param  array $courses List with course id's and favourite status.
3940       * @return array Array with an array of favourite courses.
3941       */
3942      public static function set_favourite_courses(
3943          array $courses
3944      ) {
3945          global $USER;
3946  
3947          $params = self::validate_parameters(self::set_favourite_courses_parameters(),
3948              array(
3949                  'courses' => $courses
3950              )
3951          );
3952  
3953          $warnings = [];
3954  
3955          $ufservice = \core_favourites\service_factory::get_service_for_user_context(\context_user::instance($USER->id));
3956  
3957          foreach ($params['courses'] as $course) {
3958  
3959              $warning = [];
3960  
3961              $favouriteexists = $ufservice->favourite_exists('core_course', 'courses', $course['id'],
3962                      \context_course::instance($course['id']));
3963  
3964              if ($course['favourite']) {
3965                  if (!$favouriteexists) {
3966                      try {
3967                          $ufservice->create_favourite('core_course', 'courses', $course['id'],
3968                                  \context_course::instance($course['id']));
3969                      } catch (Exception $e) {
3970                          $warning['courseid'] = $course['id'];
3971                          if ($e instanceof moodle_exception) {
3972                              $warning['warningcode'] = $e->errorcode;
3973                          } else {
3974                              $warning['warningcode'] = $e->getCode();
3975                          }
3976                          $warning['message'] = $e->getMessage();
3977                          $warnings[] = $warning;
3978                          $warnings[] = $warning;
3979                      }
3980                  } else {
3981                      $warning['courseid'] = $course['id'];
3982                      $warning['warningcode'] = 'coursealreadyfavourited';
3983                      $warning['message'] = 'Course already favourited';
3984                      $warnings[] = $warning;
3985                  }
3986              } else {
3987                  if ($favouriteexists) {
3988                      try {
3989                          $ufservice->delete_favourite('core_course', 'courses', $course['id'],
3990                                  \context_course::instance($course['id']));
3991                      } catch (Exception $e) {
3992                          $warning['courseid'] = $course['id'];
3993                          if ($e instanceof moodle_exception) {
3994                              $warning['warningcode'] = $e->errorcode;
3995                          } else {
3996                              $warning['warningcode'] = $e->getCode();
3997                          }
3998                          $warning['message'] = $e->getMessage();
3999                          $warnings[] = $warning;
4000                          $warnings[] = $warning;
4001                      }
4002                  } else {
4003                      $warning['courseid'] = $course['id'];
4004                      $warning['warningcode'] = 'cannotdeletefavourite';
4005                      $warning['message'] = 'Could not delete favourite status for course';
4006                      $warnings[] = $warning;
4007                  }
4008              }
4009          }
4010  
4011          return [
4012              'warnings' => $warnings
4013          ];
4014      }
4015  
4016      /**
4017       * Returns description of method result value
4018       *
4019       * @return external_description
4020       */
4021      public static function set_favourite_courses_returns() {
4022          return new external_single_structure(
4023              array(
4024                  'warnings' => new external_warnings()
4025              )
4026          );
4027      }
4028  
4029      /**
4030       * Returns description of method parameters
4031       *
4032       * @return external_function_parameters
4033       * @since Moodle 3.6
4034       */
4035      public static function get_recent_courses_parameters() {
4036          return new external_function_parameters(
4037              array(
4038                  'userid' => new external_value(PARAM_INT, 'id of the user, default to current user', VALUE_DEFAULT, 0),
4039                  'limit' => new external_value(PARAM_INT, 'result set limit', VALUE_DEFAULT, 0),
4040                  'offset' => new external_value(PARAM_INT, 'Result set offset', VALUE_DEFAULT, 0),
4041                  'sort' => new external_value(PARAM_TEXT, 'Sort string', VALUE_DEFAULT, null)
4042              )
4043          );
4044      }
4045  
4046      /**
4047       * Get last accessed courses adding additional course information like images.
4048       *
4049       * @param int $userid User id from which the courses will be obtained
4050       * @param int $limit Restrict result set to this amount
4051       * @param int $offset Skip this number of records from the start of the result set
4052       * @param string|null $sort SQL string for sorting
4053       * @return array List of courses
4054       * @throws  invalid_parameter_exception
4055       */
4056      public static function get_recent_courses(int $userid = 0, int $limit = 0, int $offset = 0, string $sort = null) {
4057          global $USER, $PAGE;
4058  
4059          if (empty($userid)) {
4060              $userid = $USER->id;
4061          }
4062  
4063          $params = self::validate_parameters(self::get_recent_courses_parameters(),
4064              array(
4065                  'userid' => $userid,
4066                  'limit' => $limit,
4067                  'offset' => $offset,
4068                  'sort' => $sort
4069              )
4070          );
4071  
4072          $userid = $params['userid'];
4073          $limit = $params['limit'];
4074          $offset = $params['offset'];
4075          $sort = $params['sort'];
4076  
4077          $usercontext = context_user::instance($userid);
4078  
4079          self::validate_context($usercontext);
4080  
4081          if ($userid != $USER->id and !has_capability('moodle/user:viewdetails', $usercontext)) {
4082              return array();
4083          }
4084  
4085          $courses = course_get_recent_courses($userid, $limit, $offset, $sort);
4086  
4087          $renderer = $PAGE->get_renderer('core');
4088  
4089          $recentcourses = array_map(function($course) use ($renderer) {
4090              context_helper::preload_from_record($course);
4091              $context = context_course::instance($course->id);
4092              $isfavourite = !empty($course->component);
4093              $exporter = new course_summary_exporter($course, ['context' => $context, 'isfavourite' => $isfavourite]);
4094              return $exporter->export($renderer);
4095          }, $courses);
4096  
4097          return $recentcourses;
4098      }
4099  
4100      /**
4101       * Returns description of method result value
4102       *
4103       * @return external_description
4104       * @since Moodle 3.6
4105       */
4106      public static function get_recent_courses_returns() {
4107          return new external_multiple_structure(course_summary_exporter::get_read_structure(), 'Courses');
4108      }
4109  
4110      /**
4111       * Returns description of method parameters
4112       *
4113       * @return external_function_parameters
4114       */
4115      public static function get_enrolled_users_by_cmid_parameters() {
4116          return new external_function_parameters([
4117              'cmid' => new external_value(PARAM_INT, 'id of the course module', VALUE_REQUIRED),
4118              'groupid' => new external_value(PARAM_INT, 'id of the group', VALUE_DEFAULT, 0),
4119              'onlyactive' => new external_value(PARAM_BOOL, 'whether to return only active users or all.',
4120                  VALUE_DEFAULT, false),
4121          ]);
4122      }
4123  
4124      /**
4125       * Get all users in a course for a given cmid.
4126       *
4127       * @param int $cmid Course Module id from which the users will be obtained
4128       * @param int $groupid Group id from which the users will be obtained
4129       * @param bool $onlyactive Whether to return only the active enrolled users or all enrolled users in the course.
4130       * @return array List of users
4131       * @throws invalid_parameter_exception
4132       */
4133      public static function get_enrolled_users_by_cmid(int $cmid, int $groupid = 0, bool $onlyactive = false) {
4134      global $PAGE;
4135          $warnings = [];
4136  
4137          self::validate_parameters(self::get_enrolled_users_by_cmid_parameters(), [
4138                  'cmid' => $cmid,
4139                  'groupid' => $groupid,
4140                  'onlyactive' => $onlyactive,
4141          ]);
4142  
4143          list($course, $cm) = get_course_and_cm_from_cmid($cmid);
4144          $coursecontext = context_course::instance($course->id);
4145          self::validate_context($coursecontext);
4146  
4147          $enrolledusers = get_enrolled_users($coursecontext, '', $groupid, 'u.*', null, 0, 0, $onlyactive);
4148  
4149          $users = array_map(function ($user) use ($PAGE) {
4150              $user->fullname = fullname($user);
4151              $userpicture = new user_picture($user);
4152              $userpicture->size = 1;
4153              $user->profileimage = $userpicture->get_url($PAGE)->out(false);
4154              return $user;
4155          }, $enrolledusers);
4156          sort($users);
4157  
4158          return [
4159              'users' => $users,
4160              'warnings' => $warnings,
4161          ];
4162      }
4163  
4164      /**
4165       * Returns description of method result value
4166       *
4167       * @return external_description
4168       */
4169      public static function get_enrolled_users_by_cmid_returns() {
4170          return new external_single_structure([
4171              'users' => new external_multiple_structure(self::user_description()),
4172              'warnings' => new external_warnings(),
4173          ]);
4174      }
4175  
4176      /**
4177       * Create user return value description.
4178       *
4179       * @return external_description
4180       */
4181      public static function user_description() {
4182          $userfields = array(
4183              'id'    => new external_value(core_user::get_property_type('id'), 'ID of the user'),
4184              'profileimage' => new external_value(PARAM_URL, 'The location of the users larger image', VALUE_OPTIONAL),
4185              'fullname' => new external_value(PARAM_TEXT, 'The full name of the user', VALUE_OPTIONAL),
4186              'firstname'   => new external_value(
4187                      core_user::get_property_type('firstname'),
4188                          'The first name(s) of the user',
4189                          VALUE_OPTIONAL),
4190              'lastname'    => new external_value(
4191                      core_user::get_property_type('lastname'),
4192                          'The family name of the user',
4193                          VALUE_OPTIONAL),
4194          );
4195          return new external_single_structure($userfields);
4196      }
4197  
4198      /**
4199       * Returns description of method parameters.
4200       *
4201       * @return external_function_parameters
4202       */
4203      public static function add_content_item_to_user_favourites_parameters() {
4204          return new external_function_parameters([
4205              'componentname' => new external_value(PARAM_TEXT,
4206                  'frankenstyle name of the component to which the content item belongs', VALUE_REQUIRED),
4207              'contentitemid' => new external_value(PARAM_INT, 'id of the content item', VALUE_REQUIRED, '', NULL_NOT_ALLOWED)
4208          ]);
4209      }
4210  
4211      /**
4212       * Add a content item to a user's favourites.
4213       *
4214       * @param string $componentname the name of the component from which this content item originates.
4215       * @param int $contentitemid the id of the content item.
4216       * @return stdClass the exporter content item.
4217       */
4218      public static function add_content_item_to_user_favourites(string $componentname, int $contentitemid) {
4219          global $USER;
4220  
4221          [
4222              'componentname' => $componentname,
4223              'contentitemid' => $contentitemid,
4224          ] = self::validate_parameters(self::add_content_item_to_user_favourites_parameters(),
4225              [
4226                  'componentname' => $componentname,
4227                  'contentitemid' => $contentitemid,
4228              ]
4229          );
4230  
4231          self::validate_context(context_user::instance($USER->id));
4232  
4233          $contentitemservice = \core_course\local\factory\content_item_service_factory::get_content_item_service();
4234  
4235          return $contentitemservice->add_to_user_favourites($USER, $componentname, $contentitemid);
4236      }
4237  
4238      /**
4239       * Returns description of method result value.
4240       *
4241       * @return external_description
4242       */
4243      public static function add_content_item_to_user_favourites_returns() {
4244          return \core_course\local\exporters\course_content_item_exporter::get_read_structure();
4245      }
4246  
4247      /**
4248       * Returns description of method parameters.
4249       *
4250       * @return external_function_parameters
4251       */
4252      public static function remove_content_item_from_user_favourites_parameters() {
4253          return new external_function_parameters([
4254              'componentname' => new external_value(PARAM_TEXT,
4255                  'frankenstyle name of the component to which the content item belongs', VALUE_REQUIRED),
4256              'contentitemid' => new external_value(PARAM_INT, 'id of the content item', VALUE_REQUIRED, '', NULL_NOT_ALLOWED),
4257          ]);
4258      }
4259  
4260      /**
4261       * Remove a content item from a user's favourites.
4262       *
4263       * @param string $componentname the name of the component from which this content item originates.
4264       * @param int $contentitemid the id of the content item.
4265       * @return stdClass the exported content item.
4266       */
4267      public static function remove_content_item_from_user_favourites(string $componentname, int $contentitemid) {
4268          global $USER;
4269  
4270          [
4271              'componentname' => $componentname,
4272              'contentitemid' => $contentitemid,
4273          ] = self::validate_parameters(self::remove_content_item_from_user_favourites_parameters(),
4274              [
4275                  'componentname' => $componentname,
4276                  'contentitemid' => $contentitemid,
4277              ]
4278          );
4279  
4280          self::validate_context(context_user::instance($USER->id));
4281  
4282          $contentitemservice = \core_course\local\factory\content_item_service_factory::get_content_item_service();
4283  
4284          return $contentitemservice->remove_from_user_favourites($USER, $componentname, $contentitemid);
4285      }
4286  
4287      /**
4288       * Returns description of method result value.
4289       *
4290       * @return external_description
4291       */
4292      public static function remove_content_item_from_user_favourites_returns() {
4293          return \core_course\local\exporters\course_content_item_exporter::get_read_structure();
4294      }
4295  
4296      /**
4297       * Returns description of method result value
4298       *
4299       * @return external_description
4300       */
4301      public static function get_course_content_items_returns() {
4302          return new external_single_structure([
4303              'content_items' => new external_multiple_structure(
4304                  \core_course\local\exporters\course_content_item_exporter::get_read_structure()
4305              ),
4306          ]);
4307      }
4308  
4309      /**
4310       * Returns description of method parameters
4311       *
4312       * @return external_function_parameters
4313       */
4314      public static function get_course_content_items_parameters() {
4315          return new external_function_parameters([
4316              'courseid' => new external_value(PARAM_INT, 'ID of the course', VALUE_REQUIRED),
4317          ]);
4318      }
4319  
4320      /**
4321       * Given a course ID fetch all accessible modules for that course
4322       *
4323       * @param int $courseid The course we want to fetch the modules for
4324       * @return array Contains array of modules and their metadata
4325       */
4326      public static function get_course_content_items(int $courseid) {
4327          global $USER;
4328  
4329          [
4330              'courseid' => $courseid,
4331          ] = self::validate_parameters(self::get_course_content_items_parameters(), [
4332              'courseid' => $courseid,
4333          ]);
4334  
4335          $coursecontext = context_course::instance($courseid);
4336          self::validate_context($coursecontext);
4337          $course = get_course($courseid);
4338  
4339          $contentitemservice = \core_course\local\factory\content_item_service_factory::get_content_item_service();
4340  
4341          $contentitems = $contentitemservice->get_content_items_for_user_in_course($USER, $course);
4342          return ['content_items' => $contentitems];
4343      }
4344  
4345      /**
4346       * Returns description of method parameters.
4347       *
4348       * @return external_function_parameters
4349       */
4350      public static function toggle_activity_recommendation_parameters() {
4351          return new external_function_parameters([
4352              'area' => new external_value(PARAM_TEXT, 'The favourite area (itemtype)', VALUE_REQUIRED),
4353              'id' => new external_value(PARAM_INT, 'id of the activity or whatever', VALUE_REQUIRED),
4354          ]);
4355      }
4356  
4357      /**
4358       * Update the recommendation for an activity item.
4359       *
4360       * @param  string $area identifier for this activity.
4361       * @param  int $id Associated id. This is needed in conjunction with the area to find the recommendation.
4362       * @return array some warnings or something.
4363       */
4364      public static function toggle_activity_recommendation(string $area, int $id): array {
4365          ['area' => $area, 'id' => $id] = self::validate_parameters(self::toggle_activity_recommendation_parameters(),
4366                  ['area' => $area, 'id' => $id]);
4367  
4368          $context = context_system::instance();
4369          self::validate_context($context);
4370  
4371          require_capability('moodle/course:recommendactivity', $context);
4372  
4373          $manager = \core_course\local\factory\content_item_service_factory::get_content_item_service();
4374  
4375          $status = $manager->toggle_recommendation($area, $id);
4376          return ['id' => $id, 'area' => $area, 'status' => $status];
4377      }
4378  
4379      /**
4380       * Returns warnings.
4381       *
4382       * @return external_description
4383       */
4384      public static function toggle_activity_recommendation_returns() {
4385          return new external_single_structure(
4386              [
4387                  'id' => new external_value(PARAM_INT, 'id of the activity or whatever'),
4388                  'area' => new external_value(PARAM_TEXT, 'The favourite area (itemtype)'),
4389                  'status' => new external_value(PARAM_BOOL, 'If created or deleted'),
4390              ]
4391          );
4392      }
4393  
4394      /**
4395       * Returns description of method parameters
4396       *
4397       * @return external_function_parameters
4398       */
4399      public static function get_activity_chooser_footer_parameters() {
4400          return new external_function_parameters([
4401              'courseid' => new external_value(PARAM_INT, 'ID of the course', VALUE_REQUIRED),
4402              'sectionid' => new external_value(PARAM_INT, 'ID of the section', VALUE_REQUIRED),
4403          ]);
4404      }
4405  
4406      /**
4407       * Given a course ID we need to build up a footre for the chooser.
4408       *
4409       * @param int $courseid The course we want to fetch the modules for
4410       * @param int $sectionid The section we want to fetch the modules for
4411       * @return array
4412       */
4413      public static function get_activity_chooser_footer(int $courseid, int $sectionid) {
4414          [
4415              'courseid' => $courseid,
4416              'sectionid' => $sectionid,
4417          ] = self::validate_parameters(self::get_activity_chooser_footer_parameters(), [
4418              'courseid' => $courseid,
4419              'sectionid' => $sectionid,
4420          ]);
4421  
4422          $coursecontext = context_course::instance($courseid);
4423          self::validate_context($coursecontext);
4424  
4425          $activeplugin = get_config('core', 'activitychooseractivefooter');
4426  
4427          if ($activeplugin !== COURSE_CHOOSER_FOOTER_NONE) {
4428              $footerdata = component_callback($activeplugin, 'custom_chooser_footer', [$courseid, $sectionid]);
4429              return [
4430                  'footer' => true,
4431                  'customfooterjs' => $footerdata->get_footer_js_file(),
4432                  'customfootertemplate' => $footerdata->get_footer_template(),
4433                  'customcarouseltemplate' => $footerdata->get_carousel_template(),
4434              ];
4435          } else {
4436              return [
4437                  'footer' => false,
4438              ];
4439          }
4440      }
4441  
4442      /**
4443       * Returns description of method result value
4444       *
4445       * @return external_description
4446       */
4447      public static function get_activity_chooser_footer_returns() {
4448          return new external_single_structure(
4449              [
4450                  'footer' => new external_value(PARAM_BOOL, 'Is a footer being return by this request?', VALUE_REQUIRED),
4451                  'customfooterjs' => new external_value(PARAM_RAW, 'The path to the plugin JS file', VALUE_OPTIONAL),
4452                  'customfootertemplate' => new external_value(PARAM_RAW, 'The prerendered footer', VALUE_OPTIONAL),
4453                  'customcarouseltemplate' => new external_value(PARAM_RAW, 'Either "" or the prerendered carousel page',
4454                      VALUE_OPTIONAL),
4455              ]
4456          );
4457      }
4458  }