|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 00003 // This file is part of Moodle - http://moodle.org/ 00004 // 00005 // Moodle is free software: you can redistribute it and/or modify 00006 // it under the terms of the GNU General Public License as published by 00007 // the Free Software Foundation, either version 3 of the License, or 00008 // (at your option) any later version. 00009 // 00010 // Moodle is distributed in the hope that it will be useful, 00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00013 // GNU General Public License for more details. 00014 // 00015 // You should have received a copy of the GNU General Public License 00016 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00017 00027 defined('MOODLE_INTERNAL') || die(); 00028 00029 require_once($CFG->dirroot . '/backup/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 }