[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/backup/converter/moodle1/ -> lib.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 classes used by the moodle1 converter
  20   *
  21   * @package    backup-convert
  22   * @subpackage moodle1
  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/converter/convertlib.php');
  30  require_once($CFG->dirroot . '/backup/util/xml/parser/progressive_parser.class.php');
  31  require_once($CFG->dirroot . '/backup/util/xml/parser/processors/grouped_parser_processor.class.php');
  32  require_once($CFG->dirroot . '/backup/util/dbops/backup_dbops.class.php');
  33  require_once($CFG->dirroot . '/backup/util/dbops/backup_controller_dbops.class.php');
  34  require_once($CFG->dirroot . '/backup/util/dbops/restore_dbops.class.php');
  35  require_once($CFG->dirroot . '/backup/util/xml/contenttransformer/xml_contenttransformer.class.php');
  36  require_once (__DIR__ . '/handlerlib.php');
  37  
  38  /**
  39   * Converter of Moodle 1.9 backup into Moodle 2.x format
  40   */
  41  class moodle1_converter extends base_converter {
  42  
  43      /** @var progressive_parser moodle.xml file parser */
  44      protected $xmlparser;
  45  
  46      /** @var moodle1_parser_processor */
  47      protected $xmlprocessor;
  48  
  49      /** @var array of {@link convert_path} to process */
  50      protected $pathelements = array();
  51  
  52      /** @var null|string the current module being processed - used to expand the MOD paths */
  53      protected $currentmod = null;
  54  
  55      /** @var null|string the current block being processed - used to expand the BLOCK paths */
  56      protected $currentblock = null;
  57  
  58      /** @var string path currently locking processing of children */
  59      protected $pathlock;
  60  
  61      /** @var int used by the serial number {@link get_nextid()} */
  62      private $nextid = 1;
  63  
  64      /**
  65       * Instructs the dispatcher to ignore all children below path processor returning it
  66       */
  67      const SKIP_ALL_CHILDREN = -991399;
  68  
  69      /**
  70       * Log a message
  71       *
  72       * @see parent::log()
  73       * @param string $message message text
  74       * @param int $level message level {@example backup::LOG_WARNING}
  75       * @param null|mixed $a additional information
  76       * @param null|int $depth the message depth
  77       * @param bool $display whether the message should be sent to the output, too
  78       */
  79      public function log($message, $level, $a = null, $depth = null, $display = false) {
  80          parent::log('(moodle1) '.$message, $level, $a, $depth, $display);
  81      }
  82  
  83      /**
  84       * Detects the Moodle 1.9 format of the backup directory
  85       *
  86       * @param string $tempdir the name of the backup directory
  87       * @return null|string backup::FORMAT_MOODLE1 if the Moodle 1.9 is detected, null otherwise
  88       */
  89      public static function detect_format($tempdir) {
  90          global $CFG;
  91  
  92          $filepath = $CFG->tempdir . '/backup/' . $tempdir . '/moodle.xml';
  93          if (file_exists($filepath)) {
  94              // looks promising, lets load some information
  95              $handle = fopen($filepath, 'r');
  96              $first_chars = fread($handle, 200);
  97              fclose($handle);
  98  
  99              // check if it has the required strings
 100              if (strpos($first_chars,'<?xml version="1.0" encoding="UTF-8"?>') !== false and
 101                  strpos($first_chars,'<MOODLE_BACKUP>') !== false and
 102                  strpos($first_chars,'<INFO>') !== false) {
 103  
 104                  return backup::FORMAT_MOODLE1;
 105              }
 106          }
 107  
 108          return null;
 109      }
 110  
 111      /**
 112       * Initialize the instance if needed, called by the constructor
 113       *
 114       * Here we create objects we need before the execution.
 115       */
 116      protected function init() {
 117  
 118          // ask your mother first before going out playing with toys
 119          parent::init();
 120  
 121          $this->log('initializing '.$this->get_name().' converter', backup::LOG_INFO);
 122  
 123          // good boy, prepare XML parser and processor
 124          $this->log('setting xml parser', backup::LOG_DEBUG, null, 1);
 125          $this->xmlparser = new progressive_parser();
 126          $this->xmlparser->set_file($this->get_tempdir_path() . '/moodle.xml');
 127          $this->log('setting xml processor', backup::LOG_DEBUG, null, 1);
 128          $this->xmlprocessor = new moodle1_parser_processor($this);
 129          $this->xmlparser->set_processor($this->xmlprocessor);
 130  
 131          // make sure that MOD and BLOCK paths are visited
 132          $this->xmlprocessor->add_path('/MOODLE_BACKUP/COURSE/MODULES/MOD');
 133          $this->xmlprocessor->add_path('/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK');
 134  
 135          // register the conversion handlers
 136          foreach (moodle1_handlers_factory::get_handlers($this) as $handler) {
 137              $this->log('registering handler', backup::LOG_DEBUG, get_class($handler), 1);
 138              $this->register_handler($handler, $handler->get_paths());
 139          }
 140      }
 141  
 142      /**
 143       * Converts the contents of the tempdir into the target format in the workdir
 144       */
 145      protected function execute() {
 146          $this->log('creating the stash storage', backup::LOG_DEBUG);
 147          $this->create_stash_storage();
 148  
 149          $this->log('parsing moodle.xml starts', backup::LOG_DEBUG);
 150          $this->xmlparser->process();
 151          $this->log('parsing moodle.xml done', backup::LOG_DEBUG);
 152  
 153          $this->log('dropping the stash storage', backup::LOG_DEBUG);
 154          $this->drop_stash_storage();
 155      }
 156  
 157      /**
 158       * Register a handler for the given path elements
 159       */
 160      protected function register_handler(moodle1_handler $handler, array $elements) {
 161  
 162          // first iteration, push them to new array, indexed by name
 163          // to detect duplicates in names or paths
 164          $names = array();
 165          $paths = array();
 166          foreach($elements as $element) {
 167              if (!$element instanceof convert_path) {
 168                  throw new convert_exception('path_element_wrong_class', get_class($element));
 169              }
 170              if (array_key_exists($element->get_name(), $names)) {
 171                  throw new convert_exception('path_element_name_alreadyexists', $element->get_name());
 172              }
 173              if (array_key_exists($element->get_path(), $paths)) {
 174                  throw new convert_exception('path_element_path_alreadyexists', $element->get_path());
 175              }
 176              $names[$element->get_name()] = true;
 177              $paths[$element->get_path()] = $element;
 178          }
 179  
 180          // now, for each element not having a processing object yet, assign the handler
 181          // if the element is not a memeber of a group
 182          foreach($paths as $key => $element) {
 183              if (is_null($element->get_processing_object()) and !$this->grouped_parent_exists($element, $paths)) {
 184                  $paths[$key]->set_processing_object($handler);
 185              }
 186              // add the element path to the processor
 187              $this->xmlprocessor->add_path($element->get_path(), $element->is_grouped());
 188          }
 189  
 190          // done, store the paths (duplicates by path are discarded)
 191          $this->pathelements = array_merge($this->pathelements, $paths);
 192  
 193          // remove the injected plugin name element from the MOD and BLOCK paths
 194          // and register such collapsed path, too
 195          foreach ($elements as $element) {
 196              $path = $element->get_path();
 197              $path = preg_replace('/^\/MOODLE_BACKUP\/COURSE\/MODULES\/MOD\/(\w+)\//', '/MOODLE_BACKUP/COURSE/MODULES/MOD/', $path);
 198              $path = preg_replace('/^\/MOODLE_BACKUP\/COURSE\/BLOCKS\/BLOCK\/(\w+)\//', '/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK/', $path);
 199              if (!empty($path) and $path != $element->get_path()) {
 200                  $this->xmlprocessor->add_path($path, false);
 201              }
 202          }
 203      }
 204  
 205      /**
 206       * Helper method used by {@link self::register_handler()}
 207       *
 208       * @param convert_path $pelement path element
 209       * @param array of convert_path instances
 210       * @return bool true if grouped parent was found, false otherwise
 211       */
 212      protected function grouped_parent_exists($pelement, $elements) {
 213  
 214          foreach ($elements as $element) {
 215              if ($pelement->get_path() == $element->get_path()) {
 216                  // don't compare against itself
 217                  continue;
 218              }
 219              // if the element is grouped and it is a parent of pelement, return true
 220              if ($element->is_grouped() and strpos($pelement->get_path() .  '/', $element->get_path()) === 0) {
 221                  return true;
 222              }
 223          }
 224  
 225          // no grouped parent found
 226          return false;
 227      }
 228  
 229      /**
 230       * Process the data obtained from the XML parser processor
 231       *
 232       * This methods receives one chunk of information from the XML parser
 233       * processor and dispatches it, following the naming rules.
 234       * We are expanding the modules and blocks paths here to include the plugin's name.
 235       *
 236       * @param array $data
 237       */
 238      public function process_chunk($data) {
 239  
 240          $path = $data['path'];
 241  
 242          // expand the MOD paths so that they contain the module name
 243          if ($path === '/MOODLE_BACKUP/COURSE/MODULES/MOD') {
 244              $this->currentmod = strtoupper($data['tags']['MODTYPE']);
 245              $path = '/MOODLE_BACKUP/COURSE/MODULES/MOD/' . $this->currentmod;
 246  
 247          } else if (strpos($path, '/MOODLE_BACKUP/COURSE/MODULES/MOD') === 0) {
 248              $path = str_replace('/MOODLE_BACKUP/COURSE/MODULES/MOD', '/MOODLE_BACKUP/COURSE/MODULES/MOD/' . $this->currentmod, $path);
 249          }
 250  
 251          // expand the BLOCK paths so that they contain the module name
 252          if ($path === '/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK') {
 253              $this->currentblock = strtoupper($data['tags']['NAME']);
 254              $path = '/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK/' . $this->currentblock;
 255  
 256          } else if (strpos($path, '/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK') === 0) {
 257              $path = str_replace('/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK', '/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK/' . $this->currentblock, $path);
 258          }
 259  
 260          if ($path !== $data['path']) {
 261              if (!array_key_exists($path, $this->pathelements)) {
 262                  // no handler registered for the transformed MOD or BLOCK path
 263                  $this->log('no handler attached', backup::LOG_WARNING, $path);
 264                  return;
 265  
 266              } else {
 267                  // pretend as if the original $data contained the tranformed path
 268                  $data['path'] = $path;
 269              }
 270          }
 271  
 272          if (!array_key_exists($data['path'], $this->pathelements)) {
 273              // path added to the processor without the handler
 274              throw new convert_exception('missing_path_handler', $data['path']);
 275          }
 276  
 277          $element  = $this->pathelements[$data['path']];
 278          $object   = $element->get_processing_object();
 279          $method   = $element->get_processing_method();
 280          $returned = null; // data returned by the processing method, if any
 281  
 282          if (empty($object)) {
 283              throw new convert_exception('missing_processing_object', null, $data['path']);
 284          }
 285  
 286          // release the lock if we aren't anymore within children of it
 287          if (!is_null($this->pathlock) and strpos($data['path'], $this->pathlock) === false) {
 288              $this->pathlock = null;
 289          }
 290  
 291          // if the path is not locked, apply the element's recipes and dispatch
 292          // the cooked tags to the processing method
 293          if (is_null($this->pathlock)) {
 294              $rawdatatags  = $data['tags'];
 295              $data['tags'] = $element->apply_recipes($data['tags']);
 296  
 297              // if the processing method exists, give it a chance to modify data
 298              if (method_exists($object, $method)) {
 299                  $returned = $object->$method($data['tags'], $rawdatatags);
 300              }
 301          }
 302  
 303          // if the dispatched method returned SKIP_ALL_CHILDREN, remember the current path
 304          // and lock it so that its children are not dispatched
 305          if ($returned === self::SKIP_ALL_CHILDREN) {
 306              // check we haven't any previous lock
 307              if (!is_null($this->pathlock)) {
 308                  throw new convert_exception('already_locked_path', $data['path']);
 309              }
 310              // set the lock - nothing below the current path will be dispatched
 311              $this->pathlock = $data['path'] . '/';
 312  
 313          // if the method has returned any info, set element data to it
 314          } else if (!is_null($returned)) {
 315              $element->set_tags($returned);
 316  
 317          // use just the cooked parsed data otherwise
 318          } else {
 319              $element->set_tags($data['tags']);
 320          }
 321      }
 322  
 323      /**
 324       * Executes operations required at the start of a watched path
 325       *
 326       * For MOD and BLOCK paths, this is supported only for the sub-paths, not the root
 327       * module/block element. For the illustration:
 328       *
 329       * You CAN'T attach on_xxx_start() listener to a path like
 330       * /MOODLE_BACKUP/COURSE/MODULES/MOD/WORKSHOP because the <MOD> must
 331       * be processed first in {@link self::process_chunk()} where $this->currentmod
 332       * is set.
 333       *
 334       * You CAN attach some on_xxx_start() listener to a path like
 335       * /MOODLE_BACKUP/COURSE/MODULES/MOD/WORKSHOP/SUBMISSIONS because it is
 336       * a sub-path under <MOD> and we have $this->currentmod already set when the
 337       * <SUBMISSIONS> is reached.
 338       *
 339       * @param string $path in the original file
 340       */
 341      public function path_start_reached($path) {
 342  
 343          if ($path === '/MOODLE_BACKUP/COURSE/MODULES/MOD') {
 344              $this->currentmod = null;
 345              $forbidden = true;
 346  
 347          } else if (strpos($path, '/MOODLE_BACKUP/COURSE/MODULES/MOD') === 0) {
 348              // expand the MOD paths so that they contain the module name
 349              $path = str_replace('/MOODLE_BACKUP/COURSE/MODULES/MOD', '/MOODLE_BACKUP/COURSE/MODULES/MOD/' . $this->currentmod, $path);
 350          }
 351  
 352          if ($path === '/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK') {
 353              $this->currentblock = null;
 354              $forbidden = true;
 355  
 356          } else if (strpos($path, '/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK') === 0) {
 357              // expand the BLOCK paths so that they contain the module name
 358              $path = str_replace('/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK', '/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK/' . $this->currentblock, $path);
 359          }
 360  
 361          if (empty($this->pathelements[$path])) {
 362              return;
 363          }
 364  
 365          $element = $this->pathelements[$path];
 366          $pobject = $element->get_processing_object();
 367          $method  = $element->get_start_method();
 368  
 369          if (method_exists($pobject, $method)) {
 370              if (empty($forbidden)) {
 371                  $pobject->$method();
 372  
 373              } else {
 374                  // this path is not supported because we do not know the module/block yet
 375                  throw new coding_exception('Attaching the on-start event listener to the root MOD or BLOCK element is forbidden.');
 376              }
 377          }
 378      }
 379  
 380      /**
 381       * Executes operations required at the end of a watched path
 382       *
 383       * @param string $path in the original file
 384       */
 385      public function path_end_reached($path) {
 386  
 387          // expand the MOD paths so that they contain the current module name
 388          if ($path === '/MOODLE_BACKUP/COURSE/MODULES/MOD') {
 389              $path = '/MOODLE_BACKUP/COURSE/MODULES/MOD/' . $this->currentmod;
 390  
 391          } else if (strpos($path, '/MOODLE_BACKUP/COURSE/MODULES/MOD') === 0) {
 392              $path = str_replace('/MOODLE_BACKUP/COURSE/MODULES/MOD', '/MOODLE_BACKUP/COURSE/MODULES/MOD/' . $this->currentmod, $path);
 393          }
 394  
 395          // expand the BLOCK paths so that they contain the module name
 396          if ($path === '/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK') {
 397              $path = '/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK/' . $this->currentblock;
 398  
 399          } else if (strpos($path, '/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK') === 0) {
 400              $path = str_replace('/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK', '/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK/' . $this->currentblock, $path);
 401          }
 402  
 403          if (empty($this->pathelements[$path])) {
 404              return;
 405          }
 406  
 407          $element = $this->pathelements[$path];
 408          $pobject = $element->get_processing_object();
 409          $method  = $element->get_end_method();
 410          $tags    = $element->get_tags();
 411  
 412          if (method_exists($pobject, $method)) {
 413              $pobject->$method($tags);
 414          }
 415      }
 416  
 417      /**
 418       * Creates the temporary storage for stashed data
 419       *
 420       * This implementation uses backup_ids_temp table.
 421       */
 422      public function create_stash_storage() {
 423          backup_controller_dbops::create_backup_ids_temp_table($this->get_id());
 424      }
 425  
 426      /**
 427       * Drops the temporary storage of stashed data
 428       *
 429       * This implementation uses backup_ids_temp table.
 430       */
 431      public function drop_stash_storage() {
 432          backup_controller_dbops::drop_backup_ids_temp_table($this->get_id());
 433      }
 434  
 435      /**
 436       * Stores some information for later processing
 437       *
 438       * This implementation uses backup_ids_temp table to store data. Make
 439       * sure that the $stashname + $itemid combo is unique.
 440       *
 441       * @param string $stashname name of the stash
 442       * @param mixed $info information to stash
 443       * @param int $itemid optional id for multiple infos within the same stashname
 444       */
 445      public function set_stash($stashname, $info, $itemid = 0) {
 446          try {
 447              restore_dbops::set_backup_ids_record($this->get_id(), $stashname, $itemid, 0, null, $info);
 448  
 449          } catch (dml_exception $e) {
 450              throw new moodle1_convert_storage_exception('unable_to_restore_stash', null, $e->getMessage());
 451          }
 452      }
 453  
 454      /**
 455       * Restores a given stash stored previously by {@link self::set_stash()}
 456       *
 457       * @param string $stashname name of the stash
 458       * @param int $itemid optional id for multiple infos within the same stashname
 459       * @throws moodle1_convert_empty_storage_exception if the info has not been stashed previously
 460       * @return mixed stashed data
 461       */
 462      public function get_stash($stashname, $itemid = 0) {
 463  
 464          $record = restore_dbops::get_backup_ids_record($this->get_id(), $stashname, $itemid);
 465  
 466          if (empty($record)) {
 467              throw new moodle1_convert_empty_storage_exception('required_not_stashed_data', array($stashname, $itemid));
 468          } else {
 469              if (empty($record->info)) {
 470                  return array();
 471              }
 472              return $record->info;
 473          }
 474      }
 475  
 476      /**
 477       * Restores a given stash or returns the given default if there is no such stash
 478       *
 479       * @param string $stashname name of the stash
 480       * @param int $itemid optional id for multiple infos within the same stashname
 481       * @param mixed $default information to return if the info has not been stashed previously
 482       * @return mixed stashed data or the default value
 483       */
 484      public function get_stash_or_default($stashname, $itemid = 0, $default = null) {
 485          try {
 486              return $this->get_stash($stashname, $itemid);
 487          } catch (moodle1_convert_empty_storage_exception $e) {
 488              return $default;
 489          }
 490      }
 491  
 492      /**
 493       * Returns the list of existing stashes
 494       *
 495       * @return array
 496       */
 497      public function get_stash_names() {
 498          global $DB;
 499  
 500          $search = array(
 501              'backupid' => $this->get_id(),
 502          );
 503  
 504          return array_keys($DB->get_records('backup_ids_temp', $search, '', 'itemname'));
 505      }
 506  
 507      /**
 508       * Returns the list of stashed $itemids in the given stash
 509       *
 510       * @param string $stashname
 511       * @return array
 512       */
 513      public function get_stash_itemids($stashname) {
 514          global $DB;
 515  
 516          $search = array(
 517              'backupid' => $this->get_id(),
 518              'itemname' => $stashname
 519          );
 520  
 521          return array_keys($DB->get_records('backup_ids_temp', $search, '', 'itemid'));
 522      }
 523  
 524      /**
 525       * Generates an artificial context id
 526       *
 527       * Moodle 1.9 backups do not contain any context information. But we need them
 528       * in Moodle 2.x format so here we generate fictive context id for every given
 529       * context level + instance combo.
 530       *
 531       * CONTEXT_SYSTEM and CONTEXT_COURSE ignore the $instance as they represent a
 532       * single system or the course being restored.
 533       *
 534       * @see context_system::instance()
 535       * @see context_course::instance()
 536       * @param int $level the context level, like CONTEXT_COURSE or CONTEXT_MODULE
 537       * @param int $instance the instance id, for example $course->id for courses or $cm->id for activity modules
 538       * @return int the context id
 539       */
 540      public function get_contextid($level, $instance = 0) {
 541  
 542          $stashname = 'context' . $level;
 543  
 544          if ($level == CONTEXT_SYSTEM or $level == CONTEXT_COURSE) {
 545              $instance = 0;
 546          }
 547  
 548          try {
 549              // try the previously stashed id
 550              return $this->get_stash($stashname, $instance);
 551  
 552          } catch (moodle1_convert_empty_storage_exception $e) {
 553              // this context level + instance is required for the first time
 554              $newid = $this->get_nextid();
 555              $this->set_stash($stashname, $newid, $instance);
 556              return $newid;
 557          }
 558      }
 559  
 560      /**
 561       * Simple autoincrement generator
 562       *
 563       * @return int the next number in a row of numbers
 564       */
 565      public function get_nextid() {
 566          return $this->nextid++;
 567      }
 568  
 569      /**
 570       * Creates and returns new instance of the file manager
 571       *
 572       * @param int $contextid the default context id of the files being migrated
 573       * @param string $component the default component name of the files being migrated
 574       * @param string $filearea the default file area of the files being migrated
 575       * @param int $itemid the default item id of the files being migrated
 576       * @param int $userid initial user id of the files being migrated
 577       * @return moodle1_file_manager
 578       */
 579      public function get_file_manager($contextid = null, $component = null, $filearea = null, $itemid = 0, $userid = null) {
 580          return new moodle1_file_manager($this, $contextid, $component, $filearea, $itemid, $userid);
 581      }
 582  
 583      /**
 584       * Creates and returns new instance of the inforef manager
 585       *
 586       * @param string $name the name of the annotator (like course, section, activity, block)
 587       * @param int $id the id of the annotator if required
 588       * @return moodle1_inforef_manager
 589       */
 590      public function get_inforef_manager($name, $id = 0) {
 591          return new moodle1_inforef_manager($this, $name, $id);
 592      }
 593  
 594  
 595      /**
 596       * Migrates all course files referenced from the hypertext using the given filemanager
 597       *
 598       * This is typically used to convert images embedded into the intro fields.
 599       *
 600       * @param string $text hypertext containing $@FILEPHP@$ referenced
 601       * @param moodle1_file_manager $fileman file manager to use for the file migration
 602       * @return string the original $text with $@FILEPHP@$ references replaced with the new @@PLUGINFILE@@
 603       */
 604      public static function migrate_referenced_files($text, moodle1_file_manager $fileman) {
 605  
 606          $files = self::find_referenced_files($text);
 607          if (!empty($files)) {
 608              foreach ($files as $file) {
 609                  try {
 610                      $fileman->migrate_file('course_files'.$file, dirname($file));
 611                  } catch (moodle1_convert_exception $e) {
 612                      // file probably does not exist
 613                      $fileman->log('error migrating file', backup::LOG_WARNING, 'course_files'.$file);
 614                  }
 615              }
 616              $text = self::rewrite_filephp_usage($text, $files);
 617          }
 618  
 619          return $text;
 620      }
 621  
 622      /**
 623       * Detects all links to file.php encoded via $@FILEPHP@$ and returns the files to migrate
 624       *
 625       * @see self::migrate_referenced_files()
 626       * @param string $text
 627       * @return array
 628       */
 629      public static function find_referenced_files($text) {
 630  
 631          $files = array();
 632  
 633          if (empty($text) or is_numeric($text)) {
 634              return $files;
 635          }
 636  
 637          $matches = array();
 638          $pattern = '|(["\'])(\$@FILEPHP@\$.+?)\1|';
 639          $result = preg_match_all($pattern, $text, $matches);
 640          if ($result === false) {
 641              throw new moodle1_convert_exception('error_while_searching_for_referenced_files');
 642          }
 643          if ($result == 0) {
 644              return $files;
 645          }
 646          foreach ($matches[2] as $match) {
 647              $file = str_replace(array('$@FILEPHP@$', '$@SLASH@$', '$@FORCEDOWNLOAD@$'), array('', '/', ''), $match);
 648              if ($file === clean_param($file, PARAM_PATH)) {
 649                  $files[] = rawurldecode($file);
 650              }
 651          }
 652  
 653          return array_unique($files);
 654      }
 655  
 656      /**
 657       * Given the list of migrated files, rewrites references to them from $@FILEPHP@$ form to the @@PLUGINFILE@@ one
 658       *
 659       * @see self::migrate_referenced_files()
 660       * @param string $text
 661       * @param array $files
 662       * @return string
 663       */
 664      public static function rewrite_filephp_usage($text, array $files) {
 665  
 666          foreach ($files as $file) {
 667              // Expect URLs properly encoded by default.
 668              $parts   = explode('/', $file);
 669              $encoded = implode('/', array_map('rawurlencode', $parts));
 670              $fileref = '$@FILEPHP@$'.str_replace('/', '$@SLASH@$', $encoded);
 671              $text    = str_replace($fileref.'$@FORCEDOWNLOAD@$', '@@PLUGINFILE@@'.$encoded.'?forcedownload=1', $text);
 672              $text    = str_replace($fileref, '@@PLUGINFILE@@'.$encoded, $text);
 673              // Add support for URLs without any encoding.
 674              $fileref = '$@FILEPHP@$'.str_replace('/', '$@SLASH@$', $file);
 675              $text    = str_replace($fileref.'$@FORCEDOWNLOAD@$', '@@PLUGINFILE@@'.$encoded.'?forcedownload=1', $text);
 676              $text    = str_replace($fileref, '@@PLUGINFILE@@'.$encoded, $text);
 677          }
 678  
 679          return $text;
 680      }
 681  
 682      /**
 683       * @see parent::description()
 684       */
 685      public static function description() {
 686  
 687          return array(
 688              'from'  => backup::FORMAT_MOODLE1,
 689              'to'    => backup::FORMAT_MOODLE,
 690              'cost'  => 10,
 691          );
 692      }
 693  }
 694  
 695  
 696  /**
 697   * Exception thrown by this converter
 698   */
 699  class moodle1_convert_exception extends convert_exception {
 700  }
 701  
 702  
 703  /**
 704   * Exception thrown by the temporary storage subsystem of moodle1_converter
 705   */
 706  class moodle1_convert_storage_exception extends moodle1_convert_exception {
 707  }
 708  
 709  
 710  /**
 711   * Exception thrown by the temporary storage subsystem of moodle1_converter
 712   */
 713  class moodle1_convert_empty_storage_exception extends moodle1_convert_exception {
 714  }
 715  
 716  
 717  /**
 718   * XML parser processor used for processing parsed moodle.xml
 719   */
 720  class moodle1_parser_processor extends grouped_parser_processor {
 721  
 722      /** @var moodle1_converter */
 723      protected $converter;
 724  
 725      public function __construct(moodle1_converter $converter) {
 726          $this->converter = $converter;
 727          parent::__construct();
 728      }
 729  
 730      /**
 731       * Provides NULL decoding
 732       *
 733       * Note that we do not decode $@FILEPHP@$ and friends here as we are going to write them
 734       * back immediately into another XML file.
 735       */
 736      public function process_cdata($cdata) {
 737  
 738          if ($cdata === '$@NULL@$') {
 739              return null;
 740          }
 741  
 742          return $cdata;
 743      }
 744  
 745      /**
 746       * Dispatches the data chunk to the converter class
 747       *
 748       * @param array $data the chunk of parsed data
 749       */
 750      protected function dispatch_chunk($data) {
 751          $this->converter->process_chunk($data);
 752      }
 753  
 754      /**
 755       * Informs the converter at the start of a watched path
 756       *
 757       * @param string $path
 758       */
 759      protected function notify_path_start($path) {
 760          $this->converter->path_start_reached($path);
 761      }
 762  
 763      /**
 764       * Informs the converter at the end of a watched path
 765       *
 766       * @param string $path
 767       */
 768      protected function notify_path_end($path) {
 769          $this->converter->path_end_reached($path);
 770      }
 771  }
 772  
 773  
 774  /**
 775   * XML transformer that modifies the content of the files being written during the conversion
 776   *
 777   * @see backup_xml_transformer
 778   */
 779  class moodle1_xml_transformer extends xml_contenttransformer {
 780  
 781      /**
 782       * Modify the content before it is writter to a file
 783       *
 784       * @param string|mixed $content
 785       */
 786      public function process($content) {
 787  
 788          // the content should be a string. If array or object is given, try our best recursively
 789          // but inform the developer
 790          if (is_array($content)) {
 791              debugging('Moodle1 XML transformer should not process arrays but plain content always', DEBUG_DEVELOPER);
 792              foreach($content as $key => $plaincontent) {
 793                  $content[$key] = $this->process($plaincontent);
 794              }
 795              return $content;
 796  
 797          } else if (is_object($content)) {
 798              debugging('Moodle1 XML transformer should not process objects but plain content always', DEBUG_DEVELOPER);
 799              foreach((array)$content as $key => $plaincontent) {
 800                  $content[$key] = $this->process($plaincontent);
 801              }
 802              return (object)$content;
 803          }
 804  
 805          // try to deal with some trivial cases first
 806          if (is_null($content)) {
 807              return '$@NULL@$';
 808  
 809          } else if ($content === '') {
 810              return '';
 811  
 812          } else if (is_numeric($content)) {
 813              return $content;
 814  
 815          } else if (strlen($content) < 32) {
 816              return $content;
 817          }
 818  
 819          return $content;
 820      }
 821  }
 822  
 823  
 824  /**
 825   * Class representing a path to be converted from XML file
 826   *
 827   * This was created as a copy of {@link restore_path_element} and should be refactored
 828   * probably.
 829   */
 830  class convert_path {
 831  
 832      /** @var string name of the element */
 833      protected $name;
 834  
 835      /** @var string path within the XML file this element will handle */
 836      protected $path;
 837  
 838      /** @var bool flag to define if this element will get child ones grouped or no */
 839      protected $grouped;
 840  
 841      /** @var object object instance in charge of processing this element. */
 842      protected $pobject = null;
 843  
 844      /** @var string the name of the processing method */
 845      protected $pmethod = null;
 846  
 847      /** @var string the name of the path start event handler */
 848      protected $smethod = null;
 849  
 850      /** @var string the name of the path end event handler */
 851      protected $emethod = null;
 852  
 853      /** @var mixed last data read for this element or returned data by processing method */
 854      protected $tags = null;
 855  
 856      /** @var array of deprecated fields that are dropped */
 857      protected $dropfields = array();
 858  
 859      /** @var array of fields renaming */
 860      protected $renamefields = array();
 861  
 862      /** @var array of new fields to add and their initial values */
 863      protected $newfields = array();
 864  
 865      /**
 866       * Constructor
 867       *
 868       * The optional recipe array can have three keys, and for each key, the value is another array.
 869       * - newfields    => array fieldname => defaultvalue indicates fields that have been added to the table,
 870       *                                                   and so should be added to the XML.
 871       * - dropfields   => array fieldname                 indicates fieldsthat have been dropped from the table,
 872       *                                                   and so can be dropped from the XML.
 873       * - renamefields => array oldname => newname        indicates fieldsthat have been renamed in the table,
 874       *                                                   and so should be renamed in the XML.
 875       * {@line moodle1_course_outline_handler} is a good example that uses all of these.
 876       *
 877       * @param string $name name of the element
 878       * @param string $path path of the element
 879       * @param array $recipe basic description of the structure conversion
 880       * @param bool $grouped to gather information in grouped mode or no
 881       */
 882      public function __construct($name, $path, array $recipe = array(), $grouped = false) {
 883  
 884          $this->validate_name($name);
 885  
 886          $this->name     = $name;
 887          $this->path     = $path;
 888          $this->grouped  = $grouped;
 889  
 890          // set the default method names
 891          $this->set_processing_method('process_' . $name);
 892          $this->set_start_method('on_'.$name.'_start');
 893          $this->set_end_method('on_'.$name.'_end');
 894  
 895          if ($grouped and !empty($recipe)) {
 896              throw new convert_path_exception('recipes_not_supported_for_grouped_elements');
 897          }
 898  
 899          if (isset($recipe['dropfields']) and is_array($recipe['dropfields'])) {
 900              $this->set_dropped_fields($recipe['dropfields']);
 901          }
 902          if (isset($recipe['renamefields']) and is_array($recipe['renamefields'])) {
 903              $this->set_renamed_fields($recipe['renamefields']);
 904          }
 905          if (isset($recipe['newfields']) and is_array($recipe['newfields'])) {
 906              $this->set_new_fields($recipe['newfields']);
 907          }
 908      }
 909  
 910      /**
 911       * Validates and sets the given processing object
 912       *
 913       * @param object $pobject processing object, must provide a method to be called
 914       */
 915      public function set_processing_object($pobject) {
 916          $this->validate_pobject($pobject);
 917          $this->pobject = $pobject;
 918      }
 919  
 920      /**
 921       * Sets the name of the processing method
 922       *
 923       * @param string $pmethod
 924       */
 925      public function set_processing_method($pmethod) {
 926          $this->pmethod = $pmethod;
 927      }
 928  
 929      /**
 930       * Sets the name of the path start event listener
 931       *
 932       * @param string $smethod
 933       */
 934      public function set_start_method($smethod) {
 935          $this->smethod = $smethod;
 936      }
 937  
 938      /**
 939       * Sets the name of the path end event listener
 940       *
 941       * @param string $emethod
 942       */
 943      public function set_end_method($emethod) {
 944          $this->emethod = $emethod;
 945      }
 946  
 947      /**
 948       * Sets the element tags
 949       *
 950       * @param array $tags
 951       */
 952      public function set_tags($tags) {
 953          $this->tags = $tags;
 954      }
 955  
 956      /**
 957       * Sets the list of deprecated fields to drop
 958       *
 959       * @param array $fields
 960       */
 961      public function set_dropped_fields(array $fields) {
 962          $this->dropfields = $fields;
 963      }
 964  
 965      /**
 966       * Sets the required new names of the current fields
 967       *
 968       * @param array $fields (string)$currentname => (string)$newname
 969       */
 970      public function set_renamed_fields(array $fields) {
 971          $this->renamefields = $fields;
 972      }
 973  
 974      /**
 975       * Sets the new fields and their values
 976       *
 977       * @param array $fields (string)$field => (mixed)value
 978       */
 979      public function set_new_fields(array $fields) {
 980          $this->newfields = $fields;
 981      }
 982  
 983      /**
 984       * Cooks the parsed tags data by applying known recipes
 985       *
 986       * Recipes are used for common trivial operations like adding new fields
 987       * or renaming fields. The handler's processing method receives cooked
 988       * data.
 989       *
 990       * @param array $data the contents of the element
 991       * @return array
 992       */
 993      public function apply_recipes(array $data) {
 994  
 995          $cooked = array();
 996  
 997          foreach ($data as $name => $value) {
 998              // lower case rocks!
 999              $name = strtolower($name);
1000  
1001              if (is_array($value)) {
1002                  if ($this->is_grouped()) {
1003                      $value = $this->apply_recipes($value);
1004                  } else {
1005                      throw new convert_path_exception('non_grouped_path_with_array_values');
1006                  }
1007              }
1008  
1009              // drop legacy fields
1010              if (in_array($name, $this->dropfields)) {
1011                  continue;
1012              }
1013  
1014              // fields renaming
1015              if (array_key_exists($name, $this->renamefields)) {
1016                  $name = $this->renamefields[$name];
1017              }
1018  
1019              $cooked[$name] = $value;
1020          }
1021  
1022          // adding new fields
1023          foreach ($this->newfields as $name => $value) {
1024              $cooked[$name] = $value;
1025          }
1026  
1027          return $cooked;
1028      }
1029  
1030      /**
1031       * @return string the element given name
1032       */
1033      public function get_name() {
1034          return $this->name;
1035      }
1036  
1037      /**
1038       * @return string the path to the element
1039       */
1040      public function get_path() {
1041          return $this->path;
1042      }
1043  
1044      /**
1045       * @return bool flag to define if this element will get child ones grouped or no
1046       */
1047      public function is_grouped() {
1048          return $this->grouped;
1049      }
1050  
1051      /**
1052       * @return object the processing object providing the processing method
1053       */
1054      public function get_processing_object() {
1055          return $this->pobject;
1056      }
1057  
1058      /**
1059       * @return string the name of the method to call to process the element
1060       */
1061      public function get_processing_method() {
1062          return $this->pmethod;
1063      }
1064  
1065      /**
1066       * @return string the name of the path start event listener
1067       */
1068      public function get_start_method() {
1069          return $this->smethod;
1070      }
1071  
1072      /**
1073       * @return string the name of the path end event listener
1074       */
1075      public function get_end_method() {
1076          return $this->emethod;
1077      }
1078  
1079      /**
1080       * @return mixed the element data
1081       */
1082      public function get_tags() {
1083          return $this->tags;
1084      }
1085  
1086  
1087      /// end of public API //////////////////////////////////////////////////////
1088  
1089      /**
1090       * Makes sure the given name is a valid element name
1091       *
1092       * Note it may look as if we used exceptions for code flow control here. That's not the case
1093       * as we actually validate the code, not the user data. And the code is supposed to be
1094       * correct.
1095       *
1096       * @param string @name the element given name
1097       * @throws convert_path_exception
1098       * @return void
1099       */
1100      protected function validate_name($name) {
1101          // Validate various name constraints, throwing exception if needed
1102          if (empty($name)) {
1103              throw new convert_path_exception('convert_path_emptyname', $name);
1104          }
1105          if (preg_replace('/\s/', '', $name) != $name) {
1106              throw new convert_path_exception('convert_path_whitespace', $name);
1107          }
1108          if (preg_replace('/[^\x30-\x39\x41-\x5a\x5f\x61-\x7a]/', '', $name) != $name) {
1109              throw new convert_path_exception('convert_path_notasciiname', $name);
1110          }
1111      }
1112  
1113      /**
1114       * Makes sure that the given object is a valid processing object
1115       *
1116       * The processing object must be an object providing at least element's processing method
1117       * or path-reached-end event listener or path-reached-start listener method.
1118       *
1119       * Note it may look as if we used exceptions for code flow control here. That's not the case
1120       * as we actually validate the code, not the user data. And the code is supposed to be
1121       * correct.
1122        *
1123       * @param object $pobject
1124       * @throws convert_path_exception
1125       * @return void
1126       */
1127      protected function validate_pobject($pobject) {
1128          if (!is_object($pobject)) {
1129              throw new convert_path_exception('convert_path_no_object', get_class($pobject));
1130          }
1131          if (!method_exists($pobject, $this->get_processing_method()) and
1132              !method_exists($pobject, $this->get_end_method()) and
1133              !method_exists($pobject, $this->get_start_method())) {
1134              throw new convert_path_exception('convert_path_missing_method', get_class($pobject));
1135          }
1136      }
1137  }
1138  
1139  
1140  /**
1141   * Exception being thrown by {@link convert_path} methods
1142   */
1143  class convert_path_exception extends moodle_exception {
1144  
1145      /**
1146       * Constructor
1147       *
1148       * @param string $errorcode key for the corresponding error string
1149       * @param mixed $a extra words and phrases that might be required by the error string
1150       * @param string $debuginfo optional debugging information
1151       */
1152      public function __construct($errorcode, $a = null, $debuginfo = null) {
1153          parent::__construct($errorcode, '', '', $a, $debuginfo);
1154      }
1155  }
1156  
1157  
1158  /**
1159   * The class responsible for files migration
1160   *
1161   * The files in Moodle 1.9 backup are stored in moddata, user_files, group_files,
1162   * course_files and site_files folders.
1163   */
1164  class moodle1_file_manager implements loggable {
1165  
1166      /** @var moodle1_converter instance we serve to */
1167      public $converter;
1168  
1169      /** @var int context id of the files being migrated */
1170      public $contextid;
1171  
1172      /** @var string component name of the files being migrated */
1173      public $component;
1174  
1175      /** @var string file area of the files being migrated */
1176      public $filearea;
1177  
1178      /** @var int item id of the files being migrated */
1179      public $itemid = 0;
1180  
1181      /** @var int user id */
1182      public $userid;
1183  
1184      /** @var string the root of the converter temp directory */
1185      protected $basepath;
1186  
1187      /** @var array of file ids that were migrated by this instance */
1188      protected $fileids = array();
1189  
1190      /**
1191       * Constructor optionally accepting some default values for the migrated files
1192       *
1193       * @param moodle1_converter $converter the converter instance we serve to
1194       * @param int $contextid initial context id of the files being migrated
1195       * @param string $component initial component name of the files being migrated
1196       * @param string $filearea initial file area of the files being migrated
1197       * @param int $itemid initial item id of the files being migrated
1198       * @param int $userid initial user id of the files being migrated
1199       */
1200      public function __construct(moodle1_converter $converter, $contextid = null, $component = null, $filearea = null, $itemid = 0, $userid = null) {
1201          // set the initial destination of the migrated files
1202          $this->converter = $converter;
1203          $this->contextid = $contextid;
1204          $this->component = $component;
1205          $this->filearea  = $filearea;
1206          $this->itemid    = $itemid;
1207          $this->userid    = $userid;
1208          // set other useful bits
1209          $this->basepath  = $converter->get_tempdir_path();
1210      }
1211  
1212      /**
1213       * Migrates one given file stored on disk
1214       *
1215       * @param string $sourcepath the path to the source local file within the backup archive {@example 'moddata/foobar/file.ext'}
1216       * @param string $filepath the file path of the migrated file, defaults to the root directory '/' {@example '/sub/dir/'}
1217       * @param string $filename the name of the migrated file, defaults to the same as the source file has
1218       * @param int $sortorder the sortorder of the file (main files have sortorder set to 1)
1219       * @param int $timecreated override the timestamp of when the migrated file should appear as created
1220       * @param int $timemodified override the timestamp of when the migrated file should appear as modified
1221       * @return int id of the migrated file
1222       */
1223      public function migrate_file($sourcepath, $filepath = '/', $filename = null, $sortorder = 0, $timecreated = null, $timemodified = null) {
1224  
1225          // Normalise Windows paths a bit.
1226          $sourcepath = str_replace('\\', '/', $sourcepath);
1227  
1228          // PARAM_PATH must not be used on full OS path!
1229          if ($sourcepath !== clean_param($sourcepath, PARAM_PATH)) {
1230              throw new moodle1_convert_exception('file_invalid_path', $sourcepath);
1231          }
1232  
1233          $sourcefullpath = $this->basepath.'/'.$sourcepath;
1234  
1235          if (!is_readable($sourcefullpath)) {
1236              throw new moodle1_convert_exception('file_not_readable', $sourcefullpath);
1237          }
1238  
1239          // sanitize filepath
1240          if (empty($filepath)) {
1241              $filepath = '/';
1242          }
1243          if (substr($filepath, -1) !== '/') {
1244              $filepath .= '/';
1245          }
1246          $filepath = clean_param($filepath, PARAM_PATH);
1247  
1248          if (core_text::strlen($filepath) > 255) {
1249              throw new moodle1_convert_exception('file_path_longer_than_255_chars');
1250          }
1251  
1252          if (is_null($filename)) {
1253              $filename = basename($sourcefullpath);
1254          }
1255  
1256          $filename = clean_param($filename, PARAM_FILE);
1257  
1258          if ($filename === '') {
1259              throw new moodle1_convert_exception('unsupported_chars_in_filename');
1260          }
1261  
1262          if (is_null($timecreated)) {
1263              $timecreated = filectime($sourcefullpath);
1264          }
1265  
1266          if (is_null($timemodified)) {
1267              $timemodified = filemtime($sourcefullpath);
1268          }
1269  
1270          $filerecord = $this->make_file_record(array(
1271              'filepath'      => $filepath,
1272              'filename'      => $filename,
1273              'sortorder'     => $sortorder,
1274              'mimetype'      => mimeinfo('type', $sourcefullpath),
1275              'timecreated'   => $timecreated,
1276              'timemodified'  => $timemodified,
1277          ));
1278  
1279          list($filerecord['contenthash'], $filerecord['filesize'], $newfile) = $this->add_file_to_pool($sourcefullpath);
1280          $this->stash_file($filerecord);
1281  
1282          return $filerecord['id'];
1283      }
1284  
1285      /**
1286       * Migrates all files in the given directory
1287       *
1288       * @param string $rootpath path within the backup archive to the root directory containing the files {@example 'course_files'}
1289       * @param string $relpath relative path used during the recursion - do not provide when calling this!
1290       * @return array ids of the migrated files, empty array if the $rootpath not found
1291       */
1292      public function migrate_directory($rootpath, $relpath='/') {
1293  
1294          // Check the trailing slash in the $rootpath
1295          if (substr($rootpath, -1) === '/') {
1296              debugging('moodle1_file_manager::migrate_directory() expects $rootpath without the trailing slash', DEBUG_DEVELOPER);
1297              $rootpath = substr($rootpath, 0, strlen($rootpath) - 1);
1298          }
1299  
1300          if (!file_exists($this->basepath.'/'.$rootpath.$relpath)) {
1301              return array();
1302          }
1303  
1304          $fileids = array();
1305  
1306          // make the fake file record for the directory itself
1307          $filerecord = $this->make_file_record(array('filepath' => $relpath, 'filename' => '.'));
1308          $this->stash_file($filerecord);
1309          $fileids[] = $filerecord['id'];
1310  
1311          $items = new DirectoryIterator($this->basepath.'/'.$rootpath.$relpath);
1312  
1313          foreach ($items as $item) {
1314  
1315              if ($item->isDot()) {
1316                  continue;
1317              }
1318  
1319              if ($item->isLink()) {
1320                  throw new moodle1_convert_exception('unexpected_symlink');
1321              }
1322  
1323              if ($item->isFile()) {
1324                  $fileids[] = $this->migrate_file(substr($item->getPathname(), strlen($this->basepath.'/')),
1325                      $relpath, $item->getFilename(), 0, $item->getCTime(), $item->getMTime());
1326  
1327              } else {
1328                  $dirname = clean_param($item->getFilename(), PARAM_PATH);
1329  
1330                  if ($dirname === '') {
1331                      throw new moodle1_convert_exception('unsupported_chars_in_filename');
1332                  }
1333  
1334                  // migrate subdirectories recursively
1335                  $fileids = array_merge($fileids, $this->migrate_directory($rootpath, $relpath.$item->getFilename().'/'));
1336              }
1337          }
1338  
1339          return $fileids;
1340      }
1341  
1342      /**
1343       * Returns the list of all file ids migrated by this instance so far
1344       *
1345       * @return array of int
1346       */
1347      public function get_fileids() {
1348          return $this->fileids;
1349      }
1350  
1351      /**
1352       * Explicitly clear the list of file ids migrated by this instance so far
1353       */
1354      public function reset_fileids() {
1355          $this->fileids = array();
1356      }
1357  
1358      /**
1359       * Log a message using the converter's logging mechanism
1360       *
1361       * @param string $message message text
1362       * @param int $level message level {@example backup::LOG_WARNING}
1363       * @param null|mixed $a additional information
1364       * @param null|int $depth the message depth
1365       * @param bool $display whether the message should be sent to the output, too
1366       */
1367      public function log($message, $level, $a = null, $depth = null, $display = false) {
1368          $this->converter->log($message, $level, $a, $depth, $display);
1369      }
1370  
1371      /// internal implementation details ////////////////////////////////////////
1372  
1373      /**
1374       * Prepares a fake record from the files table
1375       *
1376       * @param array $fileinfo explicit file data
1377       * @return array
1378       */
1379      protected function make_file_record(array $fileinfo) {
1380  
1381          $defaultrecord = array(
1382              'contenthash'   => 'da39a3ee5e6b4b0d3255bfef95601890afd80709',  // sha1 of an empty file
1383              'contextid'     => $this->contextid,
1384              'component'     => $this->component,
1385              'filearea'      => $this->filearea,
1386              'itemid'        => $this->itemid,
1387              'filepath'      => null,
1388              'filename'      => null,
1389              'filesize'      => 0,
1390              'userid'        => $this->userid,
1391              'mimetype'      => null,
1392              'status'        => 0,
1393              'timecreated'   => $now = time(),
1394              'timemodified'  => $now,
1395              'source'        => null,
1396              'author'        => null,
1397              'license'       => null,
1398              'sortorder'     => 0,
1399          );
1400  
1401          if (!array_key_exists('id', $fileinfo)) {
1402              $defaultrecord['id'] = $this->converter->get_nextid();
1403          }
1404  
1405          // override the default values with the explicit data provided and return
1406          return array_merge($defaultrecord, $fileinfo);
1407      }
1408  
1409      /**
1410       * Copies the given file to the pool directory
1411       *
1412       * Returns an array containing SHA1 hash of the file contents, the file size
1413       * and a flag indicating whether the file was actually added to the pool or whether
1414       * it was already there.
1415       *
1416       * @param string $pathname the full path to the file
1417       * @return array with keys (string)contenthash, (int)filesize, (bool)newfile
1418       */
1419      protected function add_file_to_pool($pathname) {
1420  
1421          if (!is_readable($pathname)) {
1422              throw new moodle1_convert_exception('file_not_readable');
1423          }
1424  
1425          $contenthash = sha1_file($pathname);
1426          $filesize    = filesize($pathname);
1427          $hashpath    = $this->converter->get_workdir_path().'/files/'.substr($contenthash, 0, 2);
1428          $hashfile    = "$hashpath/$contenthash";
1429  
1430          if (file_exists($hashfile)) {
1431              if (filesize($hashfile) !== $filesize) {
1432                  // congratulations! you have found two files with different size and the same
1433                  // content hash. or, something were wrong (which is more likely)
1434                  throw new moodle1_convert_exception('same_hash_different_size');
1435              }
1436              $newfile = false;
1437  
1438          } else {
1439              check_dir_exists($hashpath);
1440              $newfile = true;
1441  
1442              if (!copy($pathname, $hashfile)) {
1443                  throw new moodle1_convert_exception('unable_to_copy_file');
1444              }
1445  
1446              if (filesize($hashfile) !== $filesize) {
1447                  throw new moodle1_convert_exception('filesize_different_after_copy');
1448              }
1449          }
1450  
1451          return array($contenthash, $filesize, $newfile);
1452      }
1453  
1454      /**
1455       * Stashes the file record into 'files' stash and adds the record id to list of migrated files
1456       *
1457       * @param array $filerecord
1458       */
1459      protected function stash_file(array $filerecord) {
1460          $this->converter->set_stash('files', $filerecord, $filerecord['id']);
1461          $this->fileids[] = $filerecord['id'];
1462      }
1463  }
1464  
1465  
1466  /**
1467   * Helper class that handles ids annotations for inforef.xml files
1468   */
1469  class moodle1_inforef_manager {
1470  
1471      /** @var string the name of the annotator we serve to (like course, section, activity, block) */
1472      protected $annotator = null;
1473  
1474      /** @var int the id of the annotator if it can have multiple instances */
1475      protected $annotatorid = null;
1476  
1477      /** @var array the actual storage of references, currently implemented as a in-memory structure */
1478      private $refs = array();
1479  
1480      /**
1481       * Creates new instance of the manager for the given annotator
1482       *
1483       * The identification of the annotator we serve to may be important in the future
1484       * when we move the actual storage of the references from memory to a persistent storage.
1485       *
1486       * @param moodle1_converter $converter
1487       * @param string $name the name of the annotator (like course, section, activity, block)
1488       * @param int $id the id of the annotator if required
1489       */
1490      public function __construct(moodle1_converter $converter, $name, $id = 0) {
1491          $this->annotator   = $name;
1492          $this->annotatorid = $id;
1493      }
1494  
1495      /**
1496       * Adds a reference
1497       *
1498       * @param string $item the name of referenced item (like user, file, scale, outcome or grade_item)
1499       * @param int $id the value of the reference
1500       */
1501      public function add_ref($item, $id) {
1502          $this->validate_item($item);
1503          $this->refs[$item][$id] = true;
1504      }
1505  
1506      /**
1507       * Adds a bulk of references
1508       *
1509       * @param string $item the name of referenced item (like user, file, scale, outcome or grade_item)
1510       * @param array $ids the list of referenced ids
1511       */
1512      public function add_refs($item, array $ids) {
1513          $this->validate_item($item);
1514          foreach ($ids as $id) {
1515              $this->refs[$item][$id] = true;
1516          }
1517      }
1518  
1519      /**
1520       * Writes the current references using a given opened xml writer
1521       *
1522       * @param xml_writer $xmlwriter
1523       */
1524      public function write_refs(xml_writer $xmlwriter) {
1525          $xmlwriter->begin_tag('inforef');
1526          foreach ($this->refs as $item => $ids) {
1527              $xmlwriter->begin_tag($item.'ref');
1528              foreach (array_keys($ids) as $id) {
1529                  $xmlwriter->full_tag($item, $id);
1530              }
1531              $xmlwriter->end_tag($item.'ref');
1532          }
1533          $xmlwriter->end_tag('inforef');
1534      }
1535  
1536      /**
1537       * Makes sure that the given name is a valid citizen of inforef.xml file
1538       *
1539       * @see backup_helper::get_inforef_itemnames()
1540       * @param string $item the name of reference (like user, file, scale, outcome or grade_item)
1541       * @throws coding_exception
1542       */
1543      protected function validate_item($item) {
1544  
1545          $allowed = array(
1546              'user'              => true,
1547              'grouping'          => true,
1548              'group'             => true,
1549              'role'              => true,
1550              'file'              => true,
1551              'scale'             => true,
1552              'outcome'           => true,
1553              'grade_item'        => true,
1554              'question_category' => true
1555          );
1556  
1557          if (!isset($allowed[$item])) {
1558              throw new coding_exception('Invalid inforef item type');
1559          }
1560      }
1561  }


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