[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/backup/util/helper/ -> convert_helper.class.php (source)

   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  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1