Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 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 processor 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->libdir . '/csvlib.class.php');
  27  
  28  /**
  29   * Processor class.
  30   *
  31   * @package    tool_uploadcourse
  32   * @copyright  2013 Frédéric Massart
  33   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  34   */
  35  class tool_uploadcourse_processor {
  36  
  37      /**
  38       * Create courses that do not exist yet.
  39       */
  40      const MODE_CREATE_NEW = 1;
  41  
  42      /**
  43       * Create all courses, appending a suffix to the shortname if the course exists.
  44       */
  45      const MODE_CREATE_ALL = 2;
  46  
  47      /**
  48       * Create courses, and update the ones that already exist.
  49       */
  50      const MODE_CREATE_OR_UPDATE = 3;
  51  
  52      /**
  53       * Only update existing courses.
  54       */
  55      const MODE_UPDATE_ONLY = 4;
  56  
  57      /**
  58       * During update, do not update anything... O_o Huh?!
  59       */
  60      const UPDATE_NOTHING = 0;
  61  
  62      /**
  63       * During update, only use data passed from the CSV.
  64       */
  65      const UPDATE_ALL_WITH_DATA_ONLY = 1;
  66  
  67      /**
  68       * During update, use either data from the CSV, or defaults.
  69       */
  70      const UPDATE_ALL_WITH_DATA_OR_DEFAUTLS = 2;
  71  
  72      /**
  73       * During update, update missing values from either data from the CSV, or defaults.
  74       */
  75      const UPDATE_MISSING_WITH_DATA_OR_DEFAUTLS = 3;
  76  
  77      /** @var int processor mode. */
  78      protected $mode;
  79  
  80      /** @var int upload mode. */
  81      protected $updatemode;
  82  
  83      /** @var bool are renames allowed. */
  84      protected $allowrenames = false;
  85  
  86      /** @var bool are deletes allowed. */
  87      protected $allowdeletes = false;
  88  
  89      /** @var bool are resets allowed. */
  90      protected $allowresets = false;
  91  
  92      /** @var string path to a restore file. */
  93      protected $restorefile;
  94  
  95      /** @var string shortname of the course to be restored. */
  96      protected $templatecourse;
  97  
  98      /** @var string reset courses after processing them. */
  99      protected $reset;
 100  
 101      /** @var string template to generate a course shortname. */
 102      protected $shortnametemplate;
 103  
 104      /** @var csv_import_reader */
 105      protected $cir;
 106  
 107      /** @var array default values. */
 108      protected $defaults = array();
 109  
 110      /** @var array CSV columns. */
 111      protected $columns = array();
 112  
 113      /** @var array of errors where the key is the line number. */
 114      protected $errors = array();
 115  
 116      /** @var int line number. */
 117      protected $linenb = 0;
 118  
 119      /** @var bool whether the process has been started or not. */
 120      protected $processstarted = false;
 121  
 122      /**
 123       * Constructor
 124       *
 125       * @param csv_import_reader $cir import reader object
 126       * @param array $options options of the process
 127       * @param array $defaults default data value
 128       */
 129      public function __construct(csv_import_reader $cir, array $options, array $defaults = array()) {
 130  
 131          if (!isset($options['mode']) || !in_array($options['mode'], array(self::MODE_CREATE_NEW, self::MODE_CREATE_ALL,
 132                  self::MODE_CREATE_OR_UPDATE, self::MODE_UPDATE_ONLY))) {
 133              throw new coding_exception('Unknown process mode');
 134          }
 135  
 136          // Force int to make sure === comparison work as expected.
 137          $this->mode = (int) $options['mode'];
 138  
 139          $this->updatemode = self::UPDATE_NOTHING;
 140          if (isset($options['updatemode'])) {
 141              // Force int to make sure === comparison work as expected.
 142              $this->updatemode = (int) $options['updatemode'];
 143          }
 144          if (isset($options['allowrenames'])) {
 145              $this->allowrenames = $options['allowrenames'];
 146          }
 147          if (isset($options['allowdeletes'])) {
 148              $this->allowdeletes = $options['allowdeletes'];
 149          }
 150          if (isset($options['allowresets'])) {
 151              $this->allowresets = $options['allowresets'];
 152          }
 153  
 154          if (isset($options['restorefile'])) {
 155              $this->restorefile = $options['restorefile'];
 156          }
 157          if (isset($options['templatecourse'])) {
 158              $this->templatecourse = $options['templatecourse'];
 159          }
 160          if (isset($options['reset'])) {
 161              $this->reset = $options['reset'];
 162          }
 163          if (isset($options['shortnametemplate'])) {
 164              $this->shortnametemplate = $options['shortnametemplate'];
 165          }
 166  
 167          $this->cir = $cir;
 168          $this->columns = $cir->get_columns();
 169          $this->defaults = $defaults;
 170          $this->validate();
 171          $this->reset();
 172      }
 173  
 174      /**
 175       * Execute the process.
 176       *
 177       * @param object $tracker the output tracker to use.
 178       * @return void
 179       */
 180      public function execute($tracker = null) {
 181          if ($this->processstarted) {
 182              throw new coding_exception('Process has already been started');
 183          }
 184          $this->processstarted = true;
 185  
 186          if (empty($tracker)) {
 187              $tracker = new tool_uploadcourse_tracker(tool_uploadcourse_tracker::NO_OUTPUT);
 188          }
 189          $tracker->start();
 190  
 191          $total = 0;
 192          $created = 0;
 193          $updated = 0;
 194          $deleted = 0;
 195          $errors = 0;
 196  
 197          // We will most certainly need extra time and memory to process big files.
 198          core_php_time_limit::raise();
 199          raise_memory_limit(MEMORY_EXTRA);
 200  
 201          // Loop over the CSV lines.
 202          while ($line = $this->cir->next()) {
 203              $this->linenb++;
 204              $total++;
 205  
 206              $data = $this->parse_line($line);
 207              $course = $this->get_course($data);
 208              if ($course->prepare()) {
 209                  $course->proceed();
 210  
 211                  $status = $course->get_statuses();
 212                  if (array_key_exists('coursecreated', $status)) {
 213                      $created++;
 214                  } else if (array_key_exists('courseupdated', $status)) {
 215                      $updated++;
 216                  } else if (array_key_exists('coursedeleted', $status)) {
 217                      $deleted++;
 218                  }
 219  
 220                  $data = array_merge($data, $course->get_data(), array('id' => $course->get_id()));
 221                  $tracker->output($this->linenb, true, $status, $data);
 222              } else {
 223                  $errors++;
 224                  $tracker->output($this->linenb, false, $course->get_errors(), $data);
 225              }
 226          }
 227  
 228          $tracker->finish();
 229          $tracker->results($total, $created, $updated, $deleted, $errors);
 230      }
 231  
 232      /**
 233       * Return a course import object.
 234       *
 235       * @param array $data data to import the course with.
 236       * @return tool_uploadcourse_course
 237       */
 238      protected function get_course($data) {
 239          $importoptions = array(
 240              'candelete' => $this->allowdeletes,
 241              'canrename' => $this->allowrenames,
 242              'canreset' => $this->allowresets,
 243              'reset' => $this->reset,
 244              'restoredir' => $this->get_restore_content_dir(),
 245              'shortnametemplate' => $this->shortnametemplate
 246          );
 247          return new tool_uploadcourse_course($this->mode, $this->updatemode, $data, $this->defaults, $importoptions);
 248      }
 249  
 250      /**
 251       * Return the errors.
 252       *
 253       * @return array
 254       */
 255      public function get_errors() {
 256          return $this->errors;
 257      }
 258  
 259      /**
 260       * Get the directory of the object to restore.
 261       *
 262       * @return string subdirectory in $CFG->backuptempdir/...
 263       */
 264      protected function get_restore_content_dir() {
 265          $backupfile = null;
 266          $shortname = null;
 267  
 268          if (!empty($this->restorefile)) {
 269              $backupfile = $this->restorefile;
 270          } else if (!empty($this->templatecourse) || is_numeric($this->templatecourse)) {
 271              $shortname = $this->templatecourse;
 272          }
 273  
 274          $dir = tool_uploadcourse_helper::get_restore_content_dir($backupfile, $shortname);
 275          return $dir;
 276      }
 277  
 278      /**
 279       * Log errors on the current line.
 280       *
 281       * @param array $errors array of errors
 282       * @return void
 283       */
 284      protected function log_error($errors) {
 285          if (empty($errors)) {
 286              return;
 287          }
 288  
 289          foreach ($errors as $code => $langstring) {
 290              if (!isset($this->errors[$this->linenb])) {
 291                  $this->errors[$this->linenb] = array();
 292              }
 293              $this->errors[$this->linenb][$code] = $langstring;
 294          }
 295      }
 296  
 297      /**
 298       * Parse a line to return an array(column => value)
 299       *
 300       * @param array $line returned by csv_import_reader
 301       * @return array
 302       */
 303      protected function parse_line($line) {
 304          $data = array();
 305          foreach ($line as $keynum => $value) {
 306              if (!isset($this->columns[$keynum])) {
 307                  // This should not happen.
 308                  continue;
 309              }
 310  
 311              $key = $this->columns[$keynum];
 312              $data[$key] = $value;
 313          }
 314          return $data;
 315      }
 316  
 317      /**
 318       * Return a preview of the import.
 319       *
 320       * This only returns passed data, along with the errors.
 321       *
 322       * @param integer $rows number of rows to preview.
 323       * @param object $tracker the output tracker to use.
 324       * @return array of preview data.
 325       */
 326      public function preview($rows = 10, $tracker = null) {
 327          if ($this->processstarted) {
 328              throw new coding_exception('Process has already been started');
 329          }
 330          $this->processstarted = true;
 331  
 332          if (empty($tracker)) {
 333              $tracker = new tool_uploadcourse_tracker(tool_uploadcourse_tracker::NO_OUTPUT);
 334          }
 335          $tracker->start();
 336  
 337          // We might need extra time and memory depending on the number of rows to preview.
 338          core_php_time_limit::raise();
 339          raise_memory_limit(MEMORY_EXTRA);
 340  
 341          // Loop over the CSV lines.
 342          $preview = array();
 343          while (($line = $this->cir->next()) && $rows > $this->linenb) {
 344              $this->linenb++;
 345              $data = $this->parse_line($line);
 346              $course = $this->get_course($data);
 347              $result = $course->prepare();
 348              if (!$result) {
 349                  $tracker->output($this->linenb, $result, $course->get_errors(), $data);
 350              } else {
 351                  $tracker->output($this->linenb, $result, $course->get_statuses(), $data);
 352              }
 353              $row = $data;
 354              $preview[$this->linenb] = $row;
 355          }
 356  
 357          $tracker->finish();
 358  
 359          return $preview;
 360      }
 361  
 362      /**
 363       * Reset the current process.
 364       *
 365       * @return void.
 366       */
 367      public function reset() {
 368          $this->processstarted = false;
 369          $this->linenb = 0;
 370          $this->cir->init();
 371          $this->errors = array();
 372      }
 373  
 374      /**
 375       * Validation.
 376       *
 377       * @return void
 378       */
 379      protected function validate() {
 380          if (empty($this->columns)) {
 381              throw new moodle_exception('cannotreadtmpfile', 'error');
 382          } else if (count($this->columns) < 2) {
 383              throw new moodle_exception('csvfewcolumns', 'error');
 384          }
 385      }
 386  }