Search moodle.org's
Developer Documentation

See Release Notes

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

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

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * File containing the course class.
  19   *
  20   * @package    tool_uploadcourse
  21   * @copyright  2013 Frédéric Massart
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  defined('MOODLE_INTERNAL') || die();
  26  require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
  27  require_once($CFG->dirroot . '/course/lib.php');
  28  
  29  /**
  30   * Course class.
  31   *
  32   * @package    tool_uploadcourse
  33   * @copyright  2013 Frédéric Massart
  34   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35   */
  36  class tool_uploadcourse_course {
  37  
  38      /** Outcome of the process: creating the course */
  39      const DO_CREATE = 1;
  40  
  41      /** Outcome of the process: updating the course */
  42      const DO_UPDATE = 2;
  43  
  44      /** Outcome of the process: deleting the course */
  45      const DO_DELETE = 3;
  46  
  47      /** @var array assignable roles. */
  48      protected $assignableroles = [];
  49  
  50      /** @var array Roles context levels. */
  51      protected $contextlevels = [];
  52  
  53      /** @var array final import data. */
  54      protected $data = array();
  55  
  56      /** @var array default values. */
  57      protected $defaults = array();
  58  
  59      /** @var array enrolment data. */
  60      protected $enrolmentdata;
  61  
  62      /** @var array errors. */
  63      protected $errors = array();
  64  
  65      /** @var int the ID of the course that had been processed. */
  66      protected $id;
  67  
  68      /** @var array containing options passed from the processor. */
  69      protected $importoptions = array();
  70  
  71      /** @var int import mode. Matches tool_uploadcourse_processor::MODE_* */
  72      protected $mode;
  73  
  74      /** @var array course import options. */
  75      protected $options = array();
  76  
  77      /** @var int constant value of self::DO_*, what to do with that course */
  78      protected $do;
  79  
  80      /** @var bool set to true once we have prepared the course */
  81      protected $prepared = false;
  82  
  83      /** @var bool set to true once we have started the process of the course */
  84      protected $processstarted = false;
  85  
  86      /** @var array course import data. */
  87      protected $rawdata = array();
  88  
  89      /** @var array restore directory. */
  90      protected $restoredata;
  91  
  92      /** @var string course shortname. */
  93      protected $shortname;
  94  
  95      /** @var array errors. */
  96      protected $statuses = array();
  97  
  98      /** @var int update mode. Matches tool_uploadcourse_processor::UPDATE_* */
  99      protected $updatemode;
 100  
 101      /** @var array fields allowed as course data. */
 102      static protected $validfields = array('fullname', 'shortname', 'idnumber', 'category', 'visible', 'startdate', 'enddate',
 103          'summary', 'format', 'theme', 'lang', 'newsitems', 'showgrades', 'showreports', 'legacyfiles', 'maxbytes',
 104          'groupmode', 'groupmodeforce', 'enablecompletion', 'downloadcontent', 'showactivitydates');
 105  
 106      /** @var array fields required on course creation. */
 107      static protected $mandatoryfields = array('fullname', 'category');
 108  
 109      /** @var array fields which are considered as options. */
 110      static protected $optionfields = array('delete' => false, 'rename' => null, 'backupfile' => null,
 111          'templatecourse' => null, 'reset' => false);
 112  
 113      /** @var array options determining what can or cannot be done at an import level. */
 114      static protected $importoptionsdefaults = array('canrename' => false, 'candelete' => false, 'canreset' => false,
 115          'reset' => false, 'restoredir' => null, 'shortnametemplate' => null);
 116  
 117      /**
 118       * Constructor
 119       *
 120       * @param int $mode import mode, constant matching tool_uploadcourse_processor::MODE_*
 121       * @param int $updatemode update mode, constant matching tool_uploadcourse_processor::UPDATE_*
 122       * @param array $rawdata raw course data.
 123       * @param array $defaults default course data.
 124       * @param array $importoptions import options.
 125       */
 126      public function __construct($mode, $updatemode, $rawdata, $defaults = array(), $importoptions = array()) {
 127  
 128          if ($mode !== tool_uploadcourse_processor::MODE_CREATE_NEW &&
 129                  $mode !== tool_uploadcourse_processor::MODE_CREATE_ALL &&
 130                  $mode !== tool_uploadcourse_processor::MODE_CREATE_OR_UPDATE &&
 131                  $mode !== tool_uploadcourse_processor::MODE_UPDATE_ONLY) {
 132              throw new coding_exception('Incorrect mode.');
 133          } else if ($updatemode !== tool_uploadcourse_processor::UPDATE_NOTHING &&
 134                  $updatemode !== tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY &&
 135                  $updatemode !== tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_OR_DEFAUTLS &&
 136                  $updatemode !== tool_uploadcourse_processor::UPDATE_MISSING_WITH_DATA_OR_DEFAUTLS) {
 137              throw new coding_exception('Incorrect update mode.');
 138          }
 139  
 140          $this->mode = $mode;
 141          $this->updatemode = $updatemode;
 142  
 143          if (isset($rawdata['shortname'])) {
 144              $this->shortname = $rawdata['shortname'];
 145          }
 146          $this->rawdata = $rawdata;
 147          $this->defaults = $defaults;
 148  
 149          // Extract course options.
 150          foreach (self::$optionfields as $option => $default) {
 151              $this->options[$option] = isset($rawdata[$option]) ? $rawdata[$option] : $default;
 152          }
 153  
 154          // Import options.
 155          foreach (self::$importoptionsdefaults as $option => $default) {
 156              $this->importoptions[$option] = isset($importoptions[$option]) ? $importoptions[$option] : $default;
 157          }
 158      }
 159  
 160      /**
 161       * Does the mode allow for course creation?
 162       *
 163       * @return bool
 164       */
 165      public function can_create() {
 166          return in_array($this->mode, array(tool_uploadcourse_processor::MODE_CREATE_ALL,
 167              tool_uploadcourse_processor::MODE_CREATE_NEW,
 168              tool_uploadcourse_processor::MODE_CREATE_OR_UPDATE)
 169          );
 170      }
 171  
 172      /**
 173       * Does the mode allow for course deletion?
 174       *
 175       * @return bool
 176       */
 177      public function can_delete() {
 178          return $this->importoptions['candelete'];
 179      }
 180  
 181      /**
 182       * Does the mode only allow for course creation?
 183       *
 184       * @return bool
 185       */
 186      public function can_only_create() {
 187          return in_array($this->mode, array(tool_uploadcourse_processor::MODE_CREATE_ALL,
 188              tool_uploadcourse_processor::MODE_CREATE_NEW));
 189      }
 190  
 191      /**
 192       * Does the mode allow for course rename?
 193       *
 194       * @return bool
 195       */
 196      public function can_rename() {
 197          return $this->importoptions['canrename'];
 198      }
 199  
 200      /**
 201       * Does the mode allow for course reset?
 202       *
 203       * @return bool
 204       */
 205      public function can_reset() {
 206          return $this->importoptions['canreset'];
 207      }
 208  
 209      /**
 210       * Does the mode allow for course update?
 211       *
 212       * @return bool
 213       */
 214      public function can_update() {
 215          return in_array($this->mode,
 216                  array(
 217                      tool_uploadcourse_processor::MODE_UPDATE_ONLY,
 218                      tool_uploadcourse_processor::MODE_CREATE_OR_UPDATE)
 219                  ) && $this->updatemode != tool_uploadcourse_processor::UPDATE_NOTHING;
 220      }
 221  
 222      /**
 223       * Can we use default values?
 224       *
 225       * @return bool
 226       */
 227      public function can_use_defaults() {
 228          return in_array($this->updatemode, array(tool_uploadcourse_processor::UPDATE_MISSING_WITH_DATA_OR_DEFAUTLS,
 229              tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_OR_DEFAUTLS));
 230      }
 231  
 232      /**
 233       * Delete the current course.
 234       *
 235       * @return bool
 236       */
 237      protected function delete() {
 238          global $DB;
 239          $this->id = $DB->get_field_select('course', 'id', 'shortname = :shortname',
 240              array('shortname' => $this->shortname), MUST_EXIST);
 241          return delete_course($this->id, false);
 242      }
 243  
 244      /**
 245       * Log an error
 246       *
 247       * @param string $code error code.
 248       * @param lang_string $message error message.
 249       * @return void
 250       */
 251      protected function error($code, lang_string $message) {
 252          if (array_key_exists($code, $this->errors)) {
 253              throw new coding_exception('Error code already defined');
 254          }
 255          $this->errors[$code] = $message;
 256      }
 257  
 258      /**
 259       * Return whether the course exists or not.
 260       *
 261       * @param string $shortname the shortname to use to check if the course exists. Falls back on $this->shortname if empty.
 262       * @return bool
 263       */
 264      protected function exists($shortname = null) {
 265          global $DB;
 266          if (is_null($shortname)) {
 267              $shortname = $this->shortname;
 268          }
 269          if (!empty($shortname) || is_numeric($shortname)) {
 270              return $DB->record_exists('course', array('shortname' => $shortname));
 271          }
 272          return false;
 273      }
 274  
 275      /**
 276       * Return the data that will be used upon saving.
 277       *
 278       * @return null|array
 279       */
 280      public function get_data() {
 281          return $this->data;
 282      }
 283  
 284      /**
 285       * Return the errors found during preparation.
 286       *
 287       * @return array
 288       */
 289      public function get_errors() {
 290          return $this->errors;
 291      }
 292  
 293      /**
 294       * Return array of valid fields for default values
 295       *
 296       * @return array
 297       */
 298      protected function get_valid_fields() {
 299          return array_merge(self::$validfields, \tool_uploadcourse_helper::get_custom_course_field_names());
 300      }
 301  
 302      /**
 303       * Assemble the course data based on defaults.
 304       *
 305       * This returns the final data to be passed to create_course().
 306       *
 307       * @param array $data current data.
 308       * @return array
 309       */
 310      protected function get_final_create_data($data) {
 311          foreach ($this->get_valid_fields() as $field) {
 312              if (!isset($data[$field]) && isset($this->defaults[$field])) {
 313                  $data[$field] = $this->defaults[$field];
 314              }
 315          }
 316          $data['shortname'] = $this->shortname;
 317          return $data;
 318      }
 319  
 320      /**
 321       * Assemble the course data based on defaults.
 322       *
 323       * This returns the final data to be passed to update_course().
 324       *
 325       * @param array $data current data.
 326       * @param bool $usedefaults are defaults allowed?
 327       * @param bool $missingonly ignore fields which are already set.
 328       * @return array
 329       */
 330      protected function get_final_update_data($data, $usedefaults = false, $missingonly = false) {
 331          global $DB;
 332          $newdata = array();
 333          $existingdata = $DB->get_record('course', array('shortname' => $this->shortname));
 334          foreach ($this->get_valid_fields() as $field) {
 335              if ($missingonly) {
 336                  if (isset($existingdata->$field) and $existingdata->$field !== '') {
 337                      continue;
 338                  }
 339              }
 340              if (isset($data[$field])) {
 341                  $newdata[$field] = $data[$field];
 342              } else if ($usedefaults && isset($this->defaults[$field])) {
 343                  $newdata[$field] = $this->defaults[$field];
 344              }
 345          }
 346          $newdata['id'] =  $existingdata->id;
 347          return $newdata;
 348      }
 349  
 350      /**
 351       * Return the ID of the processed course.
 352       *
 353       * @return int|null
 354       */
 355      public function get_id() {
 356          if (!$this->processstarted) {
 357              throw new coding_exception('The course has not been processed yet!');
 358          }
 359          return $this->id;
 360      }
 361  
 362      /**
 363       * Get the directory of the object to restore.
 364       *
 365       * @return string|false|null subdirectory in $CFG->backuptempdir/..., false when an error occured
 366       *                           and null when there is simply nothing.
 367       */
 368      protected function get_restore_content_dir() {
 369          $backupfile = null;
 370          $shortname = null;
 371  
 372          if (!empty($this->options['backupfile'])) {
 373              $backupfile = $this->options['backupfile'];
 374          } else if (!empty($this->options['templatecourse']) || is_numeric($this->options['templatecourse'])) {
 375              $shortname = $this->options['templatecourse'];
 376          }
 377  
 378          $errors = array();
 379          $dir = tool_uploadcourse_helper::get_restore_content_dir($backupfile, $shortname, $errors);
 380          if (!empty($errors)) {
 381              foreach ($errors as $key => $message) {
 382                  $this->error($key, $message);
 383              }
 384              return false;
 385          } else if ($dir === false) {
 386              // We want to return null when nothing was wrong, but nothing was found.
 387              $dir = null;
 388          }
 389  
 390          if (empty($dir) && !empty($this->importoptions['restoredir'])) {
 391              $dir = $this->importoptions['restoredir'];
 392          }
 393  
 394          return $dir;
 395      }
 396  
 397      /**
 398       * Return the errors found during preparation.
 399       *
 400       * @return array
 401       */
 402      public function get_statuses() {
 403          return $this->statuses;
 404      }
 405  
 406      /**
 407       * Return whether there were errors with this course.
 408       *
 409       * @return boolean
 410       */
 411      public function has_errors() {
 412          return !empty($this->errors);
 413      }
 414  
 415      /**
 416       * Validates and prepares the data.
 417       *
 418       * @return bool false is any error occured.
 419       */
 420      public function prepare() {
 421          global $DB, $SITE, $CFG;
 422  
 423          $this->prepared = true;
 424  
 425          // Validate the shortname.
 426          if (!empty($this->shortname) || is_numeric($this->shortname)) {
 427              if ($this->shortname !== clean_param($this->shortname, PARAM_TEXT)) {
 428                  $this->error('invalidshortname', new lang_string('invalidshortname', 'tool_uploadcourse'));
 429                  return false;
 430              }
 431  
 432              // Ensure we don't overflow the maximum length of the shortname field.
 433              if (core_text::strlen($this->shortname) > 255) {
 434                  $this->error('invalidshortnametoolong', new lang_string('invalidshortnametoolong', 'tool_uploadcourse', 255));
 435                  return false;
 436              }
 437          }
 438  
 439          $exists = $this->exists();
 440  
 441          // Do we want to delete the course?
 442          if ($this->options['delete']) {
 443              if (!$exists) {
 444                  $this->error('cannotdeletecoursenotexist', new lang_string('cannotdeletecoursenotexist', 'tool_uploadcourse'));
 445                  return false;
 446              } else if (!$this->can_delete()) {
 447                  $this->error('coursedeletionnotallowed', new lang_string('coursedeletionnotallowed', 'tool_uploadcourse'));
 448                  return false;
 449              }
 450  
 451              $this->do = self::DO_DELETE;
 452              return true;
 453          }
 454  
 455          // Can we create/update the course under those conditions?
 456          if ($exists) {
 457              if ($this->mode === tool_uploadcourse_processor::MODE_CREATE_NEW) {
 458                  $this->error('courseexistsanduploadnotallowed',
 459                      new lang_string('courseexistsanduploadnotallowed', 'tool_uploadcourse'));
 460                  return false;
 461              } else if ($this->can_update()) {
 462                  // We can never allow for any front page changes!
 463                  if ($this->shortname == $SITE->shortname) {
 464                      $this->error('cannotupdatefrontpage', new lang_string('cannotupdatefrontpage', 'tool_uploadcourse'));
 465                      return false;
 466                  }
 467              }
 468          } else {
 469              if (!$this->can_create()) {
 470                  $this->error('coursedoesnotexistandcreatenotallowed',
 471                      new lang_string('coursedoesnotexistandcreatenotallowed', 'tool_uploadcourse'));
 472                  return false;
 473              }
 474          }
 475  
 476          // Basic data.
 477          $coursedata = array();
 478          foreach ($this->rawdata as $field => $value) {
 479              if (!in_array($field, self::$validfields)) {
 480                  continue;
 481              } else if ($field == 'shortname') {
 482                  // Let's leave it apart from now, use $this->shortname only.
 483                  continue;
 484              }
 485              $coursedata[$field] = $value;
 486          }
 487  
 488          $mode = $this->mode;
 489          $updatemode = $this->updatemode;
 490          $usedefaults = $this->can_use_defaults();
 491  
 492          // Resolve the category, and fail if not found.
 493          $errors = array();
 494          $catid = tool_uploadcourse_helper::resolve_category($this->rawdata, $errors);
 495          if (empty($errors)) {
 496              $coursedata['category'] = $catid;
 497          } else {
 498              foreach ($errors as $key => $message) {
 499                  $this->error($key, $message);
 500              }
 501              return false;
 502          }
 503  
 504          // Ensure we don't overflow the maximum length of the fullname field.
 505          if (!empty($coursedata['fullname']) && core_text::strlen($coursedata['fullname']) > 254) {
 506              $this->error('invalidfullnametoolong', new lang_string('invalidfullnametoolong', 'tool_uploadcourse', 254));
 507              return false;
 508          }
 509  
 510          // If the course does not exist, or will be forced created.
 511          if (!$exists || $mode === tool_uploadcourse_processor::MODE_CREATE_ALL) {
 512  
 513              // Mandatory fields upon creation.
 514              $errors = array();
 515              foreach (self::$mandatoryfields as $field) {
 516                  if ((!isset($coursedata[$field]) || $coursedata[$field] === '') &&
 517                          (!isset($this->defaults[$field]) || $this->defaults[$field] === '')) {
 518                      $errors[] = $field;
 519                  }
 520              }
 521              if (!empty($errors)) {
 522                  $this->error('missingmandatoryfields', new lang_string('missingmandatoryfields', 'tool_uploadcourse',
 523                      implode(', ', $errors)));
 524                  return false;
 525              }
 526          }
 527  
 528          // Should the course be renamed?
 529          if (!empty($this->options['rename']) || is_numeric($this->options['rename'])) {
 530              if (!$this->can_update()) {
 531                  $this->error('canonlyrenameinupdatemode', new lang_string('canonlyrenameinupdatemode', 'tool_uploadcourse'));
 532                  return false;
 533              } else if (!$exists) {
 534                  $this->error('cannotrenamecoursenotexist', new lang_string('cannotrenamecoursenotexist', 'tool_uploadcourse'));
 535                  return false;
 536              } else if (!$this->can_rename()) {
 537                  $this->error('courserenamingnotallowed', new lang_string('courserenamingnotallowed', 'tool_uploadcourse'));
 538                  return false;
 539              } else if ($this->options['rename'] !== clean_param($this->options['rename'], PARAM_TEXT)) {
 540                  $this->error('invalidshortname', new lang_string('invalidshortname', 'tool_uploadcourse'));
 541                  return false;
 542              } else if ($this->exists($this->options['rename'])) {
 543                  $this->error('cannotrenameshortnamealreadyinuse',
 544                      new lang_string('cannotrenameshortnamealreadyinuse', 'tool_uploadcourse'));
 545                  return false;
 546              } else if (isset($coursedata['idnumber']) &&
 547                      $DB->count_records_select('course', 'idnumber = :idn AND shortname != :sn',
 548                      array('idn' => $coursedata['idnumber'], 'sn' => $this->shortname)) > 0) {
 549                  $this->error('cannotrenameidnumberconflict', new lang_string('cannotrenameidnumberconflict', 'tool_uploadcourse'));
 550                  return false;
 551              }
 552              $coursedata['shortname'] = $this->options['rename'];
 553              $this->status('courserenamed', new lang_string('courserenamed', 'tool_uploadcourse',
 554                  array('from' => $this->shortname, 'to' => $coursedata['shortname'])));
 555          }
 556  
 557          // Should we generate a shortname?
 558          if (empty($this->shortname) && !is_numeric($this->shortname)) {
 559              if (empty($this->importoptions['shortnametemplate'])) {
 560                  $this->error('missingshortnamenotemplate', new lang_string('missingshortnamenotemplate', 'tool_uploadcourse'));
 561                  return false;
 562              } else if (!$this->can_only_create()) {
 563                  $this->error('cannotgenerateshortnameupdatemode',
 564                      new lang_string('cannotgenerateshortnameupdatemode', 'tool_uploadcourse'));
 565                  return false;
 566              } else {
 567                  $newshortname = tool_uploadcourse_helper::generate_shortname($coursedata,
 568                      $this->importoptions['shortnametemplate']);
 569                  if (is_null($newshortname)) {
 570                      $this->error('generatedshortnameinvalid', new lang_string('generatedshortnameinvalid', 'tool_uploadcourse'));
 571                      return false;
 572                  } else if ($this->exists($newshortname)) {
 573                      if ($mode === tool_uploadcourse_processor::MODE_CREATE_NEW) {
 574                          $this->error('generatedshortnamealreadyinuse',
 575                              new lang_string('generatedshortnamealreadyinuse', 'tool_uploadcourse'));
 576                          return false;
 577                      }
 578                      $exists = true;
 579                  }
 580                  $this->status('courseshortnamegenerated', new lang_string('courseshortnamegenerated', 'tool_uploadcourse',
 581                      $newshortname));
 582                  $this->shortname = $newshortname;
 583              }
 584          }
 585  
 586          // If exists, but we only want to create courses, increment the shortname.
 587          if ($exists && $mode === tool_uploadcourse_processor::MODE_CREATE_ALL) {
 588              $original = $this->shortname;
 589              $this->shortname = tool_uploadcourse_helper::increment_shortname($this->shortname);
 590              $exists = false;
 591              if ($this->shortname != $original) {
 592                  $this->status('courseshortnameincremented', new lang_string('courseshortnameincremented', 'tool_uploadcourse',
 593                      array('from' => $original, 'to' => $this->shortname)));
 594                  if (isset($coursedata['idnumber'])) {
 595                      $originalidn = $coursedata['idnumber'];
 596                      $coursedata['idnumber'] = tool_uploadcourse_helper::increment_idnumber($coursedata['idnumber']);
 597                      if ($originalidn != $coursedata['idnumber']) {
 598                          $this->status('courseidnumberincremented', new lang_string('courseidnumberincremented', 'tool_uploadcourse',
 599                              array('from' => $originalidn, 'to' => $coursedata['idnumber'])));
 600                      }
 601                  }
 602              }
 603          }
 604  
 605          // If the course does not exist, ensure that the ID number is not taken.
 606          if (!$exists && isset($coursedata['idnumber'])) {
 607              if ($DB->count_records_select('course', 'idnumber = :idn', array('idn' => $coursedata['idnumber'])) > 0) {
 608                  $this->error('idnumberalreadyinuse', new lang_string('idnumberalreadyinuse', 'tool_uploadcourse'));
 609                  return false;
 610              }
 611          }
 612  
 613          // Course start date.
 614          if (!empty($coursedata['startdate'])) {
 615              $coursedata['startdate'] = strtotime($coursedata['startdate']);
 616          }
 617  
 618          // Course end date.
 619          if (!empty($coursedata['enddate'])) {
 620              $coursedata['enddate'] = strtotime($coursedata['enddate']);
 621          }
 622  
 623          // If lang is specified, check the user is allowed to set that field.
 624          if (!empty($coursedata['lang'])) {
 625              if ($exists) {
 626                  $courseid = $DB->get_field('course', 'id', ['shortname' => $this->shortname]);
 627                  if (!has_capability('moodle/course:setforcedlanguage', context_course::instance($courseid))) {
 628                      $this->error('cannotforcelang', new lang_string('cannotforcelang', 'tool_uploadcourse'));
 629                      return false;
 630                  }
 631              } else {
 632                  $catcontext = context_coursecat::instance($coursedata['category']);
 633                  if (!guess_if_creator_will_have_course_capability('moodle/course:setforcedlanguage', $catcontext)) {
 634                      $this->error('cannotforcelang', new lang_string('cannotforcelang', 'tool_uploadcourse'));
 635                      return false;
 636                  }
 637              }
 638          }
 639  
 640          // Ultimate check mode vs. existence.
 641          switch ($mode) {
 642              case tool_uploadcourse_processor::MODE_CREATE_NEW:
 643              case tool_uploadcourse_processor::MODE_CREATE_ALL:
 644                  if ($exists) {
 645                      $this->error('courseexistsanduploadnotallowed',
 646                          new lang_string('courseexistsanduploadnotallowed', 'tool_uploadcourse'));
 647                      return false;
 648                  }
 649                  break;
 650              case tool_uploadcourse_processor::MODE_UPDATE_ONLY:
 651                  if (!$exists) {
 652                      $this->error('coursedoesnotexistandcreatenotallowed',
 653                          new lang_string('coursedoesnotexistandcreatenotallowed', 'tool_uploadcourse'));
 654                      return false;
 655                  }
 656                  // No break!
 657              case tool_uploadcourse_processor::MODE_CREATE_OR_UPDATE:
 658                  if ($exists) {
 659                      if ($updatemode === tool_uploadcourse_processor::UPDATE_NOTHING) {
 660                          $this->error('updatemodedoessettonothing',
 661                              new lang_string('updatemodedoessettonothing', 'tool_uploadcourse'));
 662                          return false;
 663                      }
 664                  }
 665                  break;
 666              default:
 667                  // O_o Huh?! This should really never happen here!
 668                  $this->error('unknownimportmode', new lang_string('unknownimportmode', 'tool_uploadcourse'));
 669                  return false;
 670          }
 671  
 672          // Get final data.
 673          if ($exists) {
 674              $missingonly = ($updatemode === tool_uploadcourse_processor::UPDATE_MISSING_WITH_DATA_OR_DEFAUTLS);
 675              $coursedata = $this->get_final_update_data($coursedata, $usedefaults, $missingonly);
 676  
 677              // Make sure we are not trying to mess with the front page, though we should never get here!
 678              if ($coursedata['id'] == $SITE->id) {
 679                  $this->error('cannotupdatefrontpage', new lang_string('cannotupdatefrontpage', 'tool_uploadcourse'));
 680                  return false;
 681              }
 682  
 683              $this->do = self::DO_UPDATE;
 684          } else {
 685              $coursedata = $this->get_final_create_data($coursedata);
 686              $this->do = self::DO_CREATE;
 687          }
 688  
 689          // Validate course start and end dates.
 690          if ($exists) {
 691              // We also check existing start and end dates if we are updating an existing course.
 692              $existingdata = $DB->get_record('course', array('shortname' => $this->shortname));
 693              if (empty($coursedata['startdate'])) {
 694                  $coursedata['startdate'] = $existingdata->startdate;
 695              }
 696              if (empty($coursedata['enddate'])) {
 697                  $coursedata['enddate'] = $existingdata->enddate;
 698              }
 699          }
 700          if ($errorcode = course_validate_dates($coursedata)) {
 701              $this->error($errorcode, new lang_string($errorcode, 'error'));
 702              return false;
 703          }
 704  
 705          // Add role renaming.
 706          $errors = array();
 707          $rolenames = tool_uploadcourse_helper::get_role_names($this->rawdata, $errors);
 708          if (!empty($errors)) {
 709              foreach ($errors as $key => $message) {
 710                  $this->error($key, $message);
 711              }
 712              return false;
 713          }
 714          foreach ($rolenames as $rolekey => $rolename) {
 715              $coursedata[$rolekey] = $rolename;
 716          }
 717  
 718          // Custom fields. If the course already exists and mode isn't set to force creation, we can use its context.
 719          if ($exists && $mode !== tool_uploadcourse_processor::MODE_CREATE_ALL) {
 720              $context = context_course::instance($coursedata['id']);
 721          } else {
 722              // The category ID is taken from the defaults if it exists, otherwise from course data.
 723              $context = context_coursecat::instance($this->defaults['category'] ?? $coursedata['category']);
 724          }
 725          $customfielddata = tool_uploadcourse_helper::get_custom_course_field_data($this->rawdata, $this->defaults, $context,
 726              $errors);
 727          if (!empty($errors)) {
 728              foreach ($errors as $key => $message) {
 729                  $this->error($key, $message);
 730              }
 731  
 732              return false;
 733          }
 734  
 735          foreach ($customfielddata as $name => $value) {
 736              $coursedata[$name] = $value;
 737          }
 738  
 739          // Some validation.
 740          if (!empty($coursedata['format']) && !in_array($coursedata['format'], tool_uploadcourse_helper::get_course_formats())) {
 741              $this->error('invalidcourseformat', new lang_string('invalidcourseformat', 'tool_uploadcourse'));
 742              return false;
 743          }
 744  
 745          // Add data for course format options.
 746          if (isset($coursedata['format']) || $exists) {
 747              if (isset($coursedata['format'])) {
 748                  $courseformat = course_get_format((object)['format' => $coursedata['format']]);
 749              } else {
 750                  $courseformat = course_get_format($existingdata);
 751              }
 752              $coursedata += $courseformat->validate_course_format_options($this->rawdata);
 753          }
 754  
 755          // Special case, 'numsections' is not a course format option any more but still should apply from the template course,
 756          // if any, and otherwise from defaults.
 757          if (!$exists || !array_key_exists('numsections', $coursedata)) {
 758              if (isset($this->rawdata['numsections']) && is_numeric($this->rawdata['numsections'])) {
 759                  $coursedata['numsections'] = (int)$this->rawdata['numsections'];
 760              } else if (isset($this->options['templatecourse'])) {
 761                  $numsections = tool_uploadcourse_helper::get_coursesection_count($this->options['templatecourse']);
 762                  if ($numsections != 0) {
 763                      $coursedata['numsections'] = $numsections;
 764                  } else {
 765                      $coursedata['numsections'] = get_config('moodlecourse', 'numsections');
 766                  }
 767              } else {
 768                  $coursedata['numsections'] = get_config('moodlecourse', 'numsections');
 769              }
 770          }
 771  
 772          // Visibility can only be 0 or 1.
 773          if (!empty($coursedata['visible']) AND !($coursedata['visible'] == 0 OR $coursedata['visible'] == 1)) {
 774              $this->error('invalidvisibilitymode', new lang_string('invalidvisibilitymode', 'tool_uploadcourse'));
 775              return false;
 776          }
 777  
 778          // Ensure that user is allowed to configure course content download and the field contains a valid value.
 779          if (isset($coursedata['downloadcontent'])) {
 780              if (!$CFG->downloadcoursecontentallowed ||
 781                      !has_capability('moodle/course:configuredownloadcontent', $context)) {
 782  
 783                  $this->error('downloadcontentnotallowed', new lang_string('downloadcontentnotallowed', 'tool_uploadcourse'));
 784                  return false;
 785              }
 786  
 787              $downloadcontentvalues = [
 788                  DOWNLOAD_COURSE_CONTENT_DISABLED,
 789                  DOWNLOAD_COURSE_CONTENT_ENABLED,
 790                  DOWNLOAD_COURSE_CONTENT_SITE_DEFAULT,
 791              ];
 792              if (!in_array($coursedata['downloadcontent'], $downloadcontentvalues)) {
 793                  $this->error('invaliddownloadcontent', new lang_string('invaliddownloadcontent', 'tool_uploadcourse'));
 794                  return false;
 795              }
 796          }
 797  
 798          // Saving data.
 799          $this->data = $coursedata;
 800  
 801          // Get enrolment data. Where the course already exists, we can also perform validation.
 802          $this->enrolmentdata = tool_uploadcourse_helper::get_enrolment_data($this->rawdata);
 803          $courseid = $coursedata['id'] ?? 0;
 804          $errors = $this->validate_enrolment_data($courseid, $this->enrolmentdata);
 805  
 806          if (!empty($errors)) {
 807              foreach ($errors as $key => $message) {
 808                  $this->error($key, $message);
 809              }
 810  
 811              return false;
 812          }
 813  
 814          if (isset($this->rawdata['tags']) && strval($this->rawdata['tags']) !== '') {
 815              $this->data['tags'] = preg_split('/\s*,\s*/', trim($this->rawdata['tags']), -1, PREG_SPLIT_NO_EMPTY);
 816          }
 817  
 818          // Restore data.
 819          // TODO Speed up things by not really extracting the backup just yet, but checking that
 820          // the backup file or shortname passed are valid. Extraction should happen in proceed().
 821          $this->restoredata = $this->get_restore_content_dir();
 822          if ($this->restoredata === false) {
 823              return false;
 824          }
 825  
 826          // We can only reset courses when allowed and we are updating the course.
 827          if ($this->importoptions['reset'] || $this->options['reset']) {
 828              if ($this->do !== self::DO_UPDATE) {
 829                  $this->error('canonlyresetcourseinupdatemode',
 830                      new lang_string('canonlyresetcourseinupdatemode', 'tool_uploadcourse'));
 831                  return false;
 832              } else if (!$this->can_reset()) {
 833                  $this->error('courseresetnotallowed', new lang_string('courseresetnotallowed', 'tool_uploadcourse'));
 834                  return false;
 835              }
 836          }
 837  
 838          return true;
 839      }
 840  
 841      /**
 842       * Proceed with the import of the course.
 843       *
 844       * @return void
 845       */
 846      public function proceed() {
 847          global $CFG, $USER;
 848  
 849          if (!$this->prepared) {
 850              throw new coding_exception('The course has not been prepared.');
 851          } else if ($this->has_errors()) {
 852              throw new moodle_exception('Cannot proceed, errors were detected.');
 853          } else if ($this->processstarted) {
 854              throw new coding_exception('The process has already been started.');
 855          }
 856          $this->processstarted = true;
 857  
 858          if ($this->do === self::DO_DELETE) {
 859              if ($this->delete()) {
 860                  $this->status('coursedeleted', new lang_string('coursedeleted', 'tool_uploadcourse'));
 861              } else {
 862                  $this->error('errorwhiledeletingcourse', new lang_string('errorwhiledeletingcourse', 'tool_uploadcourse'));
 863              }
 864              return true;
 865          } else if ($this->do === self::DO_CREATE) {
 866              $course = create_course((object) $this->data);
 867              $this->id = $course->id;
 868              $this->status('coursecreated', new lang_string('coursecreated', 'tool_uploadcourse'));
 869          } else if ($this->do === self::DO_UPDATE) {
 870              $course = (object) $this->data;
 871              update_course($course);
 872              $this->id = $course->id;
 873              $this->status('courseupdated', new lang_string('courseupdated', 'tool_uploadcourse'));
 874          } else {
 875              // Strangely the outcome has not been defined, or is unknown!
 876              throw new coding_exception('Unknown outcome!');
 877          }
 878  
 879          // Restore a course.
 880          if (!empty($this->restoredata)) {
 881              $rc = new restore_controller($this->restoredata, $course->id, backup::INTERACTIVE_NO,
 882                  backup::MODE_IMPORT, $USER->id, backup::TARGET_CURRENT_ADDING);
 883  
 884              // Check if the format conversion must happen first.
 885              if ($rc->get_status() == backup::STATUS_REQUIRE_CONV) {
 886                  $rc->convert();
 887              }
 888              if ($rc->execute_precheck()) {
 889                  $rc->execute_plan();
 890                  $this->status('courserestored', new lang_string('courserestored', 'tool_uploadcourse'));
 891              } else {
 892                  $this->error('errorwhilerestoringcourse', new lang_string('errorwhilerestoringthecourse', 'tool_uploadcourse'));
 893              }
 894              $rc->destroy();
 895          }
 896  
 897          // Proceed with enrolment data.
 898          $this->process_enrolment_data($course);
 899  
 900          // Reset the course.
 901          if ($this->importoptions['reset'] || $this->options['reset']) {
 902              if ($this->do === self::DO_UPDATE && $this->can_reset()) {
 903                  $this->reset($course);
 904                  $this->status('coursereset', new lang_string('coursereset', 'tool_uploadcourse'));
 905              }
 906          }
 907  
 908          // Mark context as dirty.
 909          $context = context_course::instance($course->id);
 910          $context->mark_dirty();
 911      }
 912  
 913      /**
 914       * Validate passed enrolment data against an existing course
 915       *
 916       * @param int $courseid
 917       * @param array[] $enrolmentdata
 918       * @return lang_string[] Errors keyed on error code
 919       */
 920      protected function validate_enrolment_data(int $courseid, array $enrolmentdata): array {
 921          global $DB;
 922  
 923          // Nothing to validate.
 924          if (empty($enrolmentdata)) {
 925              return [];
 926          }
 927  
 928          $errors = [];
 929  
 930          $enrolmentplugins = tool_uploadcourse_helper::get_enrolment_plugins();
 931          $instances = enrol_get_instances($courseid, false);
 932  
 933          foreach ($enrolmentdata as $method => $options) {
 934  
 935              if (isset($options['role']) || isset($options['roleid'])) {
 936                  if (isset($options['role'])) {
 937                      $role = $options['role'];
 938                      $roleid = $DB->get_field('role', 'id', ['shortname' => $role], MUST_EXIST);
 939                  } else {
 940                      $roleid = $options['roleid'];
 941                      $role = $DB->get_field('role', 'shortname', ['id' => $roleid], MUST_EXIST);
 942                  }
 943                  if ($courseid) {
 944                      if (!$this->validate_role_context($courseid, $roleid)) {
 945                          $errors['contextrolenotallowed'] = new lang_string('contextrolenotallowed', 'core_role', $role);
 946  
 947                          break;
 948                      }
 949                  } else {
 950                      // We can at least check that context level is correct while actual context not exist.
 951                      if (!$this->validate_role_context_level($roleid)) {
 952                          $errors['contextrolenotallowed'] = new lang_string('contextrolenotallowed', 'core_role', $role);
 953  
 954                          break;
 955                      }
 956                  }
 957              }
 958  
 959              $plugin = $enrolmentplugins[$method];
 960              $errors += $plugin->validate_enrol_plugin_data($options, $courseid);
 961              if ($errors) {
 962                  break;
 963              }
 964  
 965              if ($courseid) {
 966                  // Find matching instances by enrolment method.
 967                  $methodinstances = array_filter($instances, static function (stdClass $instance) use ($method) {
 968                      return (strcmp($instance->enrol, $method) == 0);
 969                  });
 970  
 971                  if (!empty($options['delete'])) {
 972                      // Ensure user is able to delete the instances.
 973                      foreach ($methodinstances as $methodinstance) {
 974                          if (!$plugin->can_delete_instance($methodinstance)) {
 975                              $errors['errorcannotdeleteenrolment'] = new lang_string('errorcannotdeleteenrolment',
 976                                  'tool_uploadcourse', $plugin->get_instance_name($methodinstance));
 977                              break;
 978                          }
 979                      }
 980                  } else if (!empty($options['disable'])) {
 981                      // Ensure user is able to toggle instance statuses.
 982                      foreach ($methodinstances as $methodinstance) {
 983                          if (!$plugin->can_hide_show_instance($methodinstance)) {
 984                              $errors['errorcannotdisableenrolment'] =
 985                                  new lang_string('errorcannotdisableenrolment', 'tool_uploadcourse',
 986                                      $plugin->get_instance_name($methodinstance));
 987  
 988                              break;
 989                          }
 990                      }
 991                  } else {
 992                      // Ensure user is able to create/update instance.
 993                      $methodinstance = empty($methodinstances) ? null : reset($methodinstances);
 994                      if ((empty($methodinstance) && !$plugin->can_add_instance($courseid)) ||
 995                          (!empty($methodinstance) && !$plugin->can_edit_instance($methodinstance))) {
 996  
 997                          $errors['errorcannotcreateorupdateenrolment'] =
 998                              new lang_string('errorcannotcreateorupdateenrolment', 'tool_uploadcourse',
 999                                  $plugin->get_instance_name($methodinstance));
1000  
1001                          break;
1002                      }
1003                  }
1004              }
1005          }
1006  
1007          return $errors;
1008      }
1009  
1010      /**
1011       * Add the enrolment data for the course.
1012       *
1013       * @param object $course course record.
1014       * @return void
1015       */
1016      protected function process_enrolment_data($course) {
1017          global $DB;
1018  
1019          $enrolmentdata = $this->enrolmentdata;
1020          if (empty($enrolmentdata)) {
1021              return;
1022          }
1023  
1024          $enrolmentplugins = tool_uploadcourse_helper::get_enrolment_plugins();
1025          $instances = enrol_get_instances($course->id, false);
1026          foreach ($enrolmentdata as $enrolmethod => $method) {
1027  
1028              $instance = null;
1029              foreach ($instances as $i) {
1030                  if ($i->enrol == $enrolmethod) {
1031                      $instance = $i;
1032                      break;
1033                  }
1034              }
1035  
1036              $todelete = isset($method['delete']) && $method['delete'];
1037              $todisable = isset($method['disable']) && $method['disable'];
1038              unset($method['delete']);
1039              unset($method['disable']);
1040  
1041              if ($todelete) {
1042                  // Remove the enrolment method.
1043                  if ($instance) {
1044                      $plugin = $enrolmentplugins[$instance->enrol];
1045  
1046                      // Ensure user is able to delete the instance.
1047                      if ($plugin->can_delete_instance($instance) && $plugin->is_csv_upload_supported()) {
1048                          $plugin->delete_instance($instance);
1049                      } else {
1050                          $this->error('errorcannotdeleteenrolment',
1051                              new lang_string('errorcannotdeleteenrolment', 'tool_uploadcourse',
1052                                  $plugin->get_instance_name($instance)));
1053                      }
1054                  }
1055              } else {
1056                  // Create/update enrolment.
1057                  $plugin = $enrolmentplugins[$enrolmethod];
1058  
1059                  if ($plugin->is_csv_upload_supported()) {
1060                      $status = ($todisable) ? ENROL_INSTANCE_DISABLED : ENROL_INSTANCE_ENABLED;
1061                      $method = $plugin->fill_enrol_custom_fields($method, $course->id);
1062  
1063                      // Create a new instance if necessary.
1064                      if (empty($instance) && $plugin->can_add_instance($course->id)) {
1065                          $error = $plugin->validate_plugin_data_context($method, $course->id);
1066                          if ($error) {
1067                              $this->error('contextnotallowed', $error);
1068                              break;
1069                          }
1070                          $instanceid = $plugin->add_default_instance($course);
1071                          if (!$instanceid) {
1072                              // Add instance with provided fields if plugin supports it.
1073                              $instanceid = $plugin->add_custom_instance($course, $method);
1074                          }
1075  
1076                          $instance = $DB->get_record('enrol', ['id' => $instanceid]);
1077                          if ($instance) {
1078                              $instance->roleid = $plugin->get_config('roleid');
1079                              // On creation the user can decide the status.
1080                              $plugin->update_status($instance, $status);
1081                          }
1082                      }
1083  
1084                      // Check if the we need to update the instance status.
1085                      if ($instance && $status != $instance->status) {
1086                          if ($plugin->can_hide_show_instance($instance)) {
1087                              $plugin->update_status($instance, $status);
1088                          } else {
1089                              $this->error('errorcannotdisableenrolment',
1090                                  new lang_string('errorcannotdisableenrolment', 'tool_uploadcourse',
1091                                      $plugin->get_instance_name($instance)));
1092                              break;
1093                          }
1094                      }
1095  
1096                      if (empty($instance) || !$plugin->can_edit_instance($instance)) {
1097                          $this->error('errorcannotcreateorupdateenrolment',
1098                              new lang_string('errorcannotcreateorupdateenrolment', 'tool_uploadcourse',
1099                                  $plugin->get_instance_name($instance)));
1100  
1101                          break;
1102                      }
1103  
1104                      // Now update values.
1105                      $modifiedinstance = $instance;
1106  
1107                      // Sort out the start, end and date.
1108                      $modifiedinstance->enrolstartdate = (isset($method['startdate']) ? strtotime($method['startdate']) : 0);
1109                      $modifiedinstance->enrolenddate = (isset($method['enddate']) ? strtotime($method['enddate']) : 0);
1110  
1111                      // Is the enrolment period set?
1112                      if (isset($method['enrolperiod']) && !empty($method['enrolperiod'])) {
1113                          if (preg_match('/^\d+$/', $method['enrolperiod'])) {
1114                              $method['enrolperiod'] = (int)$method['enrolperiod'];
1115                          } else {
1116                              // Try and convert period to seconds.
1117                              $method['enrolperiod'] = strtotime('1970-01-01 GMT + ' . $method['enrolperiod']);
1118                          }
1119                          $modifiedinstance->enrolperiod = $method['enrolperiod'];
1120                      }
1121                      if ($instance->enrolstartdate > 0 && isset($method['enrolperiod'])) {
1122                          $modifiedinstance->enrolenddate = $instance->enrolstartdate + $method['enrolperiod'];
1123                      }
1124                      if ($instance->enrolenddate > 0) {
1125                          $modifiedinstance->enrolperiod = $instance->enrolenddate - $instance->enrolstartdate;
1126                      }
1127                      if ($instance->enrolenddate < $instance->enrolstartdate) {
1128                          $modifiedinstance->enrolenddate = $instance->enrolstartdate;
1129                      }
1130  
1131                      // Sort out the given role.
1132                      if (isset($method['role']) || isset($method['roleid'])) {
1133                          if (isset($method['role'])) {
1134                              $role = $method['role'];
1135                              $roleid = $DB->get_field('role', 'id', ['shortname' => $role], MUST_EXIST);
1136                          } else {
1137                              $roleid = $method['roleid'];
1138                              $role = $DB->get_field('role', 'shortname', ['id' => $roleid], MUST_EXIST);
1139                          }
1140                          if (!$this->validate_role_context($course->id, $roleid)) {
1141                              $this->error('contextrolenotallowed',
1142                                  new lang_string('contextrolenotallowed', 'core_role', $role));
1143                              break;
1144                          }
1145  
1146                          $roleids = tool_uploadcourse_helper::get_role_ids();
1147                          if (in_array($roleid, $roleids)) {
1148                              $modifiedinstance->roleid = $roleid;
1149                          }
1150                      }
1151  
1152                      // Sort out custom instance name.
1153                      if (isset($method['name'])) {
1154                          $modifiedinstance->name = $method['name'];
1155                      }
1156  
1157                      $plugin->update_instance($instance, $modifiedinstance);
1158                  } else {
1159                      $this->error('errorunsupportedmethod',
1160                          new lang_string('errorunsupportedmethod', 'tool_uploadcourse',
1161                              $enrolmethod));
1162                  }
1163              }
1164          }
1165      }
1166  
1167      /**
1168       * Check if role is allowed in course context
1169       *
1170       * @param int $courseid course context.
1171       * @param int $roleid Role ID.
1172       * @return bool
1173       */
1174      protected function validate_role_context(int $courseid, int $roleid) : bool {
1175          if (empty($this->assignableroles[$courseid])) {
1176              $coursecontext = \context_course::instance($courseid);
1177              $this->assignableroles[$courseid] = get_assignable_roles($coursecontext, ROLENAME_SHORT);
1178          }
1179          if (!array_key_exists($roleid, $this->assignableroles[$courseid])) {
1180              return false;
1181          }
1182          return true;
1183      }
1184  
1185      /**
1186       * Check if role is allowed at this context level.
1187       *
1188       * @param int $roleid Role ID.
1189       * @return bool
1190       */
1191      protected function validate_role_context_level(int $roleid) : bool {
1192          if (empty($this->contextlevels[$roleid])) {
1193              $this->contextlevels[$roleid] = get_role_contextlevels($roleid);
1194          }
1195  
1196          if (!in_array(CONTEXT_COURSE, $this->contextlevels[$roleid])) {
1197              return false;
1198          }
1199          return true;
1200      }
1201  
1202      /**
1203       * Reset the current course.
1204       *
1205       * This does not reset any of the content of the activities.
1206       *
1207       * @param stdClass $course the course object of the course to reset.
1208       * @return array status array of array component, item, error.
1209       */
1210      protected function reset($course) {
1211          global $DB;
1212  
1213          $resetdata = new stdClass();
1214          $resetdata->id = $course->id;
1215          $resetdata->reset_start_date = time();
1216          $resetdata->reset_events = true;
1217          $resetdata->reset_notes = true;
1218          $resetdata->delete_blog_associations = true;
1219          $resetdata->reset_completion = true;
1220          $resetdata->reset_roles_overrides = true;
1221          $resetdata->reset_roles_local = true;
1222          $resetdata->reset_groups_members = true;
1223          $resetdata->reset_groups_remove = true;
1224          $resetdata->reset_groupings_members = true;
1225          $resetdata->reset_groupings_remove = true;
1226          $resetdata->reset_gradebook_items = true;
1227          $resetdata->reset_gradebook_grades = true;
1228          $resetdata->reset_comments = true;
1229  
1230          if (empty($course->startdate)) {
1231              $course->startdate = $DB->get_field_select('course', 'startdate', 'id = :id', array('id' => $course->id));
1232          }
1233          $resetdata->reset_start_date_old = $course->startdate;
1234  
1235          if (empty($course->enddate)) {
1236              $course->enddate = $DB->get_field_select('course', 'enddate', 'id = :id', array('id' => $course->id));
1237          }
1238          $resetdata->reset_end_date_old = $course->enddate;
1239  
1240          // Add roles.
1241          $roles = tool_uploadcourse_helper::get_role_ids();
1242          $resetdata->unenrol_users = array_values($roles);
1243          $resetdata->unenrol_users[] = 0;    // Enrolled without role.
1244  
1245          return reset_course_userdata($resetdata);
1246      }
1247  
1248      /**
1249       * Log a status
1250       *
1251       * @param string $code status code.
1252       * @param lang_string $message status message.
1253       * @return void
1254       */
1255      protected function status($code, lang_string $message) {
1256          if (array_key_exists($code, $this->statuses)) {
1257              throw new coding_exception('Status code already defined');
1258          }
1259          $this->statuses[$code] = $message;
1260      }
1261  
1262  }