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.
   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  namespace mod_data\local\importer;
  18  
  19  use context_module;
  20  use core_php_time_limit;
  21  use core_tag_tag;
  22  use core_user;
  23  use csv_import_reader;
  24  use moodle_exception;
  25  use stdClass;
  26  
  27  /**
  28   * CSV entries_importer class for importing data and - if needed - files as well from a zip archive.
  29   *
  30   * @package    mod_data
  31   * @copyright  2023 ISB Bayern
  32   * @author     Philipp Memmel
  33   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  34   */
  35  class csv_entries_importer extends entries_importer {
  36  
  37      /** @var array Log entries for successfully added records. */
  38      private array $addedrecordsmessages = [];
  39  
  40      /**
  41       * Declares the entries_importer to use a csv file as data file.
  42       *
  43       * @see entries_importer::get_import_data_file_extension()
  44       */
  45      public function get_import_data_file_extension(): string {
  46          return 'csv';
  47      }
  48  
  49      /**
  50       * Import records for a data instance from csv data.
  51       *
  52       * @param stdClass $cm Course module of the data instance.
  53       * @param stdClass $data The data instance.
  54       * @param string $encoding The encoding of csv data.
  55       * @param string $fielddelimiter The delimiter of the csv data.
  56       *
  57       * @throws moodle_exception
  58       */
  59      public function import_csv(stdClass $cm, stdClass $data, string $encoding, string $fielddelimiter): void {
  60          global $CFG, $DB;
  61          // Large files are likely to take their time and memory. Let PHP know
  62          // that we'll take longer, and that the process should be recycled soon
  63          // to free up memory.
  64          core_php_time_limit::raise();
  65          raise_memory_limit(MEMORY_HUGE);
  66  
  67          $iid = csv_import_reader::get_new_iid('moddata');
  68          $cir = new csv_import_reader($iid, 'moddata');
  69  
  70          $context = context_module::instance($cm->id);
  71  
  72          $readcount = $cir->load_csv_content($this->get_data_file_content(), $encoding, $fielddelimiter);
  73          if (empty($readcount)) {
  74              throw new \moodle_exception('csvfailed', 'data', "{$CFG->wwwroot}/mod/data/edit.php?d={$data->id}");
  75          } else {
  76              if (!$fieldnames = $cir->get_columns()) {
  77                  throw new \moodle_exception('cannotreadtmpfile', 'error');
  78              }
  79  
  80              // Check the fieldnames are valid.
  81              $rawfields = $DB->get_records('data_fields', ['dataid' => $data->id], '', 'name, id, type');
  82              $fields = [];
  83              $errorfield = '';
  84              $usernamestring = get_string('username');
  85              $safetoskipfields = [get_string('user'), get_string('email'),
  86                  get_string('timeadded', 'data'), get_string('timemodified', 'data'),
  87                  get_string('approved', 'data'), get_string('tags', 'data')];
  88              $userfieldid = null;
  89              foreach ($fieldnames as $id => $name) {
  90                  if (!isset($rawfields[$name])) {
  91                      if ($name == $usernamestring) {
  92                          $userfieldid = $id;
  93                      } else if (!in_array($name, $safetoskipfields)) {
  94                          $errorfield .= "'$name' ";
  95                      }
  96                  } else {
  97                      // If this is the second time, a field with this name comes up, it must be a field not provided by the user...
  98                      // like the username.
  99                      if (isset($fields[$name])) {
 100                          if ($name == $usernamestring) {
 101                              $userfieldid = $id;
 102                          }
 103                          unset($fieldnames[$id]); // To ensure the user provided content fields remain in the array once flipped.
 104                      } else {
 105                          $field = $rawfields[$name];
 106                          $filepath = "$CFG->dirroot/mod/data/field/$field->type/field.class.php";
 107                          if (!file_exists($filepath)) {
 108                              $errorfield .= "'$name' ";
 109                              continue;
 110                          }
 111                          require_once($filepath);
 112                          $classname = 'data_field_' . $field->type;
 113                          $fields[$name] = new $classname($field, $data, $cm);
 114                      }
 115                  }
 116              }
 117  
 118              if (!empty($errorfield)) {
 119                  throw new \moodle_exception('fieldnotmatched', 'data',
 120                      "{$CFG->wwwroot}/mod/data/edit.php?d={$data->id}", $errorfield);
 121              }
 122  
 123              $fieldnames = array_flip($fieldnames);
 124  
 125              $cir->init();
 126              while ($record = $cir->next()) {
 127                  $authorid = null;
 128                  if ($userfieldid) {
 129                      if (!($author = core_user::get_user_by_username($record[$userfieldid], 'id'))) {
 130                          $authorid = null;
 131                      } else {
 132                          $authorid = $author->id;
 133                      }
 134                  }
 135                  if ($recordid = data_add_record($data, 0, $authorid)) {  // Add instance to data_record.
 136                      foreach ($fields as $field) {
 137                          $fieldid = $fieldnames[$field->field->name];
 138                          if (isset($record[$fieldid])) {
 139                              $value = $record[$fieldid];
 140                          } else {
 141                              $value = '';
 142                          }
 143  
 144                          if (method_exists($field, 'update_content_import')) {
 145                              $field->update_content_import($recordid, $value, 'field_' . $field->field->id);
 146                          } else {
 147                              $content = new stdClass();
 148                              $content->fieldid = $field->field->id;
 149                              $content->content = $value;
 150                              $content->recordid = $recordid;
 151                              if ($field->file_import_supported() && $this->importfiletype === 'zip') {
 152                                  $filecontent = $this->get_file_content_from_zip($content->content);
 153                                  if (!$filecontent) {
 154                                      // No corresponding file in zip archive, so no record for this field being added at all.
 155                                      continue;
 156                                  }
 157                                  $contentid = $DB->insert_record('data_content', $content);
 158                                  $field->import_file_value($contentid, $filecontent, $content->content);
 159                              } else {
 160                                  $DB->insert_record('data_content', $content);
 161                              }
 162                          }
 163                      }
 164  
 165                      if (core_tag_tag::is_enabled('mod_data', 'data_records') &&
 166                          isset($fieldnames[get_string('tags', 'data')])) {
 167                          $columnindex = $fieldnames[get_string('tags', 'data')];
 168                          $rawtags = $record[$columnindex];
 169                          $tags = explode(',', $rawtags);
 170                          foreach ($tags as $tag) {
 171                              $tag = trim($tag);
 172                              if (empty($tag)) {
 173                                  continue;
 174                              }
 175                              core_tag_tag::add_item_tag('mod_data', 'data_records', $recordid, $context, $tag);
 176                          }
 177                      }
 178  
 179                      $this->addedrecordsmessages[] = get_string('added', 'moodle',
 180                              count($this->addedrecordsmessages) + 1)
 181                          . ". " . get_string('entry', 'data')
 182                          . " (ID $recordid)\n";
 183                  }
 184              }
 185              $cir->close();
 186              $cir->cleanup(true);
 187          }
 188      }
 189  
 190      /**
 191       * Getter for the array of messages for added records.
 192       *
 193       * For each successfully added record the array contains a log message.
 194       *
 195       * @return array Array of message strings: For each added record one message string
 196       */
 197      public function get_added_records_messages(): array {
 198          return $this->addedrecordsmessages;
 199      }
 200  }