Search moodle.org's
Developer Documentation

  • 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 37 and 311] [Versions 38 and 311] [Versions 39 and 311]

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