PageRenderTime 47ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/backup/util/plan/restore_structure_step.class.php

https://github.com/mylescarrick/moodle
PHP | 442 lines | 224 code | 50 blank | 168 comment | 45 complexity | 3a435a1c40ac9094f3ba1b925dd9e7b1 MD5 | raw file
Possible License(s): GPL-3.0, LGPL-2.1, BSD-3-Clause
  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. * @package moodlecore
  18. * @subpackage backup-plan
  19. * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
  20. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  21. */
  22. /**
  23. * Abstract class defining the needed stuff to restore one xml file
  24. *
  25. * TODO: Finish phpdocs
  26. */
  27. abstract class restore_structure_step extends restore_step {
  28. protected $filename; // Name of the file to be parsed
  29. protected $contentprocessor; // xml parser processor being used
  30. // (need it here, apart from parser
  31. // thanks to serialized data to process -
  32. // say thanks to blocks!)
  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
  36. protected $pathlock; // Path currently locking processing of children
  37. const SKIP_ALL_CHILDREN = -991399; // To instruct the dispatcher about to ignore
  38. // all children below path processor returning it
  39. /**
  40. * Constructor - instantiates one object of this class
  41. */
  42. public function __construct($name, $filename, $task = null) {
  43. if (!is_null($task) && !($task instanceof restore_task)) {
  44. throw new restore_step_exception('wrong_restore_task_specified');
  45. }
  46. $this->filename = $filename;
  47. $this->contentprocessor = null;
  48. $this->pathelements = array();
  49. $this->elementsoldid = array();
  50. $this->elementsnewid = array();
  51. $this->pathlock = null;
  52. parent::__construct($name, $task);
  53. }
  54. final public function execute() {
  55. if (!$this->execute_condition()) { // Check any condition to execute this
  56. return;
  57. }
  58. $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. }
  63. // Append the filename to the fullpath
  64. $fullpath = rtrim($fullpath, '/') . '/' . $this->filename;
  65. // And it MUST exist
  66. if (!file_exists($fullpath)) { // Shouldn't happen ever, but...
  67. throw new restore_step_exception('missing_moodle_backup_xml_file', $fullpath);
  68. }
  69. // Get restore_path elements array adapting and preparing it for processing
  70. $structure = $this->define_structure();
  71. if (!is_array($structure)) {
  72. throw new restore_step_exception('restore_step_structure_not_array', $this->get_name());
  73. }
  74. $this->prepare_pathelements($structure);
  75. // Create parser and processor
  76. $xmlparser = new progressive_parser();
  77. $xmlparser->set_file($fullpath);
  78. $xmlprocessor = new restore_structure_parser_processor($this->task->get_courseid(), $this);
  79. $this->contentprocessor = $xmlprocessor; // Save the reference to the contentprocessor
  80. // as far as we are going to need it out
  81. // from parser (blame serialized data!)
  82. $xmlparser->set_processor($xmlprocessor);
  83. // Add pathelements to processor
  84. foreach ($this->pathelements as $element) {
  85. $xmlprocessor->add_path($element->get_path(), $element->is_grouped());
  86. }
  87. // And process it, dispatch to target methods in step will start automatically
  88. $xmlparser->process();
  89. // Have finished, launch the after_execute method of all the processing objects
  90. $this->launch_after_execute_methods();
  91. }
  92. /**
  93. * Receive one chunk of information form the xml parser processor and
  94. * dispatch it, following the naming rules
  95. */
  96. final public function process($data) {
  97. if (!array_key_exists($data['path'], $this->pathelements)) { // Incorrect path, must not happen
  98. throw new restore_step_exception('restore_structure_step_missing_path', $data['path']);
  99. }
  100. $element = $this->pathelements[$data['path']];
  101. $object = $element->get_processing_object();
  102. $method = $element->get_processing_method();
  103. $rdata = null;
  104. if (empty($object)) { // No processing object defined
  105. throw new restore_step_exception('restore_structure_step_missing_pobject', $object);
  106. }
  107. // Release the lock if we aren't anymore within children of it
  108. if (!is_null($this->pathlock) and strpos($data['path'], $this->pathlock) === false) {
  109. $this->pathlock = null;
  110. }
  111. if (is_null($this->pathlock)) { // Only dispatch if there isn't any lock
  112. $rdata = $object->$method($data['tags']); // Dispatch to proper object/method
  113. }
  114. // If the dispatched method returns SKIP_ALL_CHILDREN, we grab current path in order to
  115. // lock dispatching to any children
  116. if ($rdata === self::SKIP_ALL_CHILDREN) {
  117. // Check we haven't any previous lock
  118. if (!is_null($this->pathlock)) {
  119. throw new restore_step_exception('restore_structure_step_already_skipping', $data['path']);
  120. }
  121. // Set the lock
  122. $this->pathlock = $data['path'] . '/'; // Lock everything below current path
  123. // Continue with normal processing of return values
  124. } else if ($rdata !== null) { // If the method has returned any info, set element data to it
  125. $element->set_data($rdata);
  126. } else { // Else, put the original parsed data
  127. $element->set_data($data);
  128. }
  129. }
  130. /**
  131. * To send ids pairs to backup_ids_table and to store them into paths
  132. *
  133. * This method will send the given itemname and old/new ids to the
  134. * backup_ids_temp table, and, at the same time, will save the new id
  135. * into the corresponding restore_path_element for easier access
  136. * by children. Also will inject the known old context id for the task
  137. * in case it's going to be used for restoring files later
  138. */
  139. public function set_mapping($itemname, $oldid, $newid, $restorefiles = false, $filesctxid = null, $parentid = null) {
  140. if ($restorefiles && $parentid) {
  141. throw new restore_step_exception('set_mapping_cannot_specify_both_restorefiles_and_parentitemid');
  142. }
  143. // If we haven't specified one context for the files, use the task one
  144. if (is_null($filesctxid)) {
  145. $parentitemid = $restorefiles ? $this->task->get_old_contextid() : null;
  146. } else { // Use the specified one
  147. $parentitemid = $restorefiles ? $filesctxid : null;
  148. }
  149. // We have passed one explicit parentid, apply it
  150. $parentitemid = !is_null($parentid) ? $parentid : $parentitemid;
  151. // Let's call the low level one
  152. restore_dbops::set_backup_ids_record($this->get_restoreid(), $itemname, $oldid, $newid, $parentitemid);
  153. // Now, if the itemname matches any pathelement->name, store the latest $newid
  154. if (array_key_exists($itemname, $this->elementsoldid)) { // If present in $this->elementsoldid, is valid, put both ids
  155. $this->elementsoldid[$itemname] = $oldid;
  156. $this->elementsnewid[$itemname] = $newid;
  157. }
  158. }
  159. /**
  160. * Returns the latest (parent) old id mapped by one pathelement
  161. */
  162. public function get_old_parentid($itemname) {
  163. return array_key_exists($itemname, $this->elementsoldid) ? $this->elementsoldid[$itemname] : null;
  164. }
  165. /**
  166. * Returns the latest (parent) new id mapped by one pathelement
  167. */
  168. public function get_new_parentid($itemname) {
  169. return array_key_exists($itemname, $this->elementsnewid) ? $this->elementsnewid[$itemname] : null;
  170. }
  171. /**
  172. * Return the new id of a mapping for the given itemname
  173. *
  174. */
  175. public function get_mappingid($itemname, $oldid) {
  176. $mapping = $this->get_mapping($itemname, $oldid);
  177. return $mapping ? $mapping->newitemid : false;
  178. }
  179. /**
  180. * Return the complete mapping from the given itemname, itemid
  181. */
  182. public function get_mapping($itemname, $oldid) {
  183. return restore_dbops::get_backup_ids_record($this->get_restoreid(), $itemname, $oldid);
  184. }
  185. /**
  186. * Add all the existing file, given their component and filearea and one backup_ids itemname to match with
  187. */
  188. public function add_related_files($component, $filearea, $mappingitemname, $filesctxid = null, $olditemid = null) {
  189. $filesctxid = is_null($filesctxid) ? $this->task->get_old_contextid() : $filesctxid;
  190. restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), $component,
  191. $filearea, $filesctxid, $this->task->get_userid(), $mappingitemname, $olditemid);
  192. }
  193. /**
  194. * Apply course startdate offset based in original course startdate and course_offset_startdate setting
  195. * Note we are using one static cache here, but *by restoreid*, so it's ok for concurrence/multiple
  196. * executions in the same request
  197. */
  198. public function apply_date_offset($value) {
  199. // empties don't offset - zeros (int and string), false and nulls return original value
  200. if (empty($value)) {
  201. return $value;
  202. }
  203. static $cache = array();
  204. // Lookup cache
  205. if (isset($cache[$this->get_restoreid()])) {
  206. return $value + $cache[$this->get_restoreid()];
  207. }
  208. // No cache, let's calculate the offset
  209. $original = $this->task->get_info()->original_course_startdate;
  210. $setting = 0;
  211. if ($this->setting_exists('course_startdate')) { // Seting may not exist (MDL-25019)
  212. $setting = $this->get_setting_value('course_startdate');
  213. }
  214. // Original course has not startdate or setting doesn't exist, offset = 0
  215. if (empty($original) || empty($setting)) {
  216. $cache[$this->get_restoreid()] = 0;
  217. // Less than 24h of difference, offset = 0 (this avoids some problems with timezones)
  218. } else if (abs($setting - $original) < 24 * 60 * 60) {
  219. $cache[$this->get_restoreid()] = 0;
  220. // Re-enforce 'moodle/restore:rolldates' capability for the user in the course, just in case
  221. } else if (!has_capability('moodle/restore:rolldates',
  222. get_context_instance(CONTEXT_COURSE, $this->get_courseid()),
  223. $this->task->get_userid())) {
  224. $cache[$this->get_restoreid()] = 0;
  225. // Arrived here, let's calculate the real offset
  226. } else {
  227. $cache[$this->get_restoreid()] = $setting - $original;
  228. }
  229. // Return the passed value with cached offset applied
  230. return $value + $cache[$this->get_restoreid()];
  231. }
  232. /**
  233. * As far as restore structure steps are implementing restore_plugin stuff, they need to
  234. * have the parent task available for wrapping purposes (get course/context....)
  235. */
  236. public function get_task() {
  237. return $this->task;
  238. }
  239. // Protected API starts here
  240. /**
  241. * Add plugin structure to any element in the structure restore tree
  242. *
  243. * @param string $plugintype type of plugin as defined by get_plugin_types()
  244. * @param restore_path_element $element element in the structure restore tree that
  245. * we are going to add plugin information to
  246. */
  247. protected function add_plugin_structure($plugintype, $element) {
  248. global $CFG;
  249. // Check the requested plugintype is a valid one
  250. if (!array_key_exists($plugintype, get_plugin_types($plugintype))) {
  251. throw new restore_step_exception('incorrect_plugin_type', $plugintype);
  252. }
  253. // Get all the restore path elements, looking across all the plugin dirs
  254. $pluginsdirs = get_plugin_list($plugintype);
  255. foreach ($pluginsdirs as $name => $pluginsdir) {
  256. // We need to add also backup plugin classes on restore, they may contain
  257. // some stuff used both in backup & restore
  258. $backupclassname = 'backup_' . $plugintype . '_' . $name . '_plugin';
  259. $backupfile = $pluginsdir . '/backup/moodle2/' . $backupclassname . '.class.php';
  260. if (file_exists($backupfile)) {
  261. require_once($backupfile);
  262. }
  263. // Now add restore plugin classes and prepare stuff
  264. $restoreclassname = 'restore_' . $plugintype . '_' . $name . '_plugin';
  265. $restorefile = $pluginsdir . '/backup/moodle2/' . $restoreclassname . '.class.php';
  266. if (file_exists($restorefile)) {
  267. require_once($restorefile);
  268. $restoreplugin = new $restoreclassname($plugintype, $name, $this);
  269. // Add plugin paths to the step
  270. $this->prepare_pathelements($restoreplugin->define_plugin_structure($element));
  271. }
  272. }
  273. }
  274. /**
  275. * Launch all the after_execute methods present in all the processing objects
  276. *
  277. * This method will launch all the after_execute methods that can be defined
  278. * both in restore_plugin and restore_structure_step classes
  279. *
  280. * For restore_plugin classes the name of the method to be executed will be
  281. * "after_execute_" + connection point (as far as can be multiple connection
  282. * points in the same class)
  283. *
  284. * For restore_structure_step classes is will be, simply, "after_execute". Note
  285. * that this is executed *after* the plugin ones
  286. */
  287. protected function launch_after_execute_methods() {
  288. $alreadylaunched = array(); // To avoid multiple executions
  289. foreach ($this->pathelements as $key => $pathelement) {
  290. // Get the processing object
  291. $pobject = $pathelement->get_processing_object();
  292. // Skip null processors (child of grouped ones for sure)
  293. if (is_null($pobject)) {
  294. continue;
  295. }
  296. // Skip restore structure step processors (this)
  297. if ($pobject instanceof restore_structure_step) {
  298. continue;
  299. }
  300. // Skip already launched processing objects
  301. if (in_array($pobject, $alreadylaunched, true)) {
  302. continue;
  303. }
  304. // Add processing object to array of launched ones
  305. $alreadylaunched[] = $pobject;
  306. // If the processing object has support for
  307. // launching after_execute methods, use it
  308. if (method_exists($pobject, 'launch_after_execute_methods')) {
  309. $pobject->launch_after_execute_methods();
  310. }
  311. }
  312. // Finally execute own (restore_structure_step) after_execute method
  313. $this->after_execute();
  314. }
  315. /**
  316. * This method will be executed after the whole structure step have been processed
  317. *
  318. * After execution method for code needed to be executed after the whole structure
  319. * has been processed. Useful for cleaning tasks, files process and others. Simply
  320. * overwrite in in your steps if needed
  321. */
  322. protected function after_execute() {
  323. // do nothing by default
  324. }
  325. /**
  326. * Prepare the pathelements for processing, looking for duplicates, applying
  327. * processing objects and other adjustments
  328. */
  329. protected function prepare_pathelements($elementsarr) {
  330. // First iteration, push them to new array, indexed by name
  331. // detecting duplicates in names or paths
  332. $names = array();
  333. $paths = array();
  334. foreach($elementsarr as $element) {
  335. if (!$element instanceof restore_path_element) {
  336. throw new restore_step_exception('restore_path_element_wrong_class', get_class($element));
  337. }
  338. if (array_key_exists($element->get_name(), $names)) {
  339. throw new restore_step_exception('restore_path_element_name_alreadyexists', $element->get_name());
  340. }
  341. if (array_key_exists($element->get_path(), $paths)) {
  342. throw new restore_step_exception('restore_path_element_path_alreadyexists', $element->get_path());
  343. }
  344. $names[$element->get_name()] = true;
  345. $paths[$element->get_path()] = $element;
  346. }
  347. // Now, for each element not having one processing object, if
  348. // not child of grouped element, assign $this (the step itself) as processing element
  349. // Note method must exist or we'll get one @restore_path_element_exception
  350. foreach($paths as $key => $pelement) {
  351. if ($pelement->get_processing_object() === null && !$this->grouped_parent_exists($pelement, $paths)) {
  352. $paths[$key]->set_processing_object($this);
  353. }
  354. // Populate $elementsoldid and $elementsoldid based on available pathelements
  355. $this->elementsoldid[$pelement->get_name()] = null;
  356. $this->elementsnewid[$pelement->get_name()] = null;
  357. }
  358. // Done, add them to pathelements (dupes by key - path - are discarded)
  359. $this->pathelements = array_merge($this->pathelements, $paths);
  360. }
  361. /**
  362. * Given one pathelement, return true if grouped parent was found
  363. */
  364. protected function grouped_parent_exists($pelement, $elements) {
  365. foreach ($elements as $element) {
  366. if ($pelement->get_path() == $element->get_path()) {
  367. continue; // Don't compare against itself
  368. }
  369. // If element is grouped and parent of pelement, return true
  370. if ($element->is_grouped() and strpos($pelement->get_path() . '/', $element->get_path()) === 0) {
  371. return true;
  372. }
  373. }
  374. return false; // no grouped parent found
  375. }
  376. /**
  377. * To conditionally decide if one step will be executed or no
  378. *
  379. * For steps needing to be executed conditionally, based in dynamic
  380. * conditions (at execution time vs at declaration time) you must
  381. * override this function. It will return true if the step must be
  382. * executed and false if not
  383. */
  384. protected function execute_condition() {
  385. return true;
  386. }
  387. /**
  388. * Function that will return the structure to be processed by this restore_step.
  389. * Must return one array of @restore_path_element elements
  390. */
  391. abstract protected function define_structure();
  392. }