Revert "Merge branch 'MDL-43127-master-fixup' of https://github.com/FMCorz/moodle"
[moodle.git] / admin / tool / uploadcourse / classes / helper.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 the helper 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 . '/coursecatlib.php');
8c313d1a 27require_once($CFG->dirroot . '/cache/lib.php');
e0164eb3
FM
28require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
29require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
9a7cd639
FM
30
31/**
32 * Class containing a set of helpers.
3f57c87e
FM
33 *
34 * @package tool_uploadcourse
35 * @copyright 2013 Frédéric Massart
36 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
9a7cd639
FM
37 */
38class tool_uploadcourse_helper {
39
9a7cd639
FM
40 /**
41 * Generate a shortname based on a template.
42 *
43 * @param array|object $data course data.
44 * @param string $templateshortname template of shortname.
f802b04f 45 * @return null|string shortname based on the template, or null when an error occured.
9a7cd639
FM
46 */
47 public static function generate_shortname($data, $templateshortname) {
f802b04f 48 if (empty($templateshortname) && !is_numeric($templateshortname)) {
9a7cd639
FM
49 return null;
50 }
51 if (strpos($templateshortname, '%') === false) {
52 return $templateshortname;
53 }
54
55 $course = (object) $data;
9a7cd639
FM
56 $fullname = isset($course->fullname) ? $course->fullname : '';
57 $idnumber = isset($course->idnumber) ? $course->idnumber : '';
58
f802b04f 59 $callback = partial(array('tool_uploadcourse_helper', 'generate_shortname_callback'), $fullname, $idnumber);
9a7cd639
FM
60 $result = preg_replace_callback('/(?<!%)%([+~-])?(\d)*([fi])/', $callback, $templateshortname);
61
62 if (!is_null($result)) {
63 $result = clean_param($result, PARAM_TEXT);
64 }
65
f802b04f
FM
66 if (empty($result) && !is_numeric($result)) {
67 $result = null;
68 }
69
9a7cd639
FM
70 return $result;
71 }
72
73 /**
74 * Callback used when generating a shortname based on a template.
75 *
9a7cd639
FM
76 * @param string $fullname full name.
77 * @param string $idnumber ID number.
78 * @param array $block result from preg_replace_callback.
79 * @return string
80 */
f802b04f 81 public static function generate_shortname_callback($fullname, $idnumber, $block) {
9a7cd639
FM
82 switch ($block[3]) {
83 case 'f':
84 $repl = $fullname;
85 break;
86 case 'i':
87 $repl = $idnumber;
88 break;
89 default:
90 return $block[0];
91 }
92
93 switch ($block[1]) {
94 case '+':
2f1e464a 95 $repl = core_text::strtoupper($repl);
9a7cd639
FM
96 break;
97 case '-':
2f1e464a 98 $repl = core_text::strtolower($repl);
9a7cd639
FM
99 break;
100 case '~':
2f1e464a 101 $repl = core_text::strtotitle($repl);
9a7cd639
FM
102 break;
103 }
104
105 if (!empty($block[2])) {
2f1e464a 106 $repl = core_text::substr($repl, 0, $block[2]);
9a7cd639
FM
107 }
108
109 return $repl;
110 }
111
112 /**
113 * Return the available course formats.
114 *
9a7cd639
FM
115 * @return array
116 */
117 public static function get_course_formats() {
41525e55 118 return array_keys(core_component::get_plugin_list('format'));
9a7cd639
FM
119 }
120
121 /**
122 * Extract enrolment data from passed data.
123 *
124 * Constructs an array of methods, and their options:
125 * array(
126 * 'method1' => array(
127 * 'option1' => value,
128 * 'option2' => value
129 * ),
130 * 'method2' => array(
131 * 'option1' => value,
132 * 'option2' => value
133 * )
134 * )
135 *
136 * @param array $data data to extract the enrolment data from.
61380ed2 137 * @param array $errors will be populated with errors found.
9a7cd639
FM
138 * @return array
139 */
61380ed2 140 public static function get_enrolment_data($data, &$errors = array()) {
9a7cd639
FM
141 $enrolmethods = array();
142 $enroloptions = array();
143 foreach ($data as $field => $value) {
144
145 // Enrolmnent data.
146 $matches = array();
147 if (preg_match('/^enrolment_(\d+)(_(.+))?$/', $field, $matches)) {
148 $key = $matches[1];
149 if (!isset($enroloptions[$key])) {
150 $enroloptions[$key] = array();
151 }
152 if (empty($matches[3])) {
153 $enrolmethods[$key] = $value;
154 } else {
155 $enroloptions[$key][$matches[3]] = $value;
156 }
157 }
158 }
159
160 // Combining enrolment methods and their options in a single array.
161 $enrolmentdata = array();
61380ed2
FM
162 $unknownmethods = array();
163 $methodsnotsupported = array();
9a7cd639
FM
164 if (!empty($enrolmethods)) {
165 $enrolmentplugins = self::get_enrolment_plugins();
166 foreach ($enrolmethods as $key => $method) {
e6f5a4b2 167 if (!array_key_exists($method, $enrolmentplugins)) {
61380ed2
FM
168 // Unknown enrolment method.
169 $unknownmethods[] = $method;
9a7cd639
FM
170 continue;
171 }
172 $enrolmentdata[$enrolmethods[$key]] = $enroloptions[$key];
173 }
174 }
61380ed2
FM
175
176 // Logging errors related to enrolment methods.
177 if (!empty($unknownmethods)) {
178 $errors['unknownenrolmentmethods'] = new lang_string('unknownenrolmentmethods', 'tool_uploadcourse',
179 implode(', ', $unknownmethods));
180 }
181
9a7cd639
FM
182 return $enrolmentdata;
183 }
184
185 /**
186 * Return the enrolment plugins.
187 *
188 * The result is cached for faster execution.
189 *
190 * @return array
191 */
192 public static function get_enrolment_plugins() {
8c313d1a
FM
193 $cache = cache::make('tool_uploadcourse', 'helper');
194 if (($enrol = $cache->get('enrol')) === false) {
195 $enrol = enrol_get_plugins(false);
196 $cache->set('enrol', $enrol);
9a7cd639 197 }
8c313d1a 198 return $enrol;
9a7cd639
FM
199 }
200
201 /**
202 * Get the restore content tempdir.
203 *
204 * The tempdir is the sub directory in which the backup has been extracted.
d90b2eb9
FM
205 *
206 * This caches the result for better performance, but $CFG->keeptempdirectoriesonbackup
207 * needs to be enabled, otherwise the cache is ignored.
9a7cd639
FM
208 *
209 * @param string $backupfile path to a backup file.
210 * @param string $shortname shortname of a course.
1d1898ac 211 * @param array $errors will be populated with errors found.
9a7cd639
FM
212 * @return string|false false when the backup couldn't retrieved.
213 */
1d1898ac 214 public static function get_restore_content_dir($backupfile = null, $shortname = null, &$errors = array()) {
9a7cd639
FM
215 global $CFG, $DB, $USER;
216
217 $cachekey = null;
218 if (!empty($backupfile)) {
219 $backupfile = realpath($backupfile);
d90b2eb9
FM
220 if (empty($backupfile) || !is_readable($backupfile)) {
221 $errors['cannotreadbackupfile'] = new lang_string('cannotreadbackupfile', 'tool_uploadcourse');
222 return false;
223 }
8c313d1a 224 $cachekey = 'backup_path:' . $backupfile;
9a7cd639 225 } else if (!empty($shortname) || is_numeric($shortname)) {
8c313d1a 226 $cachekey = 'backup_sn:' . $shortname;
9a7cd639
FM
227 }
228
229 if (empty($cachekey)) {
230 return false;
231 }
232
d90b2eb9
FM
233 // If $CFG->keeptempdirectoriesonbackup is not set to true, any restore happening would
234 // automatically delete the backup directory... causing the cache to return an unexisting directory.
235 $usecache = !empty($CFG->keeptempdirectoriesonbackup);
236 if ($usecache) {
237 $cache = cache::make('tool_uploadcourse', 'helper');
238 }
239
240 // If we don't use the cache, or if we do and not set, or the directory doesn't exist any more.
241 if (!$usecache || (($backupid = $cache->get($cachekey)) === false || !is_dir("$CFG->tempdir/backup/$backupid"))) {
242
243 // Use null instead of false because it would consider that the cache key has not been set.
244 $backupid = null;
245
1d1898ac 246 if (!empty($backupfile)) {
d90b2eb9
FM
247 // Extracting the backup file.
248 $packer = get_file_packer('application/vnd.moodle.backup');
249 $backupid = restore_controller::get_tempdir_name(SITEID, $USER->id);
250 $path = "$CFG->tempdir/backup/$backupid/";
251 $result = $packer->extract_to_pathname($backupfile, $path);
252 if (!$result) {
253 $errors['invalidbackupfile'] = new lang_string('invalidbackupfile', 'tool_uploadcourse');
1d1898ac 254 }
9a7cd639
FM
255 } else if (!empty($shortname) || is_numeric($shortname)) {
256 // Creating restore from an existing course.
257 $courseid = $DB->get_field('course', 'id', array('shortname' => $shortname), IGNORE_MISSING);
258 if (!empty($courseid)) {
259 $bc = new backup_controller(backup::TYPE_1COURSE, $courseid, backup::FORMAT_MOODLE,
260 backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id);
261 $bc->execute_plan();
262 $backupid = $bc->get_backupid();
263 $bc->destroy();
1d1898ac
FM
264 } else {
265 $errors['coursetorestorefromdoesnotexist'] =
266 new lang_string('coursetorestorefromdoesnotexist', 'tool_uploadcourse');
9a7cd639
FM
267 }
268 }
8c313d1a 269
d90b2eb9
FM
270 if ($usecache) {
271 $cache->set($cachekey, $backupid);
272 }
9a7cd639
FM
273 }
274
d90b2eb9
FM
275 if ($backupid === null) {
276 $backupid = false;
277 }
8c313d1a 278 return $backupid;
9a7cd639
FM
279 }
280
281 /**
282 * Return the role IDs.
283 *
284 * The result is cached for faster execution.
285 *
9a7cd639
FM
286 * @return array
287 */
8c313d1a
FM
288 public static function get_role_ids() {
289 $cache = cache::make('tool_uploadcourse', 'helper');
290 if (($roles = $cache->get('roles')) === false) {
291 $roles = array();
292 $rolesraw = get_all_roles();
293 foreach ($rolesraw as $role) {
294 $roles[$role->shortname] = $role->id;
9a7cd639 295 }
8c313d1a 296 $cache->set('roles', $roles);
9a7cd639 297 }
8c313d1a 298 return $roles;
9a7cd639
FM
299 }
300
301 /**
302 * Get the role renaming data from the passed data.
303 *
304 * @param array $data data to extract the names from.
1d1898ac 305 * @param array $errors will be populated with errors found.
9a7cd639
FM
306 * @return array where the key is the role_<id>, the value is the new name.
307 */
1d1898ac 308 public static function get_role_names($data, &$errors = array()) {
9a7cd639
FM
309 $rolenames = array();
310 $rolesids = self::get_role_ids();
1d1898ac 311 $invalidroles = array();
9a7cd639
FM
312 foreach ($data as $field => $value) {
313
314 $matches = array();
315 if (preg_match('/^role_(.+)?$/', $field, $matches)) {
316 if (!isset($rolesids[$matches[1]])) {
1d1898ac 317 $invalidroles[] = $matches[1];
9a7cd639
FM
318 continue;
319 }
320 $rolenames['role_' . $rolesids[$matches[1]]] = $value;
321 }
322
323 }
324
1d1898ac
FM
325 if (!empty($invalidroles)) {
326 $errors['invalidroles'] = new lang_string('invalidroles', 'tool_uploadcourse', implode(', ', $invalidroles));
327 }
328
9a7cd639
FM
329 // Roles names.
330 return $rolenames;
331 }
332
333 /**
334 * Helper to increment an ID number.
335 *
336 * This first checks if the ID number is in use.
337 *
338 * @param string $idnumber ID number to increment.
339 * @return string new ID number.
340 */
341 public static function increment_idnumber($idnumber) {
342 global $DB;
343 while ($DB->record_exists('course', array('idnumber' => $idnumber))) {
344 $matches = array();
345 if (!preg_match('/(.*?)([0-9]+)$/', $idnumber, $matches)) {
346 $newidnumber = $idnumber . '_2';
347 } else {
348 $newidnumber = $matches[1] . ((int) $matches[2] + 1);
349 }
350 $idnumber = $newidnumber;
351 }
352 return $idnumber;
353 }
354
355 /**
356 * Helper to increment a shortname.
357 *
358 * This considers that the shortname passed has to be incremented.
359 *
360 * @param string $shortname shortname to increment.
361 * @return string new shortname.
362 */
363 public static function increment_shortname($shortname) {
364 global $DB;
365 do {
366 $matches = array();
367 if (!preg_match('/(.*?)([0-9]+)$/', $shortname, $matches)) {
368 $newshortname = $shortname . '_2';
369 } else {
370 $newshortname = $matches[1] . ($matches[2]+1);
371 }
372 $shortname = $newshortname;
373 } while ($DB->record_exists('course', array('shortname' => $shortname)));
374 return $shortname;
375 }
376
377 /**
378 * Resolve a category based on the data passed.
379 *
380 * Key accepted are:
381 * - category, which is supposed to be a category ID.
382 * - category_idnumber
383 * - category_path, array of categories from parent to child.
384 *
385 * @param array $data to resolve the category from.
1d1898ac 386 * @param array $errors will be populated with errors found.
9a7cd639
FM
387 * @return int category ID.
388 */
1d1898ac 389 public static function resolve_category($data, &$errors = array()) {
9a7cd639
FM
390 $catid = null;
391
392 if (!empty($data['category'])) {
1d1898ac 393 $category = coursecat::get((int) $data['category'], IGNORE_MISSING);
8383903f 394 if (!empty($category) && !empty($category->id)) {
9a7cd639 395 $catid = $category->id;
1d1898ac
FM
396 } else {
397 $errors['couldnotresolvecatgorybyid'] =
398 new lang_string('couldnotresolvecatgorybyid', 'tool_uploadcourse');
9a7cd639
FM
399 }
400 }
401
402 if (empty($catid) && !empty($data['category_idnumber'])) {
403 $catid = self::resolve_category_by_idnumber($data['category_idnumber']);
8383903f
FM
404 if (empty($catid)) {
405 $errors['couldnotresolvecatgorybyidnumber'] =
406 new lang_string('couldnotresolvecatgorybyidnumber', 'tool_uploadcourse');
407 }
9a7cd639 408 }
9a7cd639
FM
409 if (empty($catid) && !empty($data['category_path'])) {
410 $catid = self::resolve_category_by_path(explode(' / ', $data['category_path']));
8383903f
FM
411 if (empty($catid)) {
412 $errors['couldnotresolvecatgorybypath'] =
413 new lang_string('couldnotresolvecatgorybypath', 'tool_uploadcourse');
414 }
9a7cd639
FM
415 }
416
417 return $catid;
418 }
419
420 /**
421 * Resolve a category by ID number.
422 *
423 * @param string $idnumber category ID number.
424 * @return int category ID.
425 */
426 public static function resolve_category_by_idnumber($idnumber) {
427 global $DB;
8c313d1a
FM
428 $cache = cache::make('tool_uploadcourse', 'helper');
429 $cachekey = 'cat_idn_' . $idnumber;
430 if (($id = $cache->get($cachekey)) === false) {
9a7cd639
FM
431 $params = array('idnumber' => $idnumber);
432 $id = $DB->get_field_select('course_categories', 'id', 'idnumber = :idnumber', $params, IGNORE_MISSING);
8c313d1a
FM
433
434 // Little hack to be able to differenciate between the cache not set and a category not found.
435 if ($id === false) {
436 $id = -1;
437 }
438
439 $cache->set($cachekey, $id);
440 }
441
442 // Little hack to be able to differenciate between the cache not set and a category not found.
443 if ($id == -1) {
444 $id = false;
9a7cd639 445 }
8c313d1a
FM
446
447 return $id;
9a7cd639
FM
448 }
449
450 /**
451 * Resolve a category by path.
452 *
453 * @param array $path category names indexed from parent to children.
454 * @return int category ID.
455 */
456 public static function resolve_category_by_path(array $path) {
457 global $DB;
8c313d1a
FM
458 $cache = cache::make('tool_uploadcourse', 'helper');
459 $cachekey = 'cat_path_' . serialize($path);
460 if (($id = $cache->get($cachekey)) === false) {
9a7cd639
FM
461 $parent = 0;
462 $sql = 'name = :name AND parent = :parent';
463 while ($name = array_shift($path)) {
464 $params = array('name' => $name, 'parent' => $parent);
465 if ($records = $DB->get_records_select('course_categories', $sql, $params, null, 'id, parent')) {
466 if (count($records) > 1) {
467 // Too many records with the same name!
8c313d1a 468 $id = -1;
9a7cd639
FM
469 break;
470 }
471 $record = reset($records);
472 $id = $record->id;
473 $parent = $record->id;
474 } else {
475 // Not found.
8c313d1a 476 $id = -1;
9a7cd639
FM
477 break;
478 }
479 }
8c313d1a
FM
480 $cache->set($cachekey, $id);
481 }
482
483 // We save -1 when the category has not been found to be able to know if the cache was set.
484 if ($id == -1) {
485 $id = false;
9a7cd639 486 }
8c313d1a 487 return $id;
9a7cd639
FM
488 }
489
490}