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