MDL-52523 backup of subplugins: add missing global
[moodle.git] / backup / util / plan / restore_structure_step.class.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
18 /**
19  * @package moodlecore
20  * @subpackage backup-plan
21  * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
22  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 /**
26  * Abstract class defining the needed stuff to restore one xml file
27  *
28  * TODO: Finish phpdocs
29  */
30 abstract class restore_structure_step extends restore_step {
32     protected $filename; // Name of the file to be parsed
33     protected $contentprocessor; // xml parser processor being used
34                                  // (need it here, apart from parser
35                                  // thanks to serialized data to process -
36                                  // say thanks to blocks!)
37     protected $pathelements;  // Array of pathelements to process
38     protected $elementsoldid; // Array to store last oldid used on each element
39     protected $elementsnewid; // Array to store last newid used on each element
41     protected $pathlock;      // Path currently locking processing of children
43     const SKIP_ALL_CHILDREN = -991399; // To instruct the dispatcher about to ignore
44                                        // all children below path processor returning it
46     /**
47      * Constructor - instantiates one object of this class
48      */
49     public function __construct($name, $filename, $task = null) {
50         if (!is_null($task) && !($task instanceof restore_task)) {
51             throw new restore_step_exception('wrong_restore_task_specified');
52         }
53         $this->filename = $filename;
54         $this->contentprocessor = null;
55         $this->pathelements = array();
56         $this->elementsoldid = array();
57         $this->elementsnewid = array();
58         $this->pathlock = null;
59         parent::__construct($name, $task);
60     }
62     final public function execute() {
64         if (!$this->execute_condition()) { // Check any condition to execute this
65             return;
66         }
68         $fullpath = $this->task->get_taskbasepath();
70         // We MUST have one fullpath here, else, error
71         if (empty($fullpath)) {
72             throw new restore_step_exception('restore_structure_step_undefined_fullpath');
73         }
75         // Append the filename to the fullpath
76         $fullpath = rtrim($fullpath, '/') . '/' . $this->filename;
78         // And it MUST exist
79         if (!file_exists($fullpath)) { // Shouldn't happen ever, but...
80             throw new restore_step_exception('missing_moodle_backup_xml_file', $fullpath);
81         }
83         // Get restore_path elements array adapting and preparing it for processing
84         $structure = $this->define_structure();
85         if (!is_array($structure)) {
86             throw new restore_step_exception('restore_step_structure_not_array', $this->get_name());
87         }
88         $this->prepare_pathelements($structure);
90         // Create parser and processor
91         $xmlparser = new progressive_parser();
92         $xmlparser->set_file($fullpath);
93         $xmlprocessor = new restore_structure_parser_processor($this->task->get_courseid(), $this);
94         $this->contentprocessor = $xmlprocessor; // Save the reference to the contentprocessor
95                                                  // as far as we are going to need it out
96                                                  // from parser (blame serialized data!)
97         $xmlparser->set_processor($xmlprocessor);
99         // Add pathelements to processor
100         foreach ($this->pathelements as $element) {
101             $xmlprocessor->add_path($element->get_path(), $element->is_grouped());
102         }
104         // Set up progress tracking.
105         $progress = $this->get_task()->get_progress();
106         $progress->start_progress($this->get_name(), \core\progress\base::INDETERMINATE);
107         $xmlparser->set_progress($progress);
109         // And process it, dispatch to target methods in step will start automatically
110         $xmlparser->process();
112         // Have finished, launch the after_execute method of all the processing objects
113         $this->launch_after_execute_methods();
114         $progress->end_progress();
115     }
117     /**
118      * Receive one chunk of information form the xml parser processor and
119      * dispatch it, following the naming rules
120      */
121     final public function process($data) {
122         if (!array_key_exists($data['path'], $this->pathelements)) { // Incorrect path, must not happen
123             throw new restore_step_exception('restore_structure_step_missing_path', $data['path']);
124         }
125         $element = $this->pathelements[$data['path']];
126         $object = $element->get_processing_object();
127         $method = $element->get_processing_method();
128         $rdata = null;
129         if (empty($object)) { // No processing object defined
130             throw new restore_step_exception('restore_structure_step_missing_pobject', $object);
131         }
132         // Release the lock if we aren't anymore within children of it
133         if (!is_null($this->pathlock) and strpos($data['path'], $this->pathlock) === false) {
134             $this->pathlock = null;
135         }
136         if (is_null($this->pathlock)) { // Only dispatch if there isn't any lock
137             $rdata = $object->$method($data['tags']); // Dispatch to proper object/method
138         }
140         // If the dispatched method returns SKIP_ALL_CHILDREN, we grab current path in order to
141         // lock dispatching to any children
142         if ($rdata === self::SKIP_ALL_CHILDREN) {
143             // Check we haven't any previous lock
144             if (!is_null($this->pathlock)) {
145                 throw new restore_step_exception('restore_structure_step_already_skipping', $data['path']);
146             }
147             // Set the lock
148             $this->pathlock = $data['path'] . '/'; // Lock everything below current path
150         // Continue with normal processing of return values
151         } else if ($rdata !== null) { // If the method has returned any info, set element data to it
152             $element->set_data($rdata);
153         } else {               // Else, put the original parsed data
154             $element->set_data($data);
155         }
156     }
158     /**
159      * To send ids pairs to backup_ids_table and to store them into paths
160      *
161      * This method will send the given itemname and old/new ids to the
162      * backup_ids_temp table, and, at the same time, will save the new id
163      * into the corresponding restore_path_element for easier access
164      * by children. Also will inject the known old context id for the task
165      * in case it's going to be used for restoring files later
166      */
167     public function set_mapping($itemname, $oldid, $newid, $restorefiles = false, $filesctxid = null, $parentid = null) {
168         if ($restorefiles && $parentid) {
169             throw new restore_step_exception('set_mapping_cannot_specify_both_restorefiles_and_parentitemid');
170         }
171         // If we haven't specified one context for the files, use the task one
172         if (is_null($filesctxid)) {
173             $parentitemid = $restorefiles ? $this->task->get_old_contextid() : null;
174         } else { // Use the specified one
175             $parentitemid = $restorefiles ? $filesctxid : null;
176         }
177         // We have passed one explicit parentid, apply it
178         $parentitemid = !is_null($parentid) ? $parentid : $parentitemid;
180         // Let's call the low level one
181         restore_dbops::set_backup_ids_record($this->get_restoreid(), $itemname, $oldid, $newid, $parentitemid);
182         // Now, if the itemname matches any pathelement->name, store the latest $newid
183         if (array_key_exists($itemname, $this->elementsoldid)) { // If present in  $this->elementsoldid, is valid, put both ids
184             $this->elementsoldid[$itemname] = $oldid;
185             $this->elementsnewid[$itemname] = $newid;
186         }
187     }
189     /**
190      * Returns the latest (parent) old id mapped by one pathelement
191      */
192     public function get_old_parentid($itemname) {
193         return array_key_exists($itemname, $this->elementsoldid) ? $this->elementsoldid[$itemname] : null;
194     }
196     /**
197      * Returns the latest (parent) new id mapped by one pathelement
198      */
199     public function get_new_parentid($itemname) {
200         return array_key_exists($itemname, $this->elementsnewid) ? $this->elementsnewid[$itemname] : null;
201     }
203     /**
204      * Return the new id of a mapping for the given itemname
205      *
206      * @param string $itemname the type of item
207      * @param int $oldid the item ID from the backup
208      * @param mixed $ifnotfound what to return if $oldid wasnt found. Defaults to false
209      */
210     public function get_mappingid($itemname, $oldid, $ifnotfound = false) {
211         $mapping = $this->get_mapping($itemname, $oldid);
212         return $mapping ? $mapping->newitemid : $ifnotfound;
213     }
215     /**
216      * Return the complete mapping from the given itemname, itemid
217      */
218     public function get_mapping($itemname, $oldid) {
219         return restore_dbops::get_backup_ids_record($this->get_restoreid(), $itemname, $oldid);
220     }
222     /**
223      * Add all the existing file, given their component and filearea and one backup_ids itemname to match with
224      */
225     public function add_related_files($component, $filearea, $mappingitemname, $filesctxid = null, $olditemid = null) {
226         // If the current progress object is set up and ready to receive
227         // indeterminate progress, then use it, otherwise don't. (This check is
228         // just in case this function is ever called from somewhere not within
229         // the execute() method here, which does set up progress like this.)
230         $progress = $this->get_task()->get_progress();
231         if (!$progress->is_in_progress_section() ||
232                 $progress->get_current_max() !== \core\progress\base::INDETERMINATE) {
233             $progress = null;
234         }
236         $filesctxid = is_null($filesctxid) ? $this->task->get_old_contextid() : $filesctxid;
237         $results = restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), $component,
238                 $filearea, $filesctxid, $this->task->get_userid(), $mappingitemname, $olditemid, null, false,
239                 $progress);
240         $resultstoadd = array();
241         foreach ($results as $result) {
242             $this->log($result->message, $result->level);
243             $resultstoadd[$result->code] = true;
244         }
245         $this->task->add_result($resultstoadd);
246     }
248     /**
249      * As far as restore structure steps are implementing restore_plugin stuff, they need to
250      * have the parent task available for wrapping purposes (get course/context....)
251      * @return restore_task|null
252      */
253     public function get_task() {
254         return $this->task;
255     }
257 // Protected API starts here
259     /**
260      * Add plugin structure to any element in the structure restore tree
261      *
262      * @param string $plugintype type of plugin as defined by core_component::get_plugin_types()
263      * @param restore_path_element $element element in the structure restore tree that
264      *                                       we are going to add plugin information to
265      */
266     protected function add_plugin_structure($plugintype, $element) {
268         global $CFG;
270         // Check the requested plugintype is a valid one
271         if (!array_key_exists($plugintype, core_component::get_plugin_types($plugintype))) {
272              throw new restore_step_exception('incorrect_plugin_type', $plugintype);
273         }
275         // Get all the restore path elements, looking across all the plugin dirs
276         $pluginsdirs = core_component::get_plugin_list($plugintype);
277         foreach ($pluginsdirs as $name => $pluginsdir) {
278             // We need to add also backup plugin classes on restore, they may contain
279             // some stuff used both in backup & restore
280             $backupclassname = 'backup_' . $plugintype . '_' . $name . '_plugin';
281             $backupfile = $pluginsdir . '/backup/moodle2/' . $backupclassname . '.class.php';
282             if (file_exists($backupfile)) {
283                 require_once($backupfile);
284             }
285             // Now add restore plugin classes and prepare stuff
286             $restoreclassname = 'restore_' . $plugintype . '_' . $name . '_plugin';
287             $restorefile = $pluginsdir . '/backup/moodle2/' . $restoreclassname . '.class.php';
288             if (file_exists($restorefile)) {
289                 require_once($restorefile);
290                 $restoreplugin = new $restoreclassname($plugintype, $name, $this);
291                 // Add plugin paths to the step
292                 $this->prepare_pathelements($restoreplugin->define_plugin_structure($element));
293             }
294         }
295     }
297     /**
298      * Add subplugin structure for a given plugin to any element in the structure restore tree
299      *
300      * This method allows the injection of subplugins (of a specific plugin) parsing and proccessing
301      * to any element in the restore structure.
302      *
303      * NOTE: Initially subplugins were only available for activities (mod), so only the
304      * {@link restore_activity_structure_step} class had support for them, always
305      * looking for /mod/modulenanme subplugins. This new method is a generalization of the
306      * existing one for activities, supporting all subplugins injecting information everywhere.
307      *
308      * @param string $subplugintype type of subplugin as defined in plugin's db/subplugins.php.
309      * @param restore_path_element $element element in the structure restore tree that
310      *                              we are going to add subplugin information to.
311      * @param string $plugintype type of the plugin.
312      * @param string $pluginname name of the plugin.
313      * @return void
314      */
315     protected function add_subplugin_structure($subplugintype, $element, $plugintype = null, $pluginname = null) {
316         global $CFG;
317         // This global declaration is required, because where we do require_once($backupfile);
318         // That file may in turn try to do require_once($CFG->dirroot ...).
319         // That worked in the past, we should keep it working.
321         // Verify if this is a BC call for an activity restore. See NOTE above for this special case.
322         if ($plugintype === null and $pluginname === null) {
323             $plugintype = 'mod';
324             $pluginname = $this->task->get_modulename();
325             // TODO: Once all the calls have been changed to add both not null plugintype and pluginname, add a debugging here.
326         }
328         // Check the requested plugintype is a valid one.
329         if (!array_key_exists($plugintype, core_component::get_plugin_types())) {
330             throw new restore_step_exception('incorrect_plugin_type', $plugintype);
331         }
333         // Check the requested pluginname, for the specified plugintype, is a valid one.
334         if (!array_key_exists($pluginname, core_component::get_plugin_list($plugintype))) {
335             throw new restore_step_exception('incorrect_plugin_name', array($plugintype, $pluginname));
336         }
338         // Check the requested subplugintype is a valid one.
339         $subpluginsfile = core_component::get_component_directory($plugintype . '_' . $pluginname) . '/db/subplugins.php';
340         if (!file_exists($subpluginsfile)) {
341             throw new restore_step_exception('plugin_missing_subplugins_php_file', array($plugintype, $pluginname));
342         }
343         include($subpluginsfile);
344         if (!array_key_exists($subplugintype, $subplugins)) {
345              throw new restore_step_exception('incorrect_subplugin_type', $subplugintype);
346         }
348         // Every subplugin optionally can have a common/parent subplugin
349         // class for shared stuff.
350         $parentclass = 'restore_' . $plugintype . '_' . $pluginname . '_' . $subplugintype . '_subplugin';
351         $parentfile = core_component::get_component_directory($plugintype . '_' . $pluginname) .
352             '/backup/moodle2/' . $parentclass . '.class.php';
353         if (file_exists($parentfile)) {
354             require_once($parentfile);
355         }
357         // Get all the restore path elements, looking across all the subplugin dirs.
358         $subpluginsdirs = core_component::get_plugin_list($subplugintype);
359         foreach ($subpluginsdirs as $name => $subpluginsdir) {
360             $classname = 'restore_' . $subplugintype . '_' . $name . '_subplugin';
361             $restorefile = $subpluginsdir . '/backup/moodle2/' . $classname . '.class.php';
362             if (file_exists($restorefile)) {
363                 require_once($restorefile);
364                 $restoresubplugin = new $classname($subplugintype, $name, $this);
365                 // Add subplugin paths to the step.
366                 $this->prepare_pathelements($restoresubplugin->define_subplugin_structure($element));
367             }
368         }
369     }
371     /**
372      * Launch all the after_execute methods present in all the processing objects
373      *
374      * This method will launch all the after_execute methods that can be defined
375      * both in restore_plugin and restore_structure_step classes
376      *
377      * For restore_plugin classes the name of the method to be executed will be
378      * "after_execute_" + connection point (as far as can be multiple connection
379      * points in the same class)
380      *
381      * For restore_structure_step classes is will be, simply, "after_execute". Note
382      * that this is executed *after* the plugin ones
383      */
384     protected function launch_after_execute_methods() {
385         $alreadylaunched = array(); // To avoid multiple executions
386         foreach ($this->pathelements as $key => $pathelement) {
387             // Get the processing object
388             $pobject = $pathelement->get_processing_object();
389             // Skip null processors (child of grouped ones for sure)
390             if (is_null($pobject)) {
391                 continue;
392             }
393             // Skip restore structure step processors (this)
394             if ($pobject instanceof restore_structure_step) {
395                 continue;
396             }
397             // Skip already launched processing objects
398             if (in_array($pobject, $alreadylaunched, true)) {
399                 continue;
400             }
401             // Add processing object to array of launched ones
402             $alreadylaunched[] = $pobject;
403             // If the processing object has support for
404             // launching after_execute methods, use it
405             if (method_exists($pobject, 'launch_after_execute_methods')) {
406                 $pobject->launch_after_execute_methods();
407             }
408         }
409         // Finally execute own (restore_structure_step) after_execute method
410         $this->after_execute();
412     }
414     /**
415      * Launch all the after_restore methods present in all the processing objects
416      *
417      * This method will launch all the after_restore methods that can be defined
418      * both in restore_plugin class
419      *
420      * For restore_plugin classes the name of the method to be executed will be
421      * "after_restore_" + connection point (as far as can be multiple connection
422      * points in the same class)
423      */
424     public function launch_after_restore_methods() {
425         $alreadylaunched = array(); // To avoid multiple executions
426         foreach ($this->pathelements as $pathelement) {
427             // Get the processing object
428             $pobject = $pathelement->get_processing_object();
429             // Skip null processors (child of grouped ones for sure)
430             if (is_null($pobject)) {
431                 continue;
432             }
433             // Skip restore structure step processors (this)
434             if ($pobject instanceof restore_structure_step) {
435                 continue;
436             }
437             // Skip already launched processing objects
438             if (in_array($pobject, $alreadylaunched, true)) {
439                 continue;
440             }
441             // Add processing object to array of launched ones
442             $alreadylaunched[] = $pobject;
443             // If the processing object has support for
444             // launching after_restore methods, use it
445             if (method_exists($pobject, 'launch_after_restore_methods')) {
446                 $pobject->launch_after_restore_methods();
447             }
448         }
449         // Finally execute own (restore_structure_step) after_restore method
450         $this->after_restore();
451     }
453     /**
454      * This method will be executed after the whole structure step have been processed
455      *
456      * After execution method for code needed to be executed after the whole structure
457      * has been processed. Useful for cleaning tasks, files process and others. Simply
458      * overwrite in in your steps if needed
459      */
460     protected function after_execute() {
461         // do nothing by default
462     }
464     /**
465      * This method will be executed after the rest of the restore has been processed.
466      *
467      * Use if you need to update IDs based on things which are restored after this
468      * step has completed.
469      */
470     protected function after_restore() {
471         // do nothing by default
472     }
474     /**
475      * Prepare the pathelements for processing, looking for duplicates, applying
476      * processing objects and other adjustments
477      */
478     protected function prepare_pathelements($elementsarr) {
480         // First iteration, push them to new array, indexed by name
481         // detecting duplicates in names or paths
482         $names = array();
483         $paths = array();
484         foreach($elementsarr as $element) {
485             if (!$element instanceof restore_path_element) {
486                 throw new restore_step_exception('restore_path_element_wrong_class', get_class($element));
487             }
488             if (array_key_exists($element->get_name(), $names)) {
489                 throw new restore_step_exception('restore_path_element_name_alreadyexists', $element->get_name());
490             }
491             if (array_key_exists($element->get_path(), $paths)) {
492                 throw new restore_step_exception('restore_path_element_path_alreadyexists', $element->get_path());
493             }
494             $names[$element->get_name()] = true;
495             $paths[$element->get_path()] = $element;
496         }
497         // Now, for each element not having one processing object, if
498         // not child of grouped element, assign $this (the step itself) as processing element
499         // Note method must exist or we'll get one @restore_path_element_exception
500         foreach($paths as $key => $pelement) {
501             if ($pelement->get_processing_object() === null && !$this->grouped_parent_exists($pelement, $paths)) {
502                 $paths[$key]->set_processing_object($this);
503             }
504             // Populate $elementsoldid and $elementsoldid based on available pathelements
505             $this->elementsoldid[$pelement->get_name()] = null;
506             $this->elementsnewid[$pelement->get_name()] = null;
507         }
508         // Done, add them to pathelements (dupes by key - path - are discarded)
509         $this->pathelements = array_merge($this->pathelements, $paths);
510     }
512     /**
513      * Given one pathelement, return true if grouped parent was found
514      */
515     protected function grouped_parent_exists($pelement, $elements) {
516         foreach ($elements as $element) {
517             if ($pelement->get_path() == $element->get_path()) {
518                 continue; // Don't compare against itself
519             }
520             // If element is grouped and parent of pelement, return true
521             if ($element->is_grouped() and strpos($pelement->get_path() .  '/', $element->get_path()) === 0) {
522                 return true;
523             }
524         }
525         return false; // no grouped parent found
526     }
528     /**
529      * To conditionally decide if one step will be executed or no
530      *
531      * For steps needing to be executed conditionally, based in dynamic
532      * conditions (at execution time vs at declaration time) you must
533      * override this function. It will return true if the step must be
534      * executed and false if not
535      */
536     protected function execute_condition() {
537         return true;
538     }
540     /**
541      * Function that will return the structure to be processed by this restore_step.
542      * Must return one array of @restore_path_element elements
543      */
544     abstract protected function define_structure();