Search moodle.org's
Developer Documentation

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
  • Differences Between: [Versions 310 and 311] [Versions 37 and 311] [Versions 38 and 311] [Versions 39 and 311]

       1  <?php
       2  // This file is part of Moodle - http://moodle.org/
       3  //
       4  // Moodle is free software: you can redistribute it and/or modify
       5  // it under the terms of the GNU General Public License as published by
       6  // the Free Software Foundation, either version 3 of the License, or
       7  // (at your option) any later version.
       8  //
       9  // Moodle is distributed in the hope that it will be useful,
      10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
      11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12  // GNU General Public License for more details.
      13  //
      14  // You should have received a copy of the GNU General Public License
      15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
      16  
      17  /**
      18   * 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  }