Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body