7e326b6860daafade24c47b2e606d2721bdd11ae
[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 $pathelements; // Array of pathelements to process
34     protected $elementsoldid; // Array to store last oldid used on each element
35     protected $elementsnewid; // Array to store last newid used on each element
37     /**
38      * Constructor - instantiates one object of this class
39      */
40     public function __construct($name, $filename, $task = null) {
41         if (!is_null($task) && !($task instanceof restore_task)) {
42             throw new restore_step_exception('wrong_restore_task_specified');
43         }
44         $this->filename = $filename;
45         $this->pathelements = array();
46         $this->elementsoldid = array();
47         $this->elementsnewid = array();
48         parent::__construct($name, $task);
49     }
51     public function execute() {
53         if (!$this->execute_condition()) { // Check any condition to execute this
54             return;
55         }
57         $fullpath = $this->task->get_taskbasepath();
59         // We MUST have one fullpath here, else, error
60         if (empty($fullpath)) {
61             throw new restore_step_exception('restore_structure_step_undefined_fullpath');
62         }
64         // Append the filename to the fullpath
65         $fullpath = rtrim($fullpath, '/') . '/' . $this->filename;
67         // And it MUST exist
68         if (!file_exists($fullpath)) { // Shouldn't happen ever, but...
69             throw new restore_step_exception('missing_moodle_backup_xml_file', $fullpath);
70         }
72         // Get restore_path elements array adapting and preparing it for processing
73         $this->pathelements = $this->prepare_pathelements($this->define_structure());
75         // Populate $elementsoldid and $elementsoldid based on available pathelements
76         foreach ($this->pathelements as $pathelement) {
77             $this->elementsoldid[$pathelement->get_name()] = null;
78             $this->elementsnewid[$pathelement->get_name()] = null;
79         }
81         // Create parser and processor
82         $xmlparser = new progressive_parser();
83         $xmlparser->set_file($fullpath);
84         $xmlprocessor = new restore_structure_parser_processor($this->task->get_courseid(), $this);
85         $xmlparser->set_processor($xmlprocessor);
87         // Add pathelements to processor
88         foreach ($this->pathelements as $element) {
89             $xmlprocessor->add_path($element->get_path(), $element->is_grouped());
90         }
92         // And process it, dispatch to target methods in step will start automatically
93         $xmlparser->process();
95         // Have finished, call to the after_execute method
96         $this->after_execute();
97     }
99     /**
100      * Receive one chunk of information form the xml parser processor and
101      * dispatch it, following the naming rules
102      */
103     public function process($data) {
104         if (!array_key_exists($data['path'], $this->pathelements)) { // Incorrect path, must not happen
105             throw new restore_step_exception('restore_structure_step_missing_path', $data['path']);
106         }
107         $element = $this->pathelements[$data['path']];
108         $object = $element->get_processing_object();
109         $method = $element->get_processing_method();
110         if (empty($object)) { // No processing object defined
111             throw new restore_step_exception('restore_structure_step_missing_pobject', $object);
112         }
113         $rdata = $object->$method($data['tags']); // Dispatch to proper object/method
114         if ($rdata !== null) { // If the method has returned any info, set element data to it
115             $element->set_data($rdata);
116         } else {               // Else, put the original parsed data
117             $element->set_data($data);
118         }
119     }
121 // Protected API starts here
123     /**
124      * This method will be executed after the whole structure step have been processed
125      *
126      * After execution method for code needed to be executed after the whole structure
127      * has been processed. Useful for cleaning tasks, files process and others. Simply
128      * overwrite in in your steps if needed
129      */
130     protected function after_execute() {
131         // do nothing by default
132     }
134     /**
135      * Prepare the pathelements for processing, looking for duplicates, applying
136      * processing objects and other adjustments
137      */
138     protected function prepare_pathelements($elementsarr) {
140         // First iteration, push them to new array, indexed by name
141         // detecting duplicates in names or paths
142         $names = array();
143         $paths = array();
144         foreach($elementsarr as $element) {
145             if (array_key_exists($element->get_name(), $names)) {
146                 throw new restore_step_exception('restore_path_element_name_alreadyexists', $element->get_name());
147             }
148             if (array_key_exists($element->get_path(), $paths)) {
149                 throw new restore_step_exception('restore_path_element_path_alreadyexists', $element->get_path());
150             }
151             $names[$element->get_name()] = true;
152             $elements[$element->get_path()] = $element;
153         }
154         // Now, for each element not having one processing object, if
155         // not child of grouped element, assign $this (the step itself) as processing element
156         // Note method must exist or we'll get one @restore_path_element_exception
157         foreach($elements as $key => $pelement) {
158             if ($pelement->get_processing_object() === null && !$this->grouped_parent_exists($pelement, $elements)) {
159                 $elements[$key]->set_processing_object($this);
160             }
161         }
162         // Done, return them
163         return $elements;
164     }
166     /**
167      * Given one pathelement, return true if grouped parent was found
168      */
169     protected function grouped_parent_exists($pelement, $elements) {
170         foreach ($elements as $element) {
171             if ($pelement->get_path() == $element->get_path()) {
172                 continue; // Don't compare against itself
173             }
174             // If element is grouped and parent of pelement, return true
175             if ($element->is_grouped() and strpos($pelement->get_path() .  '/', $element->get_path()) === 0) {
176                 return true;
177             }
178         }
179         return false; // no grouped parent found
180     }
182     /**
183      * To conditionally decide if one step will be executed or no
184      *
185      * For steps needing to be executed conditionally, based in dynamic
186      * conditions (at execution time vs at declaration time) you must
187      * override this function. It will return true if the step must be
188      * executed and false if not
189      */
190     protected function execute_condition() {
191         return true;
192     }
194     /**
195      * To send ids pairs to backup_ids_table and to store them into paths
196      *
197      * This method will send the given itemname and old/new ids to the
198      * backup_ids_temp table, and, at the same time, will save the new id
199      * into the corresponding restore_path_element for easier access
200      * by children. Also will inject the known old context id for the task
201      * in case it's going to be used for restoring files later
202      */
203     protected function set_mapping($itemname, $oldid, $newid, $restorefiles = false) {
204         $parentitemid = $restorefiles ? $this->task->get_old_contextid() : null;
205         // Let's call the low level one
206         restore_dbops::set_backup_ids_record($this->get_restoreid(), $itemname, $oldid, $newid, $parentitemid);
207         // Now, if the itemname matches any pathelement->name, store the latest $newid
208         if (array_key_exists($itemname, $this->elementsoldid)) { // If present in  $this->elementsoldid, is valid, put both ids
209             $this->elementsoldid[$itemname] = $oldid;
210             $this->elementsnewid[$itemname] = $newid;
211         }
212     }
214     /**
215      * Returns the latest (parent) old id mapped by one pathelement
216      */
217     protected function get_old_parentid($itemname) {
218         return array_key_exists($itemname, $this->elementsoldid) ? $this->elementsoldid[$itemname] : null;
219     }
221     /**
222      * Returns the latest (parent) new id mapped by one pathelement
223      */
224     protected function get_new_parentid($itemname) {
225         return array_key_exists($itemname, $this->elementsnewid) ? $this->elementsnewid[$itemname] : null;
226     }
228     /**
229      * Return the new id of a mapping for the given itemname
230      *
231      */
232     protected function get_mappingid($itemname, $oldid) {
233         $mapping = $this->get_mapping($itemname, $oldid);
234         return $mapping ? $mapping->newitemid : false;
235     }
237     /**
238      * Return the complete mapping from the given itemname, itemid
239      */
240     protected function get_mapping($itemname, $oldid) {
241         return restore_dbops::get_backup_ids_record($this->get_restoreid(), $itemname, $oldid);
242     }
244     /**
245      * Add all the existing file, given their component and filearea and one backup_ids itemname to match with
246      */
247     protected function add_related_files($componentname, $filearea, $mappingitemname) {
248         restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), $componentname,
249                                           $filearea, $this->task->get_old_contextid(), $mappingitemname);
250     }
252     /**
253      * Function that will return the structure to be processed by this restore_step.
254      * Must return one array of @restore_path_element elements
255      */
256     abstract protected function define_structure();