Search moodle.org's
Developer Documentation

See Release Notes

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

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

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