on-demand release 4.0dev+
[moodle.git] / admin / tool / uploadcourse / classes / processor.php
CommitLineData
9a7cd639
FM
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
25defined('MOODLE_INTERNAL') || die();
e0164eb3 26require_once($CFG->libdir . '/csvlib.class.php');
9a7cd639
FM
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 */
35class 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
1d1898ac
FM
113 /** @var array of errors where the key is the line number. */
114 protected $errors = array();
115
9a7cd639
FM
116 /** @var int line number. */
117 protected $linenb = 0;
118
9a7cd639
FM
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;
1d1898ac
FM
170 $this->validate();
171 $this->reset();
9a7cd639
FM
172 }
173
174 /**
175 * Execute the process.
176 *
5dcd7e28 177 * @param object $tracker the output tracker to use.
9a7cd639
FM
178 * @return void
179 */
5dcd7e28 180 public function execute($tracker = null) {
9a7cd639
FM
181 if ($this->processstarted) {
182 throw new coding_exception('Process has already been started');
183 }
184 $this->processstarted = true;
185
5dcd7e28
FM
186 if (empty($tracker)) {
187 $tracker = new tool_uploadcourse_tracker(tool_uploadcourse_tracker::NO_OUTPUT);
188 }
5b2f7718
FM
189 $tracker->start();
190
5dcd7e28
FM
191 $total = 0;
192 $created = 0;
193 $updated = 0;
194 $deleted = 0;
195 $errors = 0;
196
97376662 197 // We will most certainly need extra time and memory to process big files.
3ef7279f 198 core_php_time_limit::raise();
97376662
FM
199 raise_memory_limit(MEMORY_EXTRA);
200
9a7cd639
FM
201 // Loop over the CSV lines.
202 while ($line = $this->cir->next()) {
203 $this->linenb++;
5dcd7e28 204 $total++;
9a7cd639
FM
205
206 $data = $this->parse_line($line);
1d1898ac 207 $course = $this->get_course($data);
9a7cd639
FM
208 if ($course->prepare()) {
209 $course->proceed();
5dcd7e28
FM
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
1e3e4efd 220 $data = array_merge($data, $course->get_data(), array('id' => $course->get_id()));
853b6cff 221 $tracker->output($this->linenb, true, $status, $data);
9a7cd639 222 } else {
5dcd7e28 223 $errors++;
853b6cff 224 $tracker->output($this->linenb, false, $course->get_errors(), $data);
9a7cd639
FM
225 }
226 }
227
5b2f7718 228 $tracker->finish();
5dcd7e28 229 $tracker->results($total, $created, $updated, $deleted, $errors);
9a7cd639
FM
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 */
1d1898ac 238 protected function get_course($data) {
9a7cd639
FM
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
1d1898ac
FM
250 /**
251 * Return the errors.
252 *
253 * @return array
254 */
255 public function get_errors() {
256 return $this->errors;
257 }
258
9a7cd639
FM
259 /**
260 * Get the directory of the object to restore.
261 *
ef844148 262 * @return string subdirectory in $CFG->backuptempdir/...
9a7cd639 263 */
1d1898ac 264 protected function get_restore_content_dir() {
9a7cd639
FM
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
1d1898ac
FM
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
9a7cd639
FM
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
1d1898ac
FM
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.
5dcd7e28 323 * @param object $tracker the output tracker to use.
1d1898ac
FM
324 * @return array of preview data.
325 */
5dcd7e28 326 public function preview($rows = 10, $tracker = null) {
1d1898ac
FM
327 if ($this->processstarted) {
328 throw new coding_exception('Process has already been started');
329 }
330 $this->processstarted = true;
5dcd7e28
FM
331
332 if (empty($tracker)) {
333 $tracker = new tool_uploadcourse_tracker(tool_uploadcourse_tracker::NO_OUTPUT);
334 }
5b2f7718 335 $tracker->start();
1d1898ac 336
97376662 337 // We might need extra time and memory depending on the number of rows to preview.
3ef7279f 338 core_php_time_limit::raise();
97376662
FM
339 raise_memory_limit(MEMORY_EXTRA);
340
1d1898ac
FM
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) {
5b2f7718
FM
349 $tracker->output($this->linenb, $result, $course->get_errors(), $data);
350 } else {
351 $tracker->output($this->linenb, $result, $course->get_statuses(), $data);
1d1898ac
FM
352 }
353 $row = $data;
354 $preview[$this->linenb] = $row;
355 }
356
5b2f7718 357 $tracker->finish();
1d1898ac
FM
358
359 return $preview;
360 }
361
1d1898ac
FM
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 }
9a7cd639 386}