See Release Notes
Long Term Support Release
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-helper 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 defined('MOODLE_INTERNAL') || die(); 26 27 /** 28 * Non instantiable helper class providing general helper methods for backup/restore 29 * 30 * This class contains various general helper static methods available for backup/restore 31 * 32 * TODO: Finish phpdocs 33 */ 34 abstract class backup_general_helper extends backup_helper { 35 36 /** 37 * Calculate one checksum for any array/object. Works recursively 38 */ 39 public static function array_checksum_recursive($arr) { 40 41 $checksum = ''; // Init checksum 42 43 // Check we are going to process one array always, objects must be cast before 44 if (!is_array($arr)) { 45 throw new backup_helper_exception('array_expected'); 46 } 47 foreach ($arr as $key => $value) { 48 if ($value instanceof checksumable) { 49 $checksum = md5($checksum . '-' . $key . '-' . $value->calculate_checksum()); 50 } else if (is_object($value)) { 51 $checksum = md5($checksum . '-' . $key . '-' . self::array_checksum_recursive((array)$value)); 52 } else if (is_array($value)) { 53 $checksum = md5($checksum . '-' . $key . '-' . self::array_checksum_recursive($value)); 54 } else { 55 $checksum = md5($checksum . '-' . $key . '-' . $value); 56 } 57 } 58 return $checksum; 59 } 60 61 /** 62 * Load all the blocks information needed for a given path within moodle2 backup 63 * 64 * This function, given one full path (course, activities/xxxx) will look for all the 65 * blocks existing in the backup file, returning one array used to build the 66 * proper restore plan by the @restore_plan_builder 67 */ 68 public static function get_blocks_from_path($path) { 69 global $DB; 70 71 $blocks = array(); // To return results 72 73 static $availableblocks = array(); // Get and cache available blocks 74 if (empty($availableblocks)) { 75 $availableblocks = array_keys(core_component::get_plugin_list('block')); 76 } 77 78 $path = $path . '/blocks'; // Always look under blocks subdir 79 80 if (!is_dir($path)) { 81 return array(); 82 } 83 84 if (!$dir = opendir($path)) { 85 return array(); 86 } 87 while (false !== ($file = readdir($dir))) { 88 if ($file == '.' || $file == '..') { // Skip dots 89 continue; 90 } 91 if (is_dir($path .'/' . $file)) { // Dir found, check it's a valid block 92 if (!file_exists($path .'/' . $file . '/block.xml')) { // Skip if xml file not found 93 continue; 94 } 95 // Extract block name 96 $blockname = preg_replace('/(.*)_\d+/', '\\1', $file); 97 // Check block exists and is installed 98 if (in_array($blockname, $availableblocks) && $DB->record_exists('block', array('name' => $blockname))) { 99 $blocks[$path .'/' . $file] = $blockname; 100 } 101 } 102 } 103 closedir($dir); 104 105 return $blocks; 106 } 107 108 /** 109 * Load and format all the needed information from moodle_backup.xml 110 * 111 * This function loads and process all the moodle_backup.xml 112 * information, composing a big information structure that will 113 * be the used by the plan builder in order to generate the 114 * appropiate tasks / steps / settings 115 */ 116 public static function get_backup_information($tempdir) { 117 global $CFG; 118 // Make a request cache and store the data in there. 119 static $cachesha1 = null; 120 static $cache = null; 121 122 $info = new stdclass(); // Final information goes here 123 124 $backuptempdir = make_backup_temp_directory('', false); 125 $moodlefile = $backuptempdir . '/' . $tempdir . '/moodle_backup.xml'; 126 if (!file_exists($moodlefile)) { // Shouldn't happen ever, but... 127 throw new backup_helper_exception('missing_moodle_backup_xml_file', $moodlefile); 128 } 129 130 $moodlefilesha1 = sha1_file($moodlefile); 131 if ($moodlefilesha1 === $cachesha1) { 132 return clone $cache; 133 } 134 135 // Load the entire file to in-memory array 136 $xmlparser = new progressive_parser(); 137 $xmlparser->set_file($moodlefile); 138 $xmlprocessor = new restore_moodlexml_parser_processor(); 139 $xmlparser->set_processor($xmlprocessor); 140 $xmlparser->process(); 141 $infoarr = $xmlprocessor->get_all_chunks(); 142 if (count($infoarr) !== 1) { // Shouldn't happen ever, but... 143 throw new backup_helper_exception('problem_parsing_moodle_backup_xml_file'); 144 } 145 $infoarr = $infoarr[0]['tags']; // for commodity 146 147 // Let's build info 148 $info->moodle_version = $infoarr['moodle_version']; 149 $info->moodle_release = $infoarr['moodle_release']; 150 $info->backup_version = $infoarr['backup_version']; 151 $info->backup_release = $infoarr['backup_release']; 152 $info->backup_date = $infoarr['backup_date']; 153 $info->mnet_remoteusers = $infoarr['mnet_remoteusers']; 154 $info->original_wwwroot = $infoarr['original_wwwroot']; 155 $info->original_site_identifier_hash = $infoarr['original_site_identifier_hash']; 156 $info->original_course_id = $infoarr['original_course_id']; 157 $info->original_course_fullname = $infoarr['original_course_fullname']; 158 $info->original_course_shortname = $infoarr['original_course_shortname']; 159 $info->original_course_startdate = $infoarr['original_course_startdate']; 160 // Old versions may not have this. 161 if (isset($infoarr['original_course_enddate'])) { 162 $info->original_course_enddate = $infoarr['original_course_enddate']; 163 } 164 $info->original_course_contextid = $infoarr['original_course_contextid']; 165 $info->original_system_contextid = $infoarr['original_system_contextid']; 166 // Moodle backup file don't have this option before 2.3 167 if (!empty($infoarr['include_file_references_to_external_content'])) { 168 $info->include_file_references_to_external_content = 1; 169 } else { 170 $info->include_file_references_to_external_content = 0; 171 } 172 // Introduced in Moodle 2.9. 173 $info->original_course_format = ''; 174 if (!empty($infoarr['original_course_format'])) { 175 $info->original_course_format = $infoarr['original_course_format']; 176 } 177 // include_files is a new setting in 2.6. 178 if (isset($infoarr['include_files'])) { 179 $info->include_files = $infoarr['include_files']; 180 } else { 181 $info->include_files = 1; 182 } 183 $info->type = $infoarr['details']['detail'][0]['type']; 184 $info->format = $infoarr['details']['detail'][0]['format']; 185 $info->mode = $infoarr['details']['detail'][0]['mode']; 186 // Build the role mappings custom object 187 $rolemappings = new stdclass(); 188 $rolemappings->modified = false; 189 $rolemappings->mappings = array(); 190 $info->role_mappings = $rolemappings; 191 // Some initially empty containers 192 $info->sections = array(); 193 $info->activities = array(); 194 195 // Now the contents 196 $contentsarr = $infoarr['contents']; 197 if (isset($contentsarr['course']) && isset($contentsarr['course'][0])) { 198 $info->course = new stdclass(); 199 $info->course = (object)$contentsarr['course'][0]; 200 $info->course->settings = array(); 201 } 202 if (isset($contentsarr['sections']) && isset($contentsarr['sections']['section'])) { 203 $sectionarr = $contentsarr['sections']['section']; 204 foreach ($sectionarr as $section) { 205 $section = (object)$section; 206 $section->settings = array(); 207 $sections[basename($section->directory)] = $section; 208 } 209 $info->sections = $sections; 210 } 211 if (isset($contentsarr['activities']) && isset($contentsarr['activities']['activity'])) { 212 $activityarr = $contentsarr['activities']['activity']; 213 foreach ($activityarr as $activity) { 214 $activity = (object)$activity; 215 $activity->settings = array(); 216 $activities[basename($activity->directory)] = $activity; 217 } 218 $info->activities = $activities; 219 } 220 $info->root_settings = array(); // For root settings 221 222 // Now the settings, putting each one under its owner 223 $settingsarr = $infoarr['settings']['setting']; 224 foreach($settingsarr as $setting) { 225 switch ($setting['level']) { 226 case 'root': 227 $info->root_settings[$setting['name']] = $setting['value']; 228 break; 229 case 'course': 230 $info->course->settings[$setting['name']] = $setting['value']; 231 break; 232 case 'section': 233 $info->sections[$setting['section']]->settings[$setting['name']] = $setting['value']; 234 break; 235 case 'activity': 236 $info->activities[$setting['activity']]->settings[$setting['name']] = $setting['value']; 237 break; 238 default: // Shouldn't happen but tolerated for portability of customized backups. 239 debugging("Unknown backup setting level: {$setting['level']}", DEBUG_DEVELOPER); 240 break; 241 } 242 } 243 244 $cache = clone $info; 245 $cachesha1 = $moodlefilesha1; 246 return $info; 247 } 248 249 /** 250 * Load and format all the needed information from a backup file. 251 * 252 * This will only extract the moodle_backup.xml file from an MBZ 253 * file and then call {@link self::get_backup_information()}. 254 * 255 * This can be a long-running (multi-minute) operation for large backups. 256 * Pass a $progress value to receive progress updates. 257 * 258 * @param string $filepath absolute path to the MBZ file. 259 * @param file_progress $progress Progress updates 260 * @return stdClass containing information. 261 * @since Moodle 2.4 262 */ 263 public static function get_backup_information_from_mbz($filepath, file_progress $progress = null) { 264 global $CFG; 265 if (!is_readable($filepath)) { 266 throw new backup_helper_exception('missing_moodle_backup_file', $filepath); 267 } 268 269 // Extract moodle_backup.xml. 270 $tmpname = 'info_from_mbz_' . time() . '_' . random_string(4); 271 $tmpdir = make_backup_temp_directory($tmpname); 272 $fp = get_file_packer('application/vnd.moodle.backup'); 273 274 $extracted = $fp->extract_to_pathname($filepath, $tmpdir, array('moodle_backup.xml'), $progress); 275 $moodlefile = $tmpdir . '/' . 'moodle_backup.xml'; 276 if (!$extracted || !is_readable($moodlefile)) { 277 throw new backup_helper_exception('missing_moodle_backup_xml_file', $moodlefile); 278 } 279 280 // Read the information and delete the temporary directory. 281 $info = self::get_backup_information($tmpname); 282 remove_dir($tmpdir); 283 return $info; 284 } 285 286 /** 287 * Given the information fetched from moodle_backup.xml file 288 * decide if we are restoring in the same site the backup was 289 * generated or no. Behavior of various parts of restore are 290 * dependent of this. 291 * 292 * Backups created natively in 2.0 and later declare the hashed 293 * site identifier. Backups created by conversion from a 1.9 294 * backup do not declare such identifier, so there is a fallback 295 * to wwwroot comparison. See MDL-16614. 296 */ 297 public static function backup_is_samesite($info) { 298 global $CFG; 299 $hashedsiteid = md5(get_site_identifier()); 300 if (isset($info->original_site_identifier_hash) && !empty($info->original_site_identifier_hash)) { 301 return $info->original_site_identifier_hash == $hashedsiteid; 302 } else { 303 return $info->original_wwwroot == $CFG->wwwroot; 304 } 305 } 306 307 /** 308 * Detects the format of the given unpacked backup directory 309 * 310 * @param string $tempdir the name of the backup directory 311 * @return string one of backup::FORMAT_xxx constants 312 */ 313 public static function detect_backup_format($tempdir) { 314 global $CFG; 315 require_once($CFG->dirroot . '/backup/util/helper/convert_helper.class.php'); 316 317 if (convert_helper::detect_moodle2_format($tempdir)) { 318 return backup::FORMAT_MOODLE; 319 } 320 321 // see if a converter can identify the format 322 $converters = convert_helper::available_converters(); 323 foreach ($converters as $name) { 324 $classname = "{$name}_converter"; 325 if (!class_exists($classname)) { 326 throw new coding_exception("available_converters() is supposed to load 327 converter classes but class $classname not found"); 328 } 329 330 $detected = call_user_func($classname .'::detect_format', $tempdir); 331 if (!empty($detected)) { 332 return $detected; 333 } 334 } 335 336 return backup::FORMAT_UNKNOWN; 337 } 338 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body