|
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 00027 defined('MOODLE_INTERNAL') || die(); 00028 00029 require_once($CFG->dirroot . '/backup/util/includes/convert_includes.php'); 00030 00034 abstract class convert_helper { 00035 00040 public static function generate_id($entropy) { 00041 return md5(time() . '-' . $entropy . '-' . random_string(20)); 00042 } 00043 00053 public static function available_converters($restore=true) { 00054 global $CFG; 00055 00056 $converters = array(); 00057 00058 // Only apply for backup converters if the (experimental) setting enables it. 00059 // This will be out once we get proper support of backup converters. MDL-29956 00060 if (!$restore && empty($CFG->enablebackupconverters)) { 00061 return $converters; 00062 } 00063 00064 $plugins = get_list_of_plugins('backup/converter'); 00065 foreach ($plugins as $name) { 00066 $filename = $restore ? 'lib.php' : 'backuplib.php'; 00067 $classuf = $restore ? '_converter' : '_export_converter'; 00068 $classfile = "{$CFG->dirroot}/backup/converter/{$name}/{$filename}"; 00069 $classname = "{$name}{$classuf}"; 00070 $zip_contents = "{$name}_zip_contents"; 00071 $store_backup_file = "{$name}_store_backup_file"; 00072 $convert = "{$name}_backup_convert"; 00073 00074 if (!file_exists($classfile)) { 00075 throw new convert_helper_exception('converter_classfile_not_found', $classfile); 00076 } 00077 00078 require_once($classfile); 00079 00080 if (!class_exists($classname)) { 00081 throw new convert_helper_exception('converter_classname_not_found', $classname); 00082 } 00083 00084 if (call_user_func($classname .'::is_available')) { 00085 if (!$restore) { 00086 if (!class_exists($zip_contents)) { 00087 throw new convert_helper_exception('converter_classname_not_found', $zip_contents); 00088 } 00089 if (!class_exists($store_backup_file)) { 00090 throw new convert_helper_exception('converter_classname_not_found', $store_backup_file); 00091 } 00092 if (!class_exists($convert)) { 00093 throw new convert_helper_exception('converter_classname_not_found', $convert); 00094 } 00095 } 00096 00097 $converters[] = $name; 00098 } 00099 00100 } 00101 00102 return $converters; 00103 } 00104 00105 public static function export_converter_dependencies($converter, $dependency) { 00106 global $CFG; 00107 00108 $result = array(); 00109 $filename = 'backuplib.php'; 00110 $classuf = '_export_converter'; 00111 $classfile = "{$CFG->dirroot}/backup/converter/{$converter}/{$filename}"; 00112 $classname = "{$converter}{$classuf}"; 00113 00114 if (!file_exists($classfile)) { 00115 throw new convert_helper_exception('converter_classfile_not_found', $classfile); 00116 } 00117 require_once($classfile); 00118 00119 if (!class_exists($classname)) { 00120 throw new convert_helper_exception('converter_classname_not_found', $classname); 00121 } 00122 00123 if (call_user_func($classname .'::is_available')) { 00124 $deps = call_user_func($classname .'::get_deps'); 00125 if (array_key_exists($dependency, $deps)) { 00126 $result = $deps[$dependency]; 00127 } 00128 } 00129 00130 return $result; 00131 } 00132 00139 public static function detect_moodle2_format($tempdir) { 00140 global $CFG; 00141 00142 $dirpath = $CFG->tempdir . '/backup/' . $tempdir; 00143 $filepath = $dirpath . '/moodle_backup.xml'; 00144 00145 if (!is_dir($dirpath)) { 00146 throw new convert_helper_exception('tmp_backup_directory_not_found', $dirpath); 00147 } 00148 00149 if (!file_exists($filepath)) { 00150 return false; 00151 } 00152 00153 $handle = fopen($filepath, 'r'); 00154 $firstchars = fread($handle, 200); 00155 $status = fclose($handle); 00156 00157 if (strpos($firstchars,'<?xml version="1.0" encoding="UTF-8"?>') !== false and 00158 strpos($firstchars,'<moodle_backup>') !== false and 00159 strpos($firstchars,'<information>') !== false) { 00160 return true; 00161 } 00162 00163 return false; 00164 } 00165 00175 public static function to_moodle2_format($tempdir, $format = null, $logger = null) { 00176 00177 if (is_null($format)) { 00178 $format = backup_general_helper::detect_backup_format($tempdir); 00179 } 00180 00181 // get the supported conversion paths from all available converters 00182 $converters = self::available_converters(); 00183 $descriptions = array(); 00184 foreach ($converters as $name) { 00185 $classname = "{$name}_converter"; 00186 if (!class_exists($classname)) { 00187 throw new convert_helper_exception('class_not_loaded', $classname); 00188 } 00189 if ($logger instanceof base_logger) { 00190 backup_helper::log('available converter', backup::LOG_DEBUG, $classname, 1, false, $logger); 00191 } 00192 $descriptions[$name] = call_user_func($classname .'::description'); 00193 } 00194 00195 // choose the best conversion path for the given format 00196 $path = self::choose_conversion_path($format, $descriptions); 00197 00198 if (empty($path)) { 00199 if ($logger instanceof base_logger) { 00200 backup_helper::log('unable to find the conversion path', backup::LOG_ERROR, null, 0, false, $logger); 00201 } 00202 return false; 00203 } 00204 00205 if ($logger instanceof base_logger) { 00206 backup_helper::log('conversion path established', backup::LOG_INFO, 00207 implode(' => ', array_merge($path, array('moodle2'))), 0, false, $logger); 00208 } 00209 00210 foreach ($path as $name) { 00211 if ($logger instanceof base_logger) { 00212 backup_helper::log('running converter', backup::LOG_INFO, $name, 0, false, $logger); 00213 } 00214 $converter = convert_factory::get_converter($name, $tempdir, $logger); 00215 $converter->convert(); 00216 } 00217 00218 // make sure we ended with moodle2 format 00219 if (!self::detect_moodle2_format($tempdir)) { 00220 throw new convert_helper_exception('conversion_failed'); 00221 } 00222 00223 return true; 00224 } 00225 00229 public static function set_inforef($contextid) { 00230 global $DB; 00231 } 00232 00233 public static function get_inforef($contextid) { 00234 } 00235 00237 00255 protected static function choose_conversion_path($format, array $descriptions) { 00256 00257 // construct an oriented graph of conversion paths. backup formats are nodes 00258 // and the the converters are edges of the graph. 00259 $paths = array(); // [fromnode][tonode] => converter 00260 foreach ($descriptions as $converter => $description) { 00261 $from = $description['from']; 00262 $to = $description['to']; 00263 $cost = $description['cost']; 00264 00265 if (is_null($from) or $from === backup::FORMAT_UNKNOWN or 00266 is_null($to) or $to === backup::FORMAT_UNKNOWN or 00267 is_null($cost) or $cost <= 0) { 00268 throw new convert_helper_exception('invalid_converter_description', $converter); 00269 } 00270 00271 if (!isset($paths[$from][$to])) { 00272 $paths[$from][$to] = $converter; 00273 } else { 00274 // if there are two converters available for the same conversion 00275 // path, choose the one with the lowest cost. if there are more 00276 // available converters with the same cost, the chosen one is 00277 // undefined (depends on the order of processing) 00278 if ($descriptions[$paths[$from][$to]]['cost'] > $cost) { 00279 $paths[$from][$to] = $converter; 00280 } 00281 } 00282 } 00283 00284 if (empty($paths)) { 00285 // no conversion paths available 00286 return array(); 00287 } 00288 00289 // now use Dijkstra's algorithm and find the shortest conversion path 00290 00291 $dist = array(); // list of nodes and their distances from the source format 00292 $prev = array(); // list of previous nodes in optimal path from the source format 00293 foreach ($paths as $fromnode => $tonodes) { 00294 $dist[$fromnode] = null; // infinitive distance, can't be reached 00295 $prev[$fromnode] = null; // unknown 00296 foreach ($tonodes as $tonode => $converter) { 00297 $dist[$tonode] = null; // infinitive distance, can't be reached 00298 $prev[$tonode] = null; // unknown 00299 } 00300 } 00301 00302 if (!array_key_exists($format, $dist)) { 00303 return array(); 00304 } else { 00305 $dist[$format] = 0; 00306 } 00307 00308 $queue = array_flip(array_keys($dist)); 00309 while (!empty($queue)) { 00310 // find the node with the smallest distance from the source in the queue 00311 // in the first iteration, this will find the original format node itself 00312 $closest = null; 00313 foreach ($queue as $node => $undefined) { 00314 if (is_null($dist[$node])) { 00315 continue; 00316 } 00317 if (is_null($closest) or ($dist[$node] < $dist[$closest])) { 00318 $closest = $node; 00319 } 00320 } 00321 00322 if (is_null($closest) or is_null($dist[$closest])) { 00323 // all remaining nodes are inaccessible from source 00324 break; 00325 } 00326 00327 if ($closest === backup::FORMAT_MOODLE) { 00328 // bingo we can break now 00329 break; 00330 } 00331 00332 unset($queue[$closest]); 00333 00334 // visit all neighbors and update distances to them eventually 00335 00336 if (!isset($paths[$closest])) { 00337 continue; 00338 } 00339 $neighbors = array_keys($paths[$closest]); 00340 // keep just neighbors that are in the queue yet 00341 foreach ($neighbors as $ix => $neighbor) { 00342 if (!array_key_exists($neighbor, $queue)) { 00343 unset($neighbors[$ix]); 00344 } 00345 } 00346 00347 foreach ($neighbors as $neighbor) { 00348 // the alternative distance to the neighbor if we went thru the 00349 // current $closest node 00350 $alt = $dist[$closest] + $descriptions[$paths[$closest][$neighbor]]['cost']; 00351 00352 if (is_null($dist[$neighbor]) or $alt < $dist[$neighbor]) { 00353 // we found a shorter way to the $neighbor, remember it 00354 $dist[$neighbor] = $alt; 00355 $prev[$neighbor] = $closest; 00356 } 00357 } 00358 } 00359 00360 if (is_null($dist[backup::FORMAT_MOODLE])) { 00361 // unable to find a conversion path, the target format not reachable 00362 return array(); 00363 } 00364 00365 // reconstruct the optimal path from the source format to the target one 00366 $conversionpath = array(); 00367 $target = backup::FORMAT_MOODLE; 00368 while (isset($prev[$target])) { 00369 array_unshift($conversionpath, $paths[$prev[$target]][$target]); 00370 $target = $prev[$target]; 00371 } 00372 00373 return $conversionpath; 00374 } 00375 } 00376 00382 class convert_helper_exception extends moodle_exception { 00383 00391 public function __construct($errorcode, $a = null, $debuginfo = null) { 00392 parent::__construct($errorcode, '', '', $a, $debuginfo); 00393 } 00394 }