|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 00003 // This file is part of Moodle - http://moodle.org/ 00004 // 00005 // Moodle is free software: you can redistribute it and/or modify 00006 // it under the terms of the GNU General Public License as published by 00007 // the Free Software Foundation, either version 3 of the License, or 00008 // (at your option) any later version. 00009 // 00010 // Moodle is distributed in the hope that it will be useful, 00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00013 // GNU General Public License for more details. 00014 // 00015 // You should have received a copy of the GNU General Public License 00016 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00017 00030 abstract class restore_structure_step extends restore_step { 00031 00032 protected $filename; // Name of the file to be parsed 00033 protected $contentprocessor; // xml parser processor being used 00034 // (need it here, apart from parser 00035 // thanks to serialized data to process - 00036 // say thanks to blocks!) 00037 protected $pathelements; // Array of pathelements to process 00038 protected $elementsoldid; // Array to store last oldid used on each element 00039 protected $elementsnewid; // Array to store last newid used on each element 00040 00041 protected $pathlock; // Path currently locking processing of children 00042 00043 const SKIP_ALL_CHILDREN = -991399; // To instruct the dispatcher about to ignore 00044 // all children below path processor returning it 00045 00049 public function __construct($name, $filename, $task = null) { 00050 if (!is_null($task) && !($task instanceof restore_task)) { 00051 throw new restore_step_exception('wrong_restore_task_specified'); 00052 } 00053 $this->filename = $filename; 00054 $this->contentprocessor = null; 00055 $this->pathelements = array(); 00056 $this->elementsoldid = array(); 00057 $this->elementsnewid = array(); 00058 $this->pathlock = null; 00059 parent::__construct($name, $task); 00060 } 00061 00062 final public function execute() { 00063 00064 if (!$this->execute_condition()) { // Check any condition to execute this 00065 return; 00066 } 00067 00068 $fullpath = $this->task->get_taskbasepath(); 00069 00070 // We MUST have one fullpath here, else, error 00071 if (empty($fullpath)) { 00072 throw new restore_step_exception('restore_structure_step_undefined_fullpath'); 00073 } 00074 00075 // Append the filename to the fullpath 00076 $fullpath = rtrim($fullpath, '/') . '/' . $this->filename; 00077 00078 // And it MUST exist 00079 if (!file_exists($fullpath)) { // Shouldn't happen ever, but... 00080 throw new restore_step_exception('missing_moodle_backup_xml_file', $fullpath); 00081 } 00082 00083 // Get restore_path elements array adapting and preparing it for processing 00084 $structure = $this->define_structure(); 00085 if (!is_array($structure)) { 00086 throw new restore_step_exception('restore_step_structure_not_array', $this->get_name()); 00087 } 00088 $this->prepare_pathelements($structure); 00089 00090 // Create parser and processor 00091 $xmlparser = new progressive_parser(); 00092 $xmlparser->set_file($fullpath); 00093 $xmlprocessor = new restore_structure_parser_processor($this->task->get_courseid(), $this); 00094 $this->contentprocessor = $xmlprocessor; // Save the reference to the contentprocessor 00095 // as far as we are going to need it out 00096 // from parser (blame serialized data!) 00097 $xmlparser->set_processor($xmlprocessor); 00098 00099 // Add pathelements to processor 00100 foreach ($this->pathelements as $element) { 00101 $xmlprocessor->add_path($element->get_path(), $element->is_grouped()); 00102 } 00103 00104 // And process it, dispatch to target methods in step will start automatically 00105 $xmlparser->process(); 00106 00107 // Have finished, launch the after_execute method of all the processing objects 00108 $this->launch_after_execute_methods(); 00109 } 00110 00115 final public function process($data) { 00116 if (!array_key_exists($data['path'], $this->pathelements)) { // Incorrect path, must not happen 00117 throw new restore_step_exception('restore_structure_step_missing_path', $data['path']); 00118 } 00119 $element = $this->pathelements[$data['path']]; 00120 $object = $element->get_processing_object(); 00121 $method = $element->get_processing_method(); 00122 $rdata = null; 00123 if (empty($object)) { // No processing object defined 00124 throw new restore_step_exception('restore_structure_step_missing_pobject', $object); 00125 } 00126 // Release the lock if we aren't anymore within children of it 00127 if (!is_null($this->pathlock) and strpos($data['path'], $this->pathlock) === false) { 00128 $this->pathlock = null; 00129 } 00130 if (is_null($this->pathlock)) { // Only dispatch if there isn't any lock 00131 $rdata = $object->$method($data['tags']); // Dispatch to proper object/method 00132 } 00133 00134 // If the dispatched method returns SKIP_ALL_CHILDREN, we grab current path in order to 00135 // lock dispatching to any children 00136 if ($rdata === self::SKIP_ALL_CHILDREN) { 00137 // Check we haven't any previous lock 00138 if (!is_null($this->pathlock)) { 00139 throw new restore_step_exception('restore_structure_step_already_skipping', $data['path']); 00140 } 00141 // Set the lock 00142 $this->pathlock = $data['path'] . '/'; // Lock everything below current path 00143 00144 // Continue with normal processing of return values 00145 } else if ($rdata !== null) { // If the method has returned any info, set element data to it 00146 $element->set_data($rdata); 00147 } else { // Else, put the original parsed data 00148 $element->set_data($data); 00149 } 00150 } 00151 00161 public function set_mapping($itemname, $oldid, $newid, $restorefiles = false, $filesctxid = null, $parentid = null) { 00162 if ($restorefiles && $parentid) { 00163 throw new restore_step_exception('set_mapping_cannot_specify_both_restorefiles_and_parentitemid'); 00164 } 00165 // If we haven't specified one context for the files, use the task one 00166 if (is_null($filesctxid)) { 00167 $parentitemid = $restorefiles ? $this->task->get_old_contextid() : null; 00168 } else { // Use the specified one 00169 $parentitemid = $restorefiles ? $filesctxid : null; 00170 } 00171 // We have passed one explicit parentid, apply it 00172 $parentitemid = !is_null($parentid) ? $parentid : $parentitemid; 00173 00174 // Let's call the low level one 00175 restore_dbops::set_backup_ids_record($this->get_restoreid(), $itemname, $oldid, $newid, $parentitemid); 00176 // Now, if the itemname matches any pathelement->name, store the latest $newid 00177 if (array_key_exists($itemname, $this->elementsoldid)) { // If present in $this->elementsoldid, is valid, put both ids 00178 $this->elementsoldid[$itemname] = $oldid; 00179 $this->elementsnewid[$itemname] = $newid; 00180 } 00181 } 00182 00186 public function get_old_parentid($itemname) { 00187 return array_key_exists($itemname, $this->elementsoldid) ? $this->elementsoldid[$itemname] : null; 00188 } 00189 00193 public function get_new_parentid($itemname) { 00194 return array_key_exists($itemname, $this->elementsnewid) ? $this->elementsnewid[$itemname] : null; 00195 } 00196 00204 public function get_mappingid($itemname, $oldid, $ifnotfound = false) { 00205 $mapping = $this->get_mapping($itemname, $oldid); 00206 return $mapping ? $mapping->newitemid : $ifnotfound; 00207 } 00208 00212 public function get_mapping($itemname, $oldid) { 00213 return restore_dbops::get_backup_ids_record($this->get_restoreid(), $itemname, $oldid); 00214 } 00215 00219 public function add_related_files($component, $filearea, $mappingitemname, $filesctxid = null, $olditemid = null) { 00220 $filesctxid = is_null($filesctxid) ? $this->task->get_old_contextid() : $filesctxid; 00221 restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), $component, 00222 $filearea, $filesctxid, $this->task->get_userid(), $mappingitemname, $olditemid); 00223 } 00224 00230 public function apply_date_offset($value) { 00231 00232 // empties don't offset - zeros (int and string), false and nulls return original value 00233 if (empty($value)) { 00234 return $value; 00235 } 00236 00237 static $cache = array(); 00238 // Lookup cache 00239 if (isset($cache[$this->get_restoreid()])) { 00240 return $value + $cache[$this->get_restoreid()]; 00241 } 00242 // No cache, let's calculate the offset 00243 $original = $this->task->get_info()->original_course_startdate; 00244 $setting = 0; 00245 if ($this->setting_exists('course_startdate')) { // Seting may not exist (MDL-25019) 00246 $setting = $this->get_setting_value('course_startdate'); 00247 } 00248 00249 // Original course has not startdate or setting doesn't exist, offset = 0 00250 if (empty($original) || empty($setting)) { 00251 $cache[$this->get_restoreid()] = 0; 00252 00253 // Less than 24h of difference, offset = 0 (this avoids some problems with timezones) 00254 } else if (abs($setting - $original) < 24 * 60 * 60) { 00255 $cache[$this->get_restoreid()] = 0; 00256 00257 // Re-enforce 'moodle/restore:rolldates' capability for the user in the course, just in case 00258 } else if (!has_capability('moodle/restore:rolldates', 00259 get_context_instance(CONTEXT_COURSE, $this->get_courseid()), 00260 $this->task->get_userid())) { 00261 $cache[$this->get_restoreid()] = 0; 00262 00263 // Arrived here, let's calculate the real offset 00264 } else { 00265 $cache[$this->get_restoreid()] = $setting - $original; 00266 } 00267 00268 // Return the passed value with cached offset applied 00269 return $value + $cache[$this->get_restoreid()]; 00270 } 00271 00276 public function get_task() { 00277 return $this->task; 00278 } 00279 00280 // Protected API starts here 00281 00289 protected function add_plugin_structure($plugintype, $element) { 00290 00291 global $CFG; 00292 00293 // Check the requested plugintype is a valid one 00294 if (!array_key_exists($plugintype, get_plugin_types($plugintype))) { 00295 throw new restore_step_exception('incorrect_plugin_type', $plugintype); 00296 } 00297 00298 // Get all the restore path elements, looking across all the plugin dirs 00299 $pluginsdirs = get_plugin_list($plugintype); 00300 foreach ($pluginsdirs as $name => $pluginsdir) { 00301 // We need to add also backup plugin classes on restore, they may contain 00302 // some stuff used both in backup & restore 00303 $backupclassname = 'backup_' . $plugintype . '_' . $name . '_plugin'; 00304 $backupfile = $pluginsdir . '/backup/moodle2/' . $backupclassname . '.class.php'; 00305 if (file_exists($backupfile)) { 00306 require_once($backupfile); 00307 } 00308 // Now add restore plugin classes and prepare stuff 00309 $restoreclassname = 'restore_' . $plugintype . '_' . $name . '_plugin'; 00310 $restorefile = $pluginsdir . '/backup/moodle2/' . $restoreclassname . '.class.php'; 00311 if (file_exists($restorefile)) { 00312 require_once($restorefile); 00313 $restoreplugin = new $restoreclassname($plugintype, $name, $this); 00314 // Add plugin paths to the step 00315 $this->prepare_pathelements($restoreplugin->define_plugin_structure($element)); 00316 } 00317 } 00318 } 00319 00333 protected function launch_after_execute_methods() { 00334 $alreadylaunched = array(); // To avoid multiple executions 00335 foreach ($this->pathelements as $key => $pathelement) { 00336 // Get the processing object 00337 $pobject = $pathelement->get_processing_object(); 00338 // Skip null processors (child of grouped ones for sure) 00339 if (is_null($pobject)) { 00340 continue; 00341 } 00342 // Skip restore structure step processors (this) 00343 if ($pobject instanceof restore_structure_step) { 00344 continue; 00345 } 00346 // Skip already launched processing objects 00347 if (in_array($pobject, $alreadylaunched, true)) { 00348 continue; 00349 } 00350 // Add processing object to array of launched ones 00351 $alreadylaunched[] = $pobject; 00352 // If the processing object has support for 00353 // launching after_execute methods, use it 00354 if (method_exists($pobject, 'launch_after_execute_methods')) { 00355 $pobject->launch_after_execute_methods(); 00356 } 00357 } 00358 // Finally execute own (restore_structure_step) after_execute method 00359 $this->after_execute(); 00360 00361 } 00362 00373 public function launch_after_restore_methods() { 00374 $alreadylaunched = array(); // To avoid multiple executions 00375 foreach ($this->pathelements as $pathelement) { 00376 // Get the processing object 00377 $pobject = $pathelement->get_processing_object(); 00378 // Skip null processors (child of grouped ones for sure) 00379 if (is_null($pobject)) { 00380 continue; 00381 } 00382 // Skip restore structure step processors (this) 00383 if ($pobject instanceof restore_structure_step) { 00384 continue; 00385 } 00386 // Skip already launched processing objects 00387 if (in_array($pobject, $alreadylaunched, true)) { 00388 continue; 00389 } 00390 // Add processing object to array of launched ones 00391 $alreadylaunched[] = $pobject; 00392 // If the processing object has support for 00393 // launching after_restore methods, use it 00394 if (method_exists($pobject, 'launch_after_restore_methods')) { 00395 $pobject->launch_after_restore_methods(); 00396 } 00397 } 00398 } 00399 00407 protected function after_execute() { 00408 // do nothing by default 00409 } 00410 00415 protected function prepare_pathelements($elementsarr) { 00416 00417 // First iteration, push them to new array, indexed by name 00418 // detecting duplicates in names or paths 00419 $names = array(); 00420 $paths = array(); 00421 foreach($elementsarr as $element) { 00422 if (!$element instanceof restore_path_element) { 00423 throw new restore_step_exception('restore_path_element_wrong_class', get_class($element)); 00424 } 00425 if (array_key_exists($element->get_name(), $names)) { 00426 throw new restore_step_exception('restore_path_element_name_alreadyexists', $element->get_name()); 00427 } 00428 if (array_key_exists($element->get_path(), $paths)) { 00429 throw new restore_step_exception('restore_path_element_path_alreadyexists', $element->get_path()); 00430 } 00431 $names[$element->get_name()] = true; 00432 $paths[$element->get_path()] = $element; 00433 } 00434 // Now, for each element not having one processing object, if 00435 // not child of grouped element, assign $this (the step itself) as processing element 00436 // Note method must exist or we'll get one @restore_path_element_exception 00437 foreach($paths as $key => $pelement) { 00438 if ($pelement->get_processing_object() === null && !$this->grouped_parent_exists($pelement, $paths)) { 00439 $paths[$key]->set_processing_object($this); 00440 } 00441 // Populate $elementsoldid and $elementsoldid based on available pathelements 00442 $this->elementsoldid[$pelement->get_name()] = null; 00443 $this->elementsnewid[$pelement->get_name()] = null; 00444 } 00445 // Done, add them to pathelements (dupes by key - path - are discarded) 00446 $this->pathelements = array_merge($this->pathelements, $paths); 00447 } 00448 00452 protected function grouped_parent_exists($pelement, $elements) { 00453 foreach ($elements as $element) { 00454 if ($pelement->get_path() == $element->get_path()) { 00455 continue; // Don't compare against itself 00456 } 00457 // If element is grouped and parent of pelement, return true 00458 if ($element->is_grouped() and strpos($pelement->get_path() . '/', $element->get_path()) === 0) { 00459 return true; 00460 } 00461 } 00462 return false; // no grouped parent found 00463 } 00464 00473 protected function execute_condition() { 00474 return true; 00475 } 00476 00481 abstract protected function define_structure(); 00482 }