[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
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 * Provides {@link convert_helper} and {@link convert_helper_exception} classes 20 * 21 * @package core 22 * @subpackage backup-convert 23 * @copyright 2011 Mark Nielsen <mark@moodlerooms.com> 24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 */ 26 27 defined('MOODLE_INTERNAL') || die(); 28 29 require_once($CFG->dirroot . '/backup/util/includes/convert_includes.php'); 30 31 /** 32 * Provides various functionality via its static methods 33 */ 34 abstract class convert_helper { 35 36 /** 37 * @param string $entropy 38 * @return string random identifier 39 */ 40 public static function generate_id($entropy) { 41 return md5(time() . '-' . $entropy . '-' . random_string(20)); 42 } 43 44 /** 45 * Returns the list of all available converters and loads their classes 46 * 47 * Converter must be installed as a directory in backup/converter/ and its 48 * method is_available() must return true to get to the list. 49 * 50 * @see base_converter::is_available() 51 * @return array of strings 52 */ 53 public static function available_converters($restore=true) { 54 global $CFG; 55 56 $converters = array(); 57 58 $plugins = get_list_of_plugins('backup/converter'); 59 foreach ($plugins as $name) { 60 $filename = $restore ? 'lib.php' : 'backuplib.php'; 61 $classuf = $restore ? '_converter' : '_export_converter'; 62 $classfile = "{$CFG->dirroot}/backup/converter/{$name}/{$filename}"; 63 $classname = "{$name}{$classuf}"; 64 $zip_contents = "{$name}_zip_contents"; 65 $store_backup_file = "{$name}_store_backup_file"; 66 $convert = "{$name}_backup_convert"; 67 68 if (!file_exists($classfile)) { 69 throw new convert_helper_exception('converter_classfile_not_found', $classfile); 70 } 71 72 require_once($classfile); 73 74 if (!class_exists($classname)) { 75 throw new convert_helper_exception('converter_classname_not_found', $classname); 76 } 77 78 if (call_user_func($classname .'::is_available')) { 79 if (!$restore) { 80 if (!class_exists($zip_contents)) { 81 throw new convert_helper_exception('converter_classname_not_found', $zip_contents); 82 } 83 if (!class_exists($store_backup_file)) { 84 throw new convert_helper_exception('converter_classname_not_found', $store_backup_file); 85 } 86 if (!class_exists($convert)) { 87 throw new convert_helper_exception('converter_classname_not_found', $convert); 88 } 89 } 90 91 $converters[] = $name; 92 } 93 94 } 95 96 return $converters; 97 } 98 99 public static function export_converter_dependencies($converter, $dependency) { 100 global $CFG; 101 102 $result = array(); 103 $filename = 'backuplib.php'; 104 $classuf = '_export_converter'; 105 $classfile = "{$CFG->dirroot}/backup/converter/{$converter}/{$filename}"; 106 $classname = "{$converter}{$classuf}"; 107 108 if (!file_exists($classfile)) { 109 throw new convert_helper_exception('converter_classfile_not_found', $classfile); 110 } 111 require_once($classfile); 112 113 if (!class_exists($classname)) { 114 throw new convert_helper_exception('converter_classname_not_found', $classname); 115 } 116 117 if (call_user_func($classname .'::is_available')) { 118 $deps = call_user_func($classname .'::get_deps'); 119 if (array_key_exists($dependency, $deps)) { 120 $result = $deps[$dependency]; 121 } 122 } 123 124 return $result; 125 } 126 127 /** 128 * Detects if the given folder contains an unpacked moodle2 backup 129 * 130 * @param string $tempdir the name of the backup directory 131 * @return boolean true if moodle2 format detected, false otherwise 132 */ 133 public static function detect_moodle2_format($tempdir) { 134 global $CFG; 135 136 $dirpath = $CFG->tempdir . '/backup/' . $tempdir; 137 $filepath = $dirpath . '/moodle_backup.xml'; 138 139 if (!is_dir($dirpath)) { 140 throw new convert_helper_exception('tmp_backup_directory_not_found', $dirpath); 141 } 142 143 if (!file_exists($filepath)) { 144 return false; 145 } 146 147 $handle = fopen($filepath, 'r'); 148 $firstchars = fread($handle, 200); 149 $status = fclose($handle); 150 151 if (strpos($firstchars,'<?xml version="1.0" encoding="UTF-8"?>') !== false and 152 strpos($firstchars,'<moodle_backup>') !== false and 153 strpos($firstchars,'<information>') !== false) { 154 return true; 155 } 156 157 return false; 158 } 159 160 /** 161 * Converts the given directory with the backup into moodle2 format 162 * 163 * @param string $tempdir The directory to convert 164 * @param string $format The current format, if already detected 165 * @param base_logger|null if the conversion should be logged, use this logger 166 * @throws convert_helper_exception 167 * @return bool false if unable to find the conversion path, true otherwise 168 */ 169 public static function to_moodle2_format($tempdir, $format = null, $logger = null) { 170 171 if (is_null($format)) { 172 $format = backup_general_helper::detect_backup_format($tempdir); 173 } 174 175 // get the supported conversion paths from all available converters 176 $converters = self::available_converters(); 177 $descriptions = array(); 178 foreach ($converters as $name) { 179 $classname = "{$name}_converter"; 180 if (!class_exists($classname)) { 181 throw new convert_helper_exception('class_not_loaded', $classname); 182 } 183 if ($logger instanceof base_logger) { 184 backup_helper::log('available converter', backup::LOG_DEBUG, $classname, 1, false, $logger); 185 } 186 $descriptions[$name] = call_user_func($classname .'::description'); 187 } 188 189 // choose the best conversion path for the given format 190 $path = self::choose_conversion_path($format, $descriptions); 191 192 if (empty($path)) { 193 if ($logger instanceof base_logger) { 194 backup_helper::log('unable to find the conversion path', backup::LOG_ERROR, null, 0, false, $logger); 195 } 196 return false; 197 } 198 199 if ($logger instanceof base_logger) { 200 backup_helper::log('conversion path established', backup::LOG_INFO, 201 implode(' => ', array_merge($path, array('moodle2'))), 0, false, $logger); 202 } 203 204 foreach ($path as $name) { 205 if ($logger instanceof base_logger) { 206 backup_helper::log('running converter', backup::LOG_INFO, $name, 0, false, $logger); 207 } 208 $converter = convert_factory::get_converter($name, $tempdir, $logger); 209 $converter->convert(); 210 } 211 212 // make sure we ended with moodle2 format 213 if (!self::detect_moodle2_format($tempdir)) { 214 throw new convert_helper_exception('conversion_failed'); 215 } 216 217 return true; 218 } 219 220 /** 221 * Inserts an inforef into the conversion temp table 222 */ 223 public static function set_inforef($contextid) { 224 global $DB; 225 } 226 227 public static function get_inforef($contextid) { 228 } 229 230 /// end of public API ////////////////////////////////////////////////////// 231 232 /** 233 * Choose the best conversion path for the given format 234 * 235 * Given the source format and the list of available converters and their properties, 236 * this methods picks the most effective way how to convert the source format into 237 * the target moodle2 format. The method returns a list of converters that should be 238 * called, in order. 239 * 240 * This implementation uses Dijkstra's algorithm to find the shortest way through 241 * the oriented graph. 242 * 243 * @see http://en.wikipedia.org/wiki/Dijkstra's_algorithm 244 * @author David Mudrak <david@moodle.com> 245 * @param string $format the source backup format, one of backup::FORMAT_xxx 246 * @param array $descriptions list of {@link base_converter::description()} indexed by the converter name 247 * @return array ordered list of converter names to call (may be empty if not reachable) 248 */ 249 protected static function choose_conversion_path($format, array $descriptions) { 250 251 // construct an oriented graph of conversion paths. backup formats are nodes 252 // and the the converters are edges of the graph. 253 $paths = array(); // [fromnode][tonode] => converter 254 foreach ($descriptions as $converter => $description) { 255 $from = $description['from']; 256 $to = $description['to']; 257 $cost = $description['cost']; 258 259 if (is_null($from) or $from === backup::FORMAT_UNKNOWN or 260 is_null($to) or $to === backup::FORMAT_UNKNOWN or 261 is_null($cost) or $cost <= 0) { 262 throw new convert_helper_exception('invalid_converter_description', $converter); 263 } 264 265 if (!isset($paths[$from][$to])) { 266 $paths[$from][$to] = $converter; 267 } else { 268 // if there are two converters available for the same conversion 269 // path, choose the one with the lowest cost. if there are more 270 // available converters with the same cost, the chosen one is 271 // undefined (depends on the order of processing) 272 if ($descriptions[$paths[$from][$to]]['cost'] > $cost) { 273 $paths[$from][$to] = $converter; 274 } 275 } 276 } 277 278 if (empty($paths)) { 279 // no conversion paths available 280 return array(); 281 } 282 283 // now use Dijkstra's algorithm and find the shortest conversion path 284 285 $dist = array(); // list of nodes and their distances from the source format 286 $prev = array(); // list of previous nodes in optimal path from the source format 287 foreach ($paths as $fromnode => $tonodes) { 288 $dist[$fromnode] = null; // infinitive distance, can't be reached 289 $prev[$fromnode] = null; // unknown 290 foreach ($tonodes as $tonode => $converter) { 291 $dist[$tonode] = null; // infinitive distance, can't be reached 292 $prev[$tonode] = null; // unknown 293 } 294 } 295 296 if (!array_key_exists($format, $dist)) { 297 return array(); 298 } else { 299 $dist[$format] = 0; 300 } 301 302 $queue = array_flip(array_keys($dist)); 303 while (!empty($queue)) { 304 // find the node with the smallest distance from the source in the queue 305 // in the first iteration, this will find the original format node itself 306 $closest = null; 307 foreach ($queue as $node => $undefined) { 308 if (is_null($dist[$node])) { 309 continue; 310 } 311 if (is_null($closest) or ($dist[$node] < $dist[$closest])) { 312 $closest = $node; 313 } 314 } 315 316 if (is_null($closest) or is_null($dist[$closest])) { 317 // all remaining nodes are inaccessible from source 318 break; 319 } 320 321 if ($closest === backup::FORMAT_MOODLE) { 322 // bingo we can break now 323 break; 324 } 325 326 unset($queue[$closest]); 327 328 // visit all neighbors and update distances to them eventually 329 330 if (!isset($paths[$closest])) { 331 continue; 332 } 333 $neighbors = array_keys($paths[$closest]); 334 // keep just neighbors that are in the queue yet 335 foreach ($neighbors as $ix => $neighbor) { 336 if (!array_key_exists($neighbor, $queue)) { 337 unset($neighbors[$ix]); 338 } 339 } 340 341 foreach ($neighbors as $neighbor) { 342 // the alternative distance to the neighbor if we went thru the 343 // current $closest node 344 $alt = $dist[$closest] + $descriptions[$paths[$closest][$neighbor]]['cost']; 345 346 if (is_null($dist[$neighbor]) or $alt < $dist[$neighbor]) { 347 // we found a shorter way to the $neighbor, remember it 348 $dist[$neighbor] = $alt; 349 $prev[$neighbor] = $closest; 350 } 351 } 352 } 353 354 if (is_null($dist[backup::FORMAT_MOODLE])) { 355 // unable to find a conversion path, the target format not reachable 356 return array(); 357 } 358 359 // reconstruct the optimal path from the source format to the target one 360 $conversionpath = array(); 361 $target = backup::FORMAT_MOODLE; 362 while (isset($prev[$target])) { 363 array_unshift($conversionpath, $paths[$prev[$target]][$target]); 364 $target = $prev[$target]; 365 } 366 367 return $conversionpath; 368 } 369 } 370 371 /** 372 * General convert_helper related exception 373 * 374 * @author David Mudrak <david@moodle.com> 375 */ 376 class convert_helper_exception extends moodle_exception { 377 378 /** 379 * Constructor 380 * 381 * @param string $errorcode key for the corresponding error string 382 * @param object $a extra words and phrases that might be required in the error string 383 * @param string $debuginfo optional debugging information 384 */ 385 public function __construct($errorcode, $a = null, $debuginfo = null) { 386 parent::__construct($errorcode, '', '', $a, $debuginfo); 387 } 388 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |