enrol MDL-23688 added missing argument to table::set_fields
[moodle.git] / backup / util / plan / restore_structure_step.class.php
CommitLineData
69023455
EL
1<?php
2
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/>.
17
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 */
24
25/**
26 * Abstract class defining the needed stuff to restore one xml file
27 *
28 * TODO: Finish phpdocs
29 */
30abstract class restore_structure_step extends restore_step {
31
32 protected $filename; // Name of the file to be parsed
2d7cd798
EL
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
2df0f295
EL
38 protected $elementsoldid; // Array to store last oldid used on each element
39 protected $elementsnewid; // Array to store last newid used on each element
69023455
EL
40
41 /**
42 * Constructor - instantiates one object of this class
43 */
44 public function __construct($name, $filename, $task = null) {
45 if (!is_null($task) && !($task instanceof restore_task)) {
46 throw new restore_step_exception('wrong_restore_task_specified');
47 }
48 $this->filename = $filename;
2d7cd798 49 $this->contentprocessor = null;
69023455 50 $this->pathelements = array();
2df0f295
EL
51 $this->elementsoldid = array();
52 $this->elementsnewid = array();
69023455
EL
53 parent::__construct($name, $task);
54 }
55
56 public function execute() {
57
482aac65
EL
58 if (!$this->execute_condition()) { // Check any condition to execute this
59 return;
60 }
61
69023455
EL
62 $fullpath = $this->task->get_taskbasepath();
63
64 // We MUST have one fullpath here, else, error
65 if (empty($fullpath)) {
66 throw new restore_step_exception('restore_structure_step_undefined_fullpath');
67 }
68
69 // Append the filename to the fullpath
70 $fullpath = rtrim($fullpath, '/') . '/' . $this->filename;
71
72 // And it MUST exist
73 if (!file_exists($fullpath)) { // Shouldn't happen ever, but...
74 throw new restore_step_exception('missing_moodle_backup_xml_file', $fullpath);
75 }
76
77 // Get restore_path elements array adapting and preparing it for processing
245491b0
EL
78 $structure = $this->define_structure();
79 if (!is_array($structure)) {
80 throw new restore_step_exception('restore_step_structure_not_array', $this->get_name());
81 }
82 $this->pathelements = $this->prepare_pathelements($structure);
69023455 83
2df0f295
EL
84 // Populate $elementsoldid and $elementsoldid based on available pathelements
85 foreach ($this->pathelements as $pathelement) {
86 $this->elementsoldid[$pathelement->get_name()] = null;
87 $this->elementsnewid[$pathelement->get_name()] = null;
88 }
89
69023455
EL
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);
2d7cd798
EL
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!)
69023455
EL
97 $xmlparser->set_processor($xmlprocessor);
98
99 // Add pathelements to processor
100 foreach ($this->pathelements as $element) {
101 $xmlprocessor->add_path($element->get_path(), $element->is_grouped());
102 }
103
104 // And process it, dispatch to target methods in step will start automatically
105 $xmlparser->process();
69023455 106
2df0f295
EL
107 // Have finished, call to the after_execute method
108 $this->after_execute();
109 }
69023455
EL
110
111 /**
112 * Receive one chunk of information form the xml parser processor and
113 * dispatch it, following the naming rules
114 */
115 public function process($data) {
116 if (!array_key_exists($data['path'], $this->pathelements)) { // Incorrect path, must not happen
117 throw new restore_step_exception('restore_structure_step_missing_path', $data['path']);
118 }
119 $element = $this->pathelements[$data['path']];
120 $object = $element->get_processing_object();
121 $method = $element->get_processing_method();
122 if (empty($object)) { // No processing object defined
123 throw new restore_step_exception('restore_structure_step_missing_pobject', $object);
124 }
125 $rdata = $object->$method($data['tags']); // Dispatch to proper object/method
126 if ($rdata !== null) { // If the method has returned any info, set element data to it
127 $element->set_data($rdata);
128 } else { // Else, put the original parsed data
129 $element->set_data($data);
130 }
131 }
132
2df0f295
EL
133// Protected API starts here
134
135 /**
136 * This method will be executed after the whole structure step have been processed
137 *
138 * After execution method for code needed to be executed after the whole structure
139 * has been processed. Useful for cleaning tasks, files process and others. Simply
140 * overwrite in in your steps if needed
141 */
142 protected function after_execute() {
143 // do nothing by default
144 }
145
69023455
EL
146 /**
147 * Prepare the pathelements for processing, looking for duplicates, applying
148 * processing objects and other adjustments
149 */
150 protected function prepare_pathelements($elementsarr) {
151
152 // First iteration, push them to new array, indexed by name
153 // detecting duplicates in names or paths
154 $names = array();
155 $paths = array();
156 foreach($elementsarr as $element) {
245491b0
EL
157 if (!$element instanceof restore_path_element) {
158 throw new restore_step_exception('restore_path_element_wrong_class', get_class($element));
159 }
69023455
EL
160 if (array_key_exists($element->get_name(), $names)) {
161 throw new restore_step_exception('restore_path_element_name_alreadyexists', $element->get_name());
162 }
163 if (array_key_exists($element->get_path(), $paths)) {
164 throw new restore_step_exception('restore_path_element_path_alreadyexists', $element->get_path());
165 }
166 $names[$element->get_name()] = true;
167 $elements[$element->get_path()] = $element;
168 }
169 // Now, for each element not having one processing object, if
170 // not child of grouped element, assign $this (the step itself) as processing element
171 // Note method must exist or we'll get one @restore_path_element_exception
172 foreach($elements as $key => $pelement) {
173 if ($pelement->get_processing_object() === null && !$this->grouped_parent_exists($pelement, $elements)) {
174 $elements[$key]->set_processing_object($this);
175 }
176 }
177 // Done, return them
178 return $elements;
179 }
180
181 /**
182 * Given one pathelement, return true if grouped parent was found
183 */
184 protected function grouped_parent_exists($pelement, $elements) {
185 foreach ($elements as $element) {
186 if ($pelement->get_path() == $element->get_path()) {
187 continue; // Don't compare against itself
188 }
189 // If element is grouped and parent of pelement, return true
190 if ($element->is_grouped() and strpos($pelement->get_path() . '/', $element->get_path()) === 0) {
191 return true;
192 }
193 }
194 return false; // no grouped parent found
195 }
196
482aac65
EL
197 /**
198 * To conditionally decide if one step will be executed or no
199 *
200 * For steps needing to be executed conditionally, based in dynamic
201 * conditions (at execution time vs at declaration time) you must
202 * override this function. It will return true if the step must be
203 * executed and false if not
204 */
205 protected function execute_condition() {
206 return true;
207 }
208
2df0f295
EL
209 /**
210 * To send ids pairs to backup_ids_table and to store them into paths
211 *
212 * This method will send the given itemname and old/new ids to the
213 * backup_ids_temp table, and, at the same time, will save the new id
214 * into the corresponding restore_path_element for easier access
215 * by children. Also will inject the known old context id for the task
216 * in case it's going to be used for restoring files later
217 */
c0440b3f
EL
218 protected function set_mapping($itemname, $oldid, $newid, $restorefiles = false, $filesctxid = null) {
219 // If we haven't specified one context for the files, use the task one
220 if ($filesctxid == null) {
221 $parentitemid = $restorefiles ? $this->task->get_old_contextid() : null;
222 } else { // Use the specified one
223 $parentitemid = $restorefiles ? $filesctxid : null;
224 }
2df0f295
EL
225 // Let's call the low level one
226 restore_dbops::set_backup_ids_record($this->get_restoreid(), $itemname, $oldid, $newid, $parentitemid);
227 // Now, if the itemname matches any pathelement->name, store the latest $newid
228 if (array_key_exists($itemname, $this->elementsoldid)) { // If present in $this->elementsoldid, is valid, put both ids
229 $this->elementsoldid[$itemname] = $oldid;
230 $this->elementsnewid[$itemname] = $newid;
231 }
232 }
233
234 /**
235 * Returns the latest (parent) old id mapped by one pathelement
236 */
237 protected function get_old_parentid($itemname) {
238 return array_key_exists($itemname, $this->elementsoldid) ? $this->elementsoldid[$itemname] : null;
239 }
240
241 /**
242 * Returns the latest (parent) new id mapped by one pathelement
243 */
244 protected function get_new_parentid($itemname) {
245 return array_key_exists($itemname, $this->elementsnewid) ? $this->elementsnewid[$itemname] : null;
246 }
247
248 /**
249 * Return the new id of a mapping for the given itemname
250 *
251 */
252 protected function get_mappingid($itemname, $oldid) {
253 $mapping = $this->get_mapping($itemname, $oldid);
254 return $mapping ? $mapping->newitemid : false;
255 }
256
257 /**
258 * Return the complete mapping from the given itemname, itemid
259 */
260 protected function get_mapping($itemname, $oldid) {
261 return restore_dbops::get_backup_ids_record($this->get_restoreid(), $itemname, $oldid);
262 }
263
264 /**
265 * Add all the existing file, given their component and filearea and one backup_ids itemname to match with
266 */
c0440b3f
EL
267 protected function add_related_files($component, $filearea, $mappingitemname, $filesctxid = null) {
268 $filesctxid = is_null($filesctxid) ? $this->task->get_old_contextid() : $filesctxid;
b8bb45b0 269 restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), $component,
84cdf7de 270 $filearea, $filesctxid, $this->task->get_userid(), $mappingitemname);
2df0f295
EL
271 }
272
4bca307a
EL
273 /**
274 * Apply course startdate offset based in original course startdate and course_offset_startdate setting
275 * Note we are using one static cache here, but *by restoreid*, so it's ok for concurrence/multiple
276 * executions in the same request
277 */
278 protected function apply_date_offset($value) {
279
fc46764d
EL
280 // 0 doesn't offset
281 if ($value == 0) {
282 return $value;
283 }
284
4bca307a
EL
285 static $cache = array();
286 // Lookup cache
287 if (isset($cache[$this->get_restoreid()])) {
288 return $value + $cache[$this->get_restoreid()];
289 }
290 // No cache, let's calculate the offset
291 $original = $this->task->get_info()->original_course_startdate;
71aa9472 292 $setting = $this->get_setting_value('course_startdate');
4bca307a
EL
293
294 // Original course has not startdate, offset = 0
295 if (empty($original)) {
296 $cache[$this->get_restoreid()] = 0;
297
298 // Less than 24h of difference, offset = 0 (this avoids some problems with timezones)
299 } else if (abs($setting - $original) < 24 * 60 * 60) {
300 $cache[$this->get_restoreid()] = 0;
301
302 // Re-enforce 'moodle/restore:rolldates' capability for the user in the course, just in case
303 } else if (!has_capability('moodle/restore:rolldates',
304 get_context_instance(CONTEXT_COURSE, $this->get_courseid()),
ba8bead5 305 $this->task->get_userid())) {
4bca307a
EL
306 $cache[$this->get_restoreid()] = 0;
307
308 // Arrived here, let's calculate the real offset
309 } else {
310 $cache[$this->get_restoreid()] = $setting - $original;
311 }
312
313 // Return the passed value with cached offset applied
314 return $value + $cache[$this->get_restoreid()];
315 }
316
69023455
EL
317 /**
318 * Function that will return the structure to be processed by this restore_step.
319 * Must return one array of @restore_path_element elements
320 */
321 abstract protected function define_structure();
322}