Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/backup/converter/moodle1/lib.php
Go to the documentation of this file.
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/converter/convertlib.php');
00030 require_once($CFG->dirroot . '/backup/util/xml/parser/progressive_parser.class.php');
00031 require_once($CFG->dirroot . '/backup/util/xml/parser/processors/grouped_parser_processor.class.php');
00032 require_once($CFG->dirroot . '/backup/util/dbops/backup_dbops.class.php');
00033 require_once($CFG->dirroot . '/backup/util/dbops/backup_controller_dbops.class.php');
00034 require_once($CFG->dirroot . '/backup/util/dbops/restore_dbops.class.php');
00035 require_once($CFG->dirroot . '/backup/util/xml/contenttransformer/xml_contenttransformer.class.php');
00036 require_once(dirname(__FILE__) . '/handlerlib.php');
00037 
00041 class moodle1_converter extends base_converter {
00042 
00044     protected $xmlparser;
00045 
00047     protected $xmlprocessor;
00048 
00050     protected $pathelements = array();
00051 
00053     protected $currentmod = null;
00054 
00056     protected $currentblock = null;
00057 
00059     protected $pathlock;
00060 
00062     private $nextid = 1;
00063 
00067     const SKIP_ALL_CHILDREN = -991399;
00068 
00079     public function log($message, $level, $a = null, $depth = null, $display = false) {
00080         parent::log('(moodle1) '.$message, $level, $a, $depth, $display);
00081     }
00082 
00089     public static function detect_format($tempdir) {
00090         global $CFG;
00091 
00092         $filepath = $CFG->tempdir . '/backup/' . $tempdir . '/moodle.xml';
00093         if (file_exists($filepath)) {
00094             // looks promising, lets load some information
00095             $handle = fopen($filepath, 'r');
00096             $first_chars = fread($handle, 200);
00097             fclose($handle);
00098 
00099             // check if it has the required strings
00100             if (strpos($first_chars,'<?xml version="1.0" encoding="UTF-8"?>') !== false and
00101                 strpos($first_chars,'<MOODLE_BACKUP>') !== false and
00102                 strpos($first_chars,'<INFO>') !== false) {
00103 
00104                 return backup::FORMAT_MOODLE1;
00105             }
00106         }
00107 
00108         return null;
00109     }
00110 
00116     protected function init() {
00117 
00118         // ask your mother first before going out playing with toys
00119         parent::init();
00120 
00121         $this->log('initializing '.$this->get_name().' converter', backup::LOG_INFO);
00122 
00123         // good boy, prepare XML parser and processor
00124         $this->log('setting xml parser', backup::LOG_DEBUG, null, 1);
00125         $this->xmlparser = new progressive_parser();
00126         $this->xmlparser->set_file($this->get_tempdir_path() . '/moodle.xml');
00127         $this->log('setting xml processor', backup::LOG_DEBUG, null, 1);
00128         $this->xmlprocessor = new moodle1_parser_processor($this);
00129         $this->xmlparser->set_processor($this->xmlprocessor);
00130 
00131         // make sure that MOD and BLOCK paths are visited
00132         $this->xmlprocessor->add_path('/MOODLE_BACKUP/COURSE/MODULES/MOD');
00133         $this->xmlprocessor->add_path('/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK');
00134 
00135         // register the conversion handlers
00136         foreach (moodle1_handlers_factory::get_handlers($this) as $handler) {
00137             $this->log('registering handler', backup::LOG_DEBUG, get_class($handler), 1);
00138             $this->register_handler($handler, $handler->get_paths());
00139         }
00140     }
00141 
00145     protected function execute() {
00146         $this->log('creating the stash storage', backup::LOG_DEBUG);
00147         $this->create_stash_storage();
00148 
00149         $this->log('parsing moodle.xml starts', backup::LOG_DEBUG);
00150         $this->xmlparser->process();
00151         $this->log('parsing moodle.xml done', backup::LOG_DEBUG);
00152 
00153         $this->log('dropping the stash storage', backup::LOG_DEBUG);
00154         $this->drop_stash_storage();
00155     }
00156 
00160     protected function register_handler(moodle1_handler $handler, array $elements) {
00161 
00162         // first iteration, push them to new array, indexed by name
00163         // to detect duplicates in names or paths
00164         $names = array();
00165         $paths = array();
00166         foreach($elements as $element) {
00167             if (!$element instanceof convert_path) {
00168                 throw new convert_exception('path_element_wrong_class', get_class($element));
00169             }
00170             if (array_key_exists($element->get_name(), $names)) {
00171                 throw new convert_exception('path_element_name_alreadyexists', $element->get_name());
00172             }
00173             if (array_key_exists($element->get_path(), $paths)) {
00174                 throw new convert_exception('path_element_path_alreadyexists', $element->get_path());
00175             }
00176             $names[$element->get_name()] = true;
00177             $paths[$element->get_path()] = $element;
00178         }
00179 
00180         // now, for each element not having a processing object yet, assign the handler
00181         // if the element is not a memeber of a group
00182         foreach($paths as $key => $element) {
00183             if (is_null($element->get_processing_object()) and !$this->grouped_parent_exists($element, $paths)) {
00184                 $paths[$key]->set_processing_object($handler);
00185             }
00186             // add the element path to the processor
00187             $this->xmlprocessor->add_path($element->get_path(), $element->is_grouped());
00188         }
00189 
00190         // done, store the paths (duplicates by path are discarded)
00191         $this->pathelements = array_merge($this->pathelements, $paths);
00192 
00193         // remove the injected plugin name element from the MOD and BLOCK paths
00194         // and register such collapsed path, too
00195         foreach ($elements as $element) {
00196             $path = $element->get_path();
00197             $path = preg_replace('/^\/MOODLE_BACKUP\/COURSE\/MODULES\/MOD\/(\w+)\//', '/MOODLE_BACKUP/COURSE/MODULES/MOD/', $path);
00198             $path = preg_replace('/^\/MOODLE_BACKUP\/COURSE\/BLOCKS\/BLOCK\/(\w+)\//', '/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK/', $path);
00199             if (!empty($path) and $path != $element->get_path()) {
00200                 $this->xmlprocessor->add_path($path, false);
00201             }
00202         }
00203     }
00204 
00212     protected function grouped_parent_exists($pelement, $elements) {
00213 
00214         foreach ($elements as $element) {
00215             if ($pelement->get_path() == $element->get_path()) {
00216                 // don't compare against itself
00217                 continue;
00218             }
00219             // if the element is grouped and it is a parent of pelement, return true
00220             if ($element->is_grouped() and strpos($pelement->get_path() .  '/', $element->get_path()) === 0) {
00221                 return true;
00222             }
00223         }
00224 
00225         // no grouped parent found
00226         return false;
00227     }
00228 
00238     public function process_chunk($data) {
00239 
00240         $path = $data['path'];
00241 
00242         // expand the MOD paths so that they contain the module name
00243         if ($path === '/MOODLE_BACKUP/COURSE/MODULES/MOD') {
00244             $this->currentmod = strtoupper($data['tags']['MODTYPE']);
00245             $path = '/MOODLE_BACKUP/COURSE/MODULES/MOD/' . $this->currentmod;
00246 
00247         } else if (strpos($path, '/MOODLE_BACKUP/COURSE/MODULES/MOD') === 0) {
00248             $path = str_replace('/MOODLE_BACKUP/COURSE/MODULES/MOD', '/MOODLE_BACKUP/COURSE/MODULES/MOD/' . $this->currentmod, $path);
00249         }
00250 
00251         // expand the BLOCK paths so that they contain the module name
00252         if ($path === '/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK') {
00253             $this->currentblock = strtoupper($data['tags']['NAME']);
00254             $path = '/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK/' . $this->currentblock;
00255 
00256         } else if (strpos($path, '/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK') === 0) {
00257             $path = str_replace('/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK', '/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK/' . $this->currentmod, $path);
00258         }
00259 
00260         if ($path !== $data['path']) {
00261             if (!array_key_exists($path, $this->pathelements)) {
00262                 // no handler registered for the transformed MOD or BLOCK path
00263                 $this->log('no handler attached', backup::LOG_WARNING, $path);
00264                 return;
00265 
00266             } else {
00267                 // pretend as if the original $data contained the tranformed path
00268                 $data['path'] = $path;
00269             }
00270         }
00271 
00272         if (!array_key_exists($data['path'], $this->pathelements)) {
00273             // path added to the processor without the handler
00274             throw new convert_exception('missing_path_handler', $data['path']);
00275         }
00276 
00277         $element  = $this->pathelements[$data['path']];
00278         $object   = $element->get_processing_object();
00279         $method   = $element->get_processing_method();
00280         $returned = null; // data returned by the processing method, if any
00281 
00282         if (empty($object)) {
00283             throw new convert_exception('missing_processing_object', null, $data['path']);
00284         }
00285 
00286         // release the lock if we aren't anymore within children of it
00287         if (!is_null($this->pathlock) and strpos($data['path'], $this->pathlock) === false) {
00288             $this->pathlock = null;
00289         }
00290 
00291         // if the path is not locked, apply the element's recipes and dispatch
00292         // the cooked tags to the processing method
00293         if (is_null($this->pathlock)) {
00294             $rawdatatags  = $data['tags'];
00295             $data['tags'] = $element->apply_recipes($data['tags']);
00296 
00297             // if the processing method exists, give it a chance to modify data
00298             if (method_exists($object, $method)) {
00299                 $returned = $object->$method($data['tags'], $rawdatatags);
00300             }
00301         }
00302 
00303         // if the dispatched method returned SKIP_ALL_CHILDREN, remember the current path
00304         // and lock it so that its children are not dispatched
00305         if ($returned === self::SKIP_ALL_CHILDREN) {
00306             // check we haven't any previous lock
00307             if (!is_null($this->pathlock)) {
00308                 throw new convert_exception('already_locked_path', $data['path']);
00309             }
00310             // set the lock - nothing below the current path will be dispatched
00311             $this->pathlock = $data['path'] . '/';
00312 
00313         // if the method has returned any info, set element data to it
00314         } else if (!is_null($returned)) {
00315             $element->set_tags($returned);
00316 
00317         // use just the cooked parsed data otherwise
00318         } else {
00319             $element->set_tags($data['tags']);
00320         }
00321     }
00322 
00341     public function path_start_reached($path) {
00342 
00343         if ($path === '/MOODLE_BACKUP/COURSE/MODULES/MOD') {
00344             $this->currentmod = null;
00345             $forbidden = true;
00346 
00347         } else if (strpos($path, '/MOODLE_BACKUP/COURSE/MODULES/MOD') === 0) {
00348             // expand the MOD paths so that they contain the module name
00349             $path = str_replace('/MOODLE_BACKUP/COURSE/MODULES/MOD', '/MOODLE_BACKUP/COURSE/MODULES/MOD/' . $this->currentmod, $path);
00350         }
00351 
00352         if ($path === '/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK') {
00353             $this->currentmod = null;
00354             $forbidden = true;
00355 
00356         } else if (strpos($path, '/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK') === 0) {
00357             // expand the BLOCK paths so that they contain the module name
00358             $path = str_replace('/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK', '/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK/' . $this->currentmod, $path);
00359         }
00360 
00361         if (empty($this->pathelements[$path])) {
00362             return;
00363         }
00364 
00365         $element = $this->pathelements[$path];
00366         $pobject = $element->get_processing_object();
00367         $method  = $element->get_start_method();
00368 
00369         if (method_exists($pobject, $method)) {
00370             if (empty($forbidden)) {
00371                 $pobject->$method();
00372 
00373             } else {
00374                 // this path is not supported because we do not know the module/block yet
00375                 throw new coding_exception('Attaching the on-start event listener to the root MOD or BLOCK element is forbidden.');
00376             }
00377         }
00378     }
00379 
00385     public function path_end_reached($path) {
00386 
00387         // expand the MOD paths so that they contain the current module name
00388         if ($path === '/MOODLE_BACKUP/COURSE/MODULES/MOD') {
00389             $path = '/MOODLE_BACKUP/COURSE/MODULES/MOD/' . $this->currentmod;
00390 
00391         } else if (strpos($path, '/MOODLE_BACKUP/COURSE/MODULES/MOD') === 0) {
00392             $path = str_replace('/MOODLE_BACKUP/COURSE/MODULES/MOD', '/MOODLE_BACKUP/COURSE/MODULES/MOD/' . $this->currentmod, $path);
00393         }
00394 
00395         // expand the BLOCK paths so that they contain the module name
00396         if ($path === '/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK') {
00397             $path = '/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK/' . $this->currentblock;
00398 
00399         } else if (strpos($path, '/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK') === 0) {
00400             $path = str_replace('/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK', '/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK/' . $this->currentmod, $path);
00401         }
00402 
00403         if (empty($this->pathelements[$path])) {
00404             return;
00405         }
00406 
00407         $element = $this->pathelements[$path];
00408         $pobject = $element->get_processing_object();
00409         $method  = $element->get_end_method();
00410         $tags    = $element->get_tags();
00411 
00412         if (method_exists($pobject, $method)) {
00413             $pobject->$method($tags);
00414         }
00415     }
00416 
00422     public function create_stash_storage() {
00423         backup_controller_dbops::create_backup_ids_temp_table($this->get_id());
00424     }
00425 
00431     public function drop_stash_storage() {
00432         backup_controller_dbops::drop_backup_ids_temp_table($this->get_id());
00433     }
00434 
00445     public function set_stash($stashname, $info, $itemid = 0) {
00446         try {
00447             restore_dbops::set_backup_ids_record($this->get_id(), $stashname, $itemid, 0, null, $info);
00448 
00449         } catch (dml_exception $e) {
00450             throw new moodle1_convert_storage_exception('unable_to_restore_stash', null, $e->getMessage());
00451         }
00452     }
00453 
00462     public function get_stash($stashname, $itemid = 0) {
00463 
00464         $record = restore_dbops::get_backup_ids_record($this->get_id(), $stashname, $itemid);
00465 
00466         if (empty($record)) {
00467             throw new moodle1_convert_empty_storage_exception('required_not_stashed_data', array($stashname, $itemid));
00468         } else {
00469             return $record->info;
00470         }
00471     }
00472 
00481     public function get_stash_or_default($stashname, $itemid = 0, $default = null) {
00482         try {
00483             return $this->get_stash($stashname, $itemid);
00484         } catch (moodle1_convert_empty_storage_exception $e) {
00485             return $default;
00486         }
00487     }
00488 
00494     public function get_stash_names() {
00495         global $DB;
00496 
00497         $search = array(
00498             'backupid' => $this->get_id(),
00499         );
00500 
00501         return array_keys($DB->get_records('backup_ids_temp', $search, '', 'itemname'));
00502     }
00503 
00510     public function get_stash_itemids($stashname) {
00511         global $DB;
00512 
00513         $search = array(
00514             'backupid' => $this->get_id(),
00515             'itemname' => $stashname
00516         );
00517 
00518         return array_keys($DB->get_records('backup_ids_temp', $search, '', 'itemid'));
00519     }
00520 
00536     public function get_contextid($level, $instance = 0) {
00537 
00538         $stashname = 'context' . $level;
00539 
00540         if ($level == CONTEXT_SYSTEM or $level == CONTEXT_COURSE) {
00541             $instance = 0;
00542         }
00543 
00544         try {
00545             // try the previously stashed id
00546             return $this->get_stash($stashname, $instance);
00547 
00548         } catch (moodle1_convert_empty_storage_exception $e) {
00549             // this context level + instance is required for the first time
00550             $newid = $this->get_nextid();
00551             $this->set_stash($stashname, $newid, $instance);
00552             return $newid;
00553         }
00554     }
00555 
00561     public function get_nextid() {
00562         return $this->nextid++;
00563     }
00564 
00575     public function get_file_manager($contextid = null, $component = null, $filearea = null, $itemid = 0, $userid = null) {
00576         return new moodle1_file_manager($this, $contextid, $component, $filearea, $itemid, $userid);
00577     }
00578 
00586     public function get_inforef_manager($name, $id = 0) {
00587         return new moodle1_inforef_manager($this, $name, $id);
00588     }
00589 
00590 
00600     public static function migrate_referenced_files($text, moodle1_file_manager $fileman) {
00601 
00602         $files = self::find_referenced_files($text);
00603         if (!empty($files)) {
00604             foreach ($files as $file) {
00605                 try {
00606                     $fileman->migrate_file('course_files'.$file, dirname($file));
00607                 } catch (moodle1_convert_exception $e) {
00608                     // file probably does not exist
00609                     $fileman->log('error migrating file', backup::LOG_WARNING, 'course_files'.$file);
00610                 }
00611             }
00612             $text = self::rewrite_filephp_usage($text, $files);
00613         }
00614 
00615         return $text;
00616     }
00617 
00625     public static function find_referenced_files($text) {
00626 
00627         $files = array();
00628 
00629         if (empty($text) or is_numeric($text)) {
00630             return $files;
00631         }
00632 
00633         $matches = array();
00634         $pattern = '|(["\'])(\$@FILEPHP@\$.+?)\1|';
00635         $result = preg_match_all($pattern, $text, $matches);
00636         if ($result === false) {
00637             throw new moodle1_convert_exception('error_while_searching_for_referenced_files');
00638         }
00639         if ($result == 0) {
00640             return $files;
00641         }
00642         foreach ($matches[2] as $match) {
00643             $files[] = str_replace(array('$@FILEPHP@$', '$@SLASH@$', '$@FORCEDOWNLOAD@$'), array('', '/', ''), $match);
00644         }
00645 
00646         return array_unique($files);
00647     }
00648 
00657     public static function rewrite_filephp_usage($text, array $files) {
00658 
00659         foreach ($files as $file) {
00660             $fileref = '$@FILEPHP@$'.str_replace('/', '$@SLASH@$', $file);
00661             $text    = str_replace($fileref.'$@FORCEDOWNLOAD@$', '@@PLUGINFILE@@'.$file.'?forcedownload=1', $text);
00662             $text    = str_replace($fileref, '@@PLUGINFILE@@'.$file, $text);
00663         }
00664 
00665         return $text;
00666     }
00667 
00671     public static function description() {
00672 
00673         return array(
00674             'from'  => backup::FORMAT_MOODLE1,
00675             'to'    => backup::FORMAT_MOODLE,
00676             'cost'  => 10,
00677         );
00678     }
00679 }
00680 
00681 
00685 class moodle1_convert_exception extends convert_exception {
00686 }
00687 
00688 
00692 class moodle1_convert_storage_exception extends moodle1_convert_exception {
00693 }
00694 
00695 
00699 class moodle1_convert_empty_storage_exception extends moodle1_convert_exception {
00700 }
00701 
00702 
00706 class moodle1_parser_processor extends grouped_parser_processor {
00707 
00709     protected $converter;
00710 
00711     public function __construct(moodle1_converter $converter) {
00712         $this->converter = $converter;
00713         parent::__construct();
00714     }
00715 
00722     public function process_cdata($cdata) {
00723 
00724         if ($cdata === '$@NULL@$') {
00725             return null;
00726         }
00727 
00728         return $cdata;
00729     }
00730 
00736     protected function dispatch_chunk($data) {
00737         $this->converter->process_chunk($data);
00738     }
00739 
00745     protected function notify_path_start($path) {
00746         $this->converter->path_start_reached($path);
00747     }
00748 
00754     protected function notify_path_end($path) {
00755         $this->converter->path_end_reached($path);
00756     }
00757 }
00758 
00759 
00765 class moodle1_xml_transformer extends xml_contenttransformer {
00766 
00772     public function process($content) {
00773 
00774         // the content should be a string. If array or object is given, try our best recursively
00775         // but inform the developer
00776         if (is_array($content)) {
00777             debugging('Moodle1 XML transformer should not process arrays but plain content always', DEBUG_DEVELOPER);
00778             foreach($content as $key => $plaincontent) {
00779                 $content[$key] = $this->process($plaincontent);
00780             }
00781             return $content;
00782 
00783         } else if (is_object($content)) {
00784             debugging('Moodle1 XML transformer should not process objects but plain content always', DEBUG_DEVELOPER);
00785             foreach((array)$content as $key => $plaincontent) {
00786                 $content[$key] = $this->process($plaincontent);
00787             }
00788             return (object)$content;
00789         }
00790 
00791         // try to deal with some trivial cases first
00792         if (is_null($content)) {
00793             return '$@NULL@$';
00794 
00795         } else if ($content === '') {
00796             return '';
00797 
00798         } else if (is_numeric($content)) {
00799             return $content;
00800 
00801         } else if (strlen($content) < 32) {
00802             return $content;
00803         }
00804 
00805         return $content;
00806     }
00807 }
00808 
00809 
00816 class convert_path {
00817 
00819     protected $name;
00820 
00822     protected $path;
00823 
00825     protected $grouped;
00826 
00828     protected $pobject = null;
00829 
00831     protected $pmethod = null;
00832 
00834     protected $smethod = null;
00835 
00837     protected $emethod = null;
00838 
00840     protected $tags = null;
00841 
00843     protected $dropfields = array();
00844 
00846     protected $renamefields = array();
00847 
00849     protected $newfields = array();
00850 
00859     public function __construct($name, $path, array $recipe = array(), $grouped = false) {
00860 
00861         $this->validate_name($name);
00862 
00863         $this->name     = $name;
00864         $this->path     = $path;
00865         $this->grouped  = $grouped;
00866 
00867         // set the default method names
00868         $this->set_processing_method('process_' . $name);
00869         $this->set_start_method('on_'.$name.'_start');
00870         $this->set_end_method('on_'.$name.'_end');
00871 
00872         if ($grouped and !empty($recipe)) {
00873             throw new convert_path_exception('recipes_not_supported_for_grouped_elements');
00874         }
00875 
00876         if (isset($recipe['dropfields']) and is_array($recipe['dropfields'])) {
00877             $this->set_dropped_fields($recipe['dropfields']);
00878         }
00879         if (isset($recipe['renamefields']) and is_array($recipe['renamefields'])) {
00880             $this->set_renamed_fields($recipe['renamefields']);
00881         }
00882         if (isset($recipe['newfields']) and is_array($recipe['newfields'])) {
00883             $this->set_new_fields($recipe['newfields']);
00884         }
00885     }
00886 
00892     public function set_processing_object($pobject) {
00893         $this->validate_pobject($pobject);
00894         $this->pobject = $pobject;
00895     }
00896 
00902     public function set_processing_method($pmethod) {
00903         $this->pmethod = $pmethod;
00904     }
00905 
00911     public function set_start_method($smethod) {
00912         $this->smethod = $smethod;
00913     }
00914 
00920     public function set_end_method($emethod) {
00921         $this->emethod = $emethod;
00922     }
00923 
00929     public function set_tags($tags) {
00930         $this->tags = $tags;
00931     }
00932 
00938     public function set_dropped_fields(array $fields) {
00939         $this->dropfields = $fields;
00940     }
00941 
00947     public function set_renamed_fields(array $fields) {
00948         $this->renamefields = $fields;
00949     }
00950 
00956     public function set_new_fields(array $fields) {
00957         $this->newfields = $fields;
00958     }
00959 
00970     public function apply_recipes(array $data) {
00971 
00972         $cooked = array();
00973 
00974         foreach ($data as $name => $value) {
00975             // lower case rocks!
00976             $name = strtolower($name);
00977 
00978             if (is_array($value)) {
00979                 if ($this->is_grouped()) {
00980                     $value = $this->apply_recipes($value);
00981                 } else {
00982                     throw new convert_path_exception('non_grouped_path_with_array_values');
00983                 }
00984             }
00985 
00986             // drop legacy fields
00987             if (in_array($name, $this->dropfields)) {
00988                 continue;
00989             }
00990 
00991             // fields renaming
00992             if (array_key_exists($name, $this->renamefields)) {
00993                 $name = $this->renamefields[$name];
00994             }
00995 
00996             $cooked[$name] = $value;
00997         }
00998 
00999         // adding new fields
01000         foreach ($this->newfields as $name => $value) {
01001             $cooked[$name] = $value;
01002         }
01003 
01004         return $cooked;
01005     }
01006 
01010     public function get_name() {
01011         return $this->name;
01012     }
01013 
01017     public function get_path() {
01018         return $this->path;
01019     }
01020 
01024     public function is_grouped() {
01025         return $this->grouped;
01026     }
01027 
01031     public function get_processing_object() {
01032         return $this->pobject;
01033     }
01034 
01038     public function get_processing_method() {
01039         return $this->pmethod;
01040     }
01041 
01045     public function get_start_method() {
01046         return $this->smethod;
01047     }
01048 
01052     public function get_end_method() {
01053         return $this->emethod;
01054     }
01055 
01059     public function get_tags() {
01060         return $this->tags;
01061     }
01062 
01063 
01065 
01077     protected function validate_name($name) {
01078         // Validate various name constraints, throwing exception if needed
01079         if (empty($name)) {
01080             throw new convert_path_exception('convert_path_emptyname', $name);
01081         }
01082         if (preg_replace('/\s/', '', $name) != $name) {
01083             throw new convert_path_exception('convert_path_whitespace', $name);
01084         }
01085         if (preg_replace('/[^\x30-\x39\x41-\x5a\x5f\x61-\x7a]/', '', $name) != $name) {
01086             throw new convert_path_exception('convert_path_notasciiname', $name);
01087         }
01088     }
01089 
01104     protected function validate_pobject($pobject) {
01105         if (!is_object($pobject)) {
01106             throw new convert_path_exception('convert_path_no_object', get_class($pobject));
01107         }
01108         if (!method_exists($pobject, $this->get_processing_method()) and
01109             !method_exists($pobject, $this->get_end_method()) and
01110             !method_exists($pobject, $this->get_start_method())) {
01111             throw new convert_path_exception('convert_path_missing_method', get_class($pobject));
01112         }
01113     }
01114 }
01115 
01116 
01120 class convert_path_exception extends moodle_exception {
01121 
01129     public function __construct($errorcode, $a = null, $debuginfo = null) {
01130         parent::__construct($errorcode, '', '', $a, $debuginfo);
01131     }
01132 }
01133 
01134 
01141 class moodle1_file_manager implements loggable {
01142 
01144     public $converter;
01145 
01147     public $contextid;
01148 
01150     public $component;
01151 
01153     public $filearea;
01154 
01156     public $itemid = 0;
01157 
01159     public $userid;
01160 
01162     protected $basepath;
01163 
01165     protected $textlib;
01166 
01168     protected $fileids = array();
01169 
01180     public function __construct(moodle1_converter $converter, $contextid = null, $component = null, $filearea = null, $itemid = 0, $userid = null) {
01181         // set the initial destination of the migrated files
01182         $this->converter = $converter;
01183         $this->contextid = $contextid;
01184         $this->component = $component;
01185         $this->filearea  = $filearea;
01186         $this->itemid    = $itemid;
01187         $this->userid    = $userid;
01188         // set other useful bits
01189         $this->basepath  = $converter->get_tempdir_path();
01190         $this->textlib   = textlib_get_instance();
01191     }
01192 
01204     public function migrate_file($sourcepath, $filepath = '/', $filename = null, $sortorder = 0, $timecreated = null, $timemodified = null) {
01205 
01206         $sourcefullpath = $this->basepath.'/'.$sourcepath;
01207 
01208         if (!is_readable($sourcefullpath)) {
01209             throw new moodle1_convert_exception('file_not_readable', $sourcefullpath);
01210         }
01211 
01212         // sanitize filepath
01213         if (empty($filepath)) {
01214             $filepath = '/';
01215         }
01216         if (substr($filepath, -1) !== '/') {
01217             $filepath .= '/';
01218         }
01219         $filepath = clean_param($filepath, PARAM_PATH);
01220 
01221         if ($this->textlib->strlen($filepath) > 255) {
01222             throw new moodle1_convert_exception('file_path_longer_than_255_chars');
01223         }
01224 
01225         if (is_null($filename)) {
01226             $filename = basename($sourcefullpath);
01227         }
01228 
01229         $filename = clean_param($filename, PARAM_FILE);
01230 
01231         if ($filename === '') {
01232             throw new moodle1_convert_exception('unsupported_chars_in_filename');
01233         }
01234 
01235         if (is_null($timecreated)) {
01236             $timecreated = filectime($sourcefullpath);
01237         }
01238 
01239         if (is_null($timemodified)) {
01240             $timemodified = filemtime($sourcefullpath);
01241         }
01242 
01243         $filerecord = $this->make_file_record(array(
01244             'filepath'      => $filepath,
01245             'filename'      => $filename,
01246             'sortorder'     => $sortorder,
01247             'mimetype'      => mimeinfo('type', $sourcefullpath),
01248             'timecreated'   => $timecreated,
01249             'timemodified'  => $timemodified,
01250         ));
01251 
01252         list($filerecord['contenthash'], $filerecord['filesize'], $newfile) = $this->add_file_to_pool($sourcefullpath);
01253         $this->stash_file($filerecord);
01254 
01255         return $filerecord['id'];
01256     }
01257 
01265     public function migrate_directory($rootpath, $relpath='/') {
01266 
01267         if (!file_exists($this->basepath.'/'.$rootpath.$relpath)) {
01268             return array();
01269         }
01270 
01271         $fileids = array();
01272 
01273         // make the fake file record for the directory itself
01274         $filerecord = $this->make_file_record(array('filepath' => $relpath, 'filename' => '.'));
01275         $this->stash_file($filerecord);
01276         $fileids[] = $filerecord['id'];
01277 
01278         $items = new DirectoryIterator($this->basepath.'/'.$rootpath.$relpath);
01279 
01280         foreach ($items as $item) {
01281 
01282             if ($item->isDot()) {
01283                 continue;
01284             }
01285 
01286             if ($item->isLink()) {
01287                 throw new moodle1_convert_exception('unexpected_symlink');
01288             }
01289 
01290             if ($item->isFile()) {
01291                 $fileids[] = $this->migrate_file(substr($item->getPathname(), strlen($this->basepath.'/')),
01292                     $relpath, $item->getFilename(), 0, $item->getCTime(), $item->getMTime());
01293 
01294             } else {
01295                 $dirname = clean_param($item->getFilename(), PARAM_PATH);
01296 
01297                 if ($dirname === '') {
01298                     throw new moodle1_convert_exception('unsupported_chars_in_filename');
01299                 }
01300 
01301                 // migrate subdirectories recursively
01302                 $fileids = array_merge($fileids, $this->migrate_directory($rootpath, $relpath.$item->getFilename().'/'));
01303             }
01304         }
01305 
01306         return $fileids;
01307     }
01308 
01314     public function get_fileids() {
01315         return $this->fileids;
01316     }
01317 
01321     public function reset_fileids() {
01322         $this->fileids = array();
01323     }
01324 
01334     public function log($message, $level, $a = null, $depth = null, $display = false) {
01335         $this->converter->log($message, $level, $a, $depth, $display);
01336     }
01337 
01339 
01346     protected function make_file_record(array $fileinfo) {
01347 
01348         $defaultrecord = array(
01349             'contenthash'   => 'da39a3ee5e6b4b0d3255bfef95601890afd80709',  // sha1 of an empty file
01350             'contextid'     => $this->contextid,
01351             'component'     => $this->component,
01352             'filearea'      => $this->filearea,
01353             'itemid'        => $this->itemid,
01354             'filepath'      => null,
01355             'filename'      => null,
01356             'filesize'      => 0,
01357             'userid'        => $this->userid,
01358             'mimetype'      => null,
01359             'status'        => 0,
01360             'timecreated'   => $now = time(),
01361             'timemodified'  => $now,
01362             'source'        => null,
01363             'author'        => null,
01364             'license'       => null,
01365             'sortorder'     => 0,
01366         );
01367 
01368         if (!array_key_exists('id', $fileinfo)) {
01369             $defaultrecord['id'] = $this->converter->get_nextid();
01370         }
01371 
01372         // override the default values with the explicit data provided and return
01373         return array_merge($defaultrecord, $fileinfo);
01374     }
01375 
01386     protected function add_file_to_pool($pathname) {
01387 
01388         if (!is_readable($pathname)) {
01389             throw new moodle1_convert_exception('file_not_readable');
01390         }
01391 
01392         $contenthash = sha1_file($pathname);
01393         $filesize    = filesize($pathname);
01394         $hashpath    = $this->converter->get_workdir_path().'/files/'.substr($contenthash, 0, 2);
01395         $hashfile    = "$hashpath/$contenthash";
01396 
01397         if (file_exists($hashfile)) {
01398             if (filesize($hashfile) !== $filesize) {
01399                 // congratulations! you have found two files with different size and the same
01400                 // content hash. or, something were wrong (which is more likely)
01401                 throw new moodle1_convert_exception('same_hash_different_size');
01402             }
01403             $newfile = false;
01404 
01405         } else {
01406             check_dir_exists($hashpath);
01407             $newfile = true;
01408 
01409             if (!copy($pathname, $hashfile)) {
01410                 throw new moodle1_convert_exception('unable_to_copy_file');
01411             }
01412 
01413             if (filesize($hashfile) !== $filesize) {
01414                 throw new moodle1_convert_exception('filesize_different_after_copy');
01415             }
01416         }
01417 
01418         return array($contenthash, $filesize, $newfile);
01419     }
01420 
01426     protected function stash_file(array $filerecord) {
01427         $this->converter->set_stash('files', $filerecord, $filerecord['id']);
01428         $this->fileids[] = $filerecord['id'];
01429     }
01430 }
01431 
01432 
01436 class moodle1_inforef_manager {
01437 
01439     protected $annotator = null;
01440 
01442     protected $annotatorid = null;
01443 
01445     private $refs = array();
01446 
01457     public function __construct(moodle1_converter $converter, $name, $id = 0) {
01458         $this->annotator   = $name;
01459         $this->annotatorid = $id;
01460     }
01461 
01468     public function add_ref($item, $id) {
01469         $this->validate_item($item);
01470         $this->refs[$item][$id] = true;
01471     }
01472 
01479     public function add_refs($item, array $ids) {
01480         $this->validate_item($item);
01481         foreach ($ids as $id) {
01482             $this->refs[$item][$id] = true;
01483         }
01484     }
01485 
01491     public function write_refs(xml_writer $xmlwriter) {
01492         $xmlwriter->begin_tag('inforef');
01493         foreach ($this->refs as $item => $ids) {
01494             $xmlwriter->begin_tag($item.'ref');
01495             foreach (array_keys($ids) as $id) {
01496                 $xmlwriter->full_tag($item, $id);
01497             }
01498             $xmlwriter->end_tag($item.'ref');
01499         }
01500         $xmlwriter->end_tag('inforef');
01501     }
01502 
01510     protected function validate_item($item) {
01511 
01512         $allowed = array(
01513             'user'              => true,
01514             'grouping'          => true,
01515             'group'             => true,
01516             'role'              => true,
01517             'file'              => true,
01518             'scale'             => true,
01519             'outcome'           => true,
01520             'grade_item'        => true,
01521             'question_category' => true
01522         );
01523 
01524         if (!isset($allowed[$item])) {
01525             throw new coding_exception('Invalid inforef item type');
01526         }
01527     }
01528 }
 All Data Structures Namespaces Files Functions Variables Enumerations