Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 310 and 311] [Versions 39 and 311]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * 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                  if ($course->has_errors()) {
 223                      $errors++;
 224                      $tracker->output($this->linenb, false, $course->get_errors(), $data);
 225                  }
 226              } else {
 227                  $errors++;
 228                  $tracker->output($this->linenb, false, $course->get_errors(), $data);
 229              }
 230          }
 231  
 232          $tracker->finish();
 233          $tracker->results($total, $created, $updated, $deleted, $errors);
 234      }
 235  
 236      /**
 237       * Return a course import object.
 238       *
 239       * @param array $data data to import the course with.
 240       * @return tool_uploadcourse_course
 241       */
 242      protected function get_course($data) {
 243          $importoptions = array(
 244              'candelete' => $this->allowdeletes,
 245              'canrename' => $this->allowrenames,
 246              'canreset' => $this->allowresets,
 247              'reset' => $this->reset,
 248              'restoredir' => $this->get_restore_content_dir(),
 249              'shortnametemplate' => $this->shortnametemplate
 250          );
 251          return new tool_uploadcourse_course($this->mode, $this->updatemode, $data, $this->defaults, $importoptions);
 252      }
 253  
 254      /**
 255       * Return the errors.
 256       *
 257       * @return array
 258       */
 259      public function get_errors() {
 260          return $this->errors;
 261      }
 262  
 263      /**
 264       * Get the directory of the object to restore.
 265       *
 266       * @return string subdirectory in $CFG->backuptempdir/...
 267       */
 268      protected function get_restore_content_dir() {
 269          $backupfile = null;
 270          $shortname = null;
 271  
 272          if (!empty($this->restorefile)) {
 273              $backupfile = $this->restorefile;
 274          } else if (!empty($this->templatecourse) || is_numeric($this->templatecourse)) {
 275              $shortname = $this->templatecourse;
 276          }
 277  
 278          $dir = tool_uploadcourse_helper::get_restore_content_dir($backupfile, $shortname);
 279          return $dir;
 280      }
 281  
 282      /**
 283       * Log errors on the current line.
 284       *
 285       * @param array $errors array of errors
 286       * @return void
 287       */
 288      protected function log_error($errors) {
 289          if (empty($errors)) {
 290              return;
 291          }
 292  
 293          foreach ($errors as $code => $langstring) {
 294              if (!isset($this->errors[$this->linenb])) {
 295                  $this->errors[$this->linenb] = array();
 296              }
 297              $this->errors[$this->linenb][$code] = $langstring;
 298          }
 299      }
 300  
 301      /**
 302       * Parse a line to return an array(column => value)
 303       *
 304       * @param array $line returned by csv_import_reader
 305       * @return array
 306       */
 307      protected function parse_line($line) {
 308          $data = array();
 309          foreach ($line as $keynum => $value) {
 310              if (!isset($this->columns[$keynum])) {
 311                  // This should not happen.
 312                  continue;
 313              }
 314  
 315              $key = $this->columns[$keynum];
 316              $data[$key] = $value;
 317          }
 318          return $data;
 319      }
 320  
 321      /**
 322       * Return a preview of the import.
 323       *
 324       * This only returns passed data, along with the errors.
 325       *
 326       * @param integer $rows number of rows to preview.
 327       * @param object $tracker the output tracker to use.
 328       * @return array of preview data.
 329       */
 330      public function preview($rows = 10, $tracker = null) {
 331          if ($this->processstarted) {
 332              throw new coding_exception('Process has already been started');
 333          }
 334          $this->processstarted = true;
 335  
 336          if (empty($tracker)) {
 337              $tracker = new tool_uploadcourse_tracker(tool_uploadcourse_tracker::NO_OUTPUT);
 338          }
 339          $tracker->start();
 340  
 341          // We might need extra time and memory depending on the number of rows to preview.
 342          core_php_time_limit::raise();
 343          raise_memory_limit(MEMORY_EXTRA);
 344  
 345          // Loop over the CSV lines.
 346          $preview = array();
 347          while (($line = $this->cir->next()) && $rows > $this->linenb) {
 348              $this->linenb++;
 349              $data = $this->parse_line($line);
 350              $course = $this->get_course($data);
 351              $result = $course->prepare();
 352              if (!$result) {
 353                  $tracker->output($this->linenb, $result, $course->get_errors(), $data);
 354              } else {
 355                  $tracker->output($this->linenb, $result, $course->get_statuses(), $data);
 356              }
 357              $row = $data;
 358              $preview[$this->linenb] = $row;
 359          }
 360  
 361          $tracker->finish();
 362  
 363          return $preview;
 364      }
 365  
 366      /**
 367       * Reset the current process.
 368       *
 369       * @return void.
 370       */
 371      public function reset() {
 372          $this->processstarted = false;
 373          $this->linenb = 0;
 374          $this->cir->init();
 375          $this->errors = array();
 376      }
 377  
 378      /**
 379       * Validation.
 380       *
 381       * @return void
 382       */
 383      protected function validate() {
 384          if (empty($this->columns)) {
 385              throw new moodle_exception('cannotreadtmpfile', 'error');
 386          } else if (count($this->columns) < 2) {
 387              throw new moodle_exception('csvfewcolumns', 'error');
 388          }
 389      }
 390  }