From 8450e2c41f384b265317331d0f116c49aa26b3b3 Mon Sep 17 00:00:00 2001 From: Mark Nielsen Date: Fri, 11 Mar 2011 10:32:26 -0800 Subject: [PATCH] Attempting to write up processing of the moodle.xml file - the converter collects all of the element paths to watch and then dispatches them to the convert_structure_step classes. --- backup/converter/moodle1.class.php | 9 ++ .../util/converter/plan_converter.class.php | 122 ++++++++++++++++++ ...onvert_structure_paser_processor.class.php | 72 +++++++++++ backup/util/includes/convert_includes.php | 9 +- backup/util/plan/convert_plan.class.php | 7 + backup/util/plan/convert_step.class.php | 11 ++ .../plan/convert_structure_step.class.php | 112 +++------------- backup/util/plan/convert_task.class.php | 7 + .../structure/convert_path_element.class.php | 7 + 9 files changed, 256 insertions(+), 100 deletions(-) create mode 100644 backup/util/helper/convert_structure_paser_processor.class.php create mode 100644 backup/util/structure/convert_path_element.class.php diff --git a/backup/converter/moodle1.class.php b/backup/converter/moodle1.class.php index c3cd5a1aeef..02d630e9a6f 100644 --- a/backup/converter/moodle1.class.php +++ b/backup/converter/moodle1.class.php @@ -25,4 +25,13 @@ abstract class moodle1_converter extends plan_converter { return false; } + public function build_plan() { + $this->xmlparser = new progressive_parser(); + $this->xmlparser->set_file($this->get_tempdir() . '/moodle.xml'); + $this->xmlprocessor = new convert_structure_parser_processor($this); // @todo Probably move this + $this->xmlparser->set_processor($this->xmlprocessor); + + + $xmlparser->process(); // @todo When to really do this? + } } \ No newline at end of file diff --git a/backup/util/converter/plan_converter.class.php b/backup/util/converter/plan_converter.class.php index c651e942247..df3aa5df35a 100644 --- a/backup/util/converter/plan_converter.class.php +++ b/backup/util/converter/plan_converter.class.php @@ -4,8 +4,29 @@ */ abstract class plan_converter extends base_converter { + /** + * @var convert_plan + */ protected $plan; + /** + * @var progressive_parser + */ + protected $xmlparser; + + /** + * @var convert_structure_parser_processor + */ + protected $xmlprocessor; + + /** + * @var array + */ + protected $pathelements; // Array of pathelements to process + + // @todo needed? redo? + protected $pathlock; // Path currently locking processing of children + /** * @return convert_plan */ @@ -27,4 +48,105 @@ abstract class plan_converter extends base_converter { parent::destroy(); $this->get_plan()->destroy(); } + + public function add_structures($processingobject, array $structures) { + // Override if using class convert_structure_step + $this->prepare_pathelements($processingobject, $structures); + + // Add pathelements to processor + foreach ($this->pathelements as $element) { + $this->xmlprocessor->add_path($element->get_path(), $element->is_grouped()); + } + } + + /** + * Prepare the pathelements for processing, looking for duplicates, applying + * processing objects and other adjustments + */ + protected function prepare_pathelements($processingobject, $elementsarr) { + // First iteration, push them to new array, indexed by name + // detecting duplicates in names or paths + $names = array(); + $paths = array(); + foreach($elementsarr as $element) { + if (!$element instanceof convert_path_element) { + throw new restore_step_exception('restore_path_element_wrong_class', get_class($element)); // @todo Change exception + } + if (array_key_exists($element->get_name(), $names)) { + throw new restore_step_exception('restore_path_element_name_alreadyexists', $element->get_name()); // @todo Change exception + } + if (array_key_exists($element->get_path(), $paths)) { + throw new restore_step_exception('restore_path_element_path_alreadyexists', $element->get_path()); // @todo Change exception + } + $names[$element->get_name()] = true; + $paths[$element->get_path()] = $element; + } + // Now, for each element not having one processing object, if + // not child of grouped element, assign $this (the step itself) as processing element + // Note method must exist or we'll get one @restore_path_element_exception + foreach($paths as $key => $pelement) { + if ($pelement->get_processing_object() === null && !$this->grouped_parent_exists($pelement, $paths)) { + $paths[$key]->set_processing_object($processingobject); + } + } + // Done, add them to pathelements (dupes by key - path - are discarded) + $this->pathelements = array_merge($this->pathelements, $paths); + } + + /** + * Given one pathelement, return true if grouped parent was found + */ + protected function grouped_parent_exists($pelement, $elements) { + foreach ($elements as $element) { + if ($pelement->get_path() == $element->get_path()) { + continue; // Don't compare against itself + } + // If element is grouped and parent of pelement, return true + if ($element->is_grouped() and strpos($pelement->get_path() . '/', $element->get_path()) === 0) { + return true; + } + } + return false; // no grouped parent found + } + + /** + * Receive one chunk of information form the xml parser processor and + * dispatch it, following the naming rules + */ + final public function process($data) { + if (!array_key_exists($data['path'], $this->pathelements)) { // Incorrect path, must not happen + throw new restore_step_exception('restore_structure_step_missing_path', $data['path']); // @todo Change exception + } + $element = $this->pathelements[$data['path']]; + $object = $element->get_processing_object(); + $method = $element->get_processing_method(); + $rdata = null; + if (empty($object)) { // No processing object defined + throw new restore_step_exception('restore_structure_step_missing_pobject', $object); // @todo Change exception + } + // Release the lock if we aren't anymore within children of it + if (!is_null($this->pathlock) and strpos($data['path'], $this->pathlock) === false) { + $this->pathlock = null; + } + if (is_null($this->pathlock)) { // Only dispatch if there isn't any lock + $rdata = $object->$method($data['tags']); // Dispatch to proper object/method + } + + // If the dispatched method returns SKIP_ALL_CHILDREN, we grab current path in order to + // lock dispatching to any children + if ($rdata === self::SKIP_ALL_CHILDREN) { + // Check we haven't any previous lock + if (!is_null($this->pathlock)) { + throw new restore_step_exception('restore_structure_step_already_skipping', $data['path']); // @todo Change exception + } + // Set the lock + $this->pathlock = $data['path'] . '/'; // Lock everything below current path + + // Continue with normal processing of return values + } else if ($rdata !== null) { // If the method has returned any info, set element data to it + $element->set_data($rdata); + } else { // Else, put the original parsed data + $element->set_data($data); + } + } } \ No newline at end of file diff --git a/backup/util/helper/convert_structure_paser_processor.class.php b/backup/util/helper/convert_structure_paser_processor.class.php new file mode 100644 index 00000000000..066310a5d8e --- /dev/null +++ b/backup/util/helper/convert_structure_paser_processor.class.php @@ -0,0 +1,72 @@ +converter = $converter; + parent::__construct(); + } + + /** + * Provide NULL and legacy file.php uses decoding + */ + public function process_cdata($cdata) { + global $CFG; + if ($cdata === '$@NULL@$') { // Some cases we know we can skip complete processing + return null; + } else if ($cdata === '') { + return ''; + } else if (is_numeric($cdata)) { + return $cdata; + } else if (strlen($cdata) < 32) { // Impossible to have one link in 32cc + return $cdata; // (http://10.0.0.1/file.php/1/1.jpg, http://10.0.0.1/mod/url/view.php?id=) + } else if (strpos($cdata, '$@FILEPHP@$') === false) { // No $@FILEPHP@$, nothing to convert + return $cdata; + } + // Decode file.php calls + $search = array ("$@FILEPHP@$"); + $replace = array(get_file_url($this->courseid)); + $result = str_replace($search, $replace, $cdata); + // Now $@SLASH@$ and $@FORCEDOWNLOAD@$ MDL-18799 + $search = array('$@SLASH@$', '$@FORCEDOWNLOAD@$'); + if ($CFG->slasharguments) { + $replace = array('/', '?forcedownload=1'); + } else { + $replace = array('%2F', '&forcedownload=1'); + } + return str_replace($search, $replace, $result); + } + + /** + * Override this method so we'll be able to skip + * dispatching some well-known chunks, like the + * ones being 100% part of subplugins stuff. Useful + * for allowing development without having all the + * possible restore subplugins defined + */ + protected function postprocess_chunk($data) { + + // Iterate over all the data tags, if any of them is + // not 'subplugin_XXXX' or has value, then it's a valid chunk, + // pass it to standard (parent) processing of chunks. + foreach ($data['tags'] as $key => $value) { + if (trim($value) !== '' || strpos($key, 'subplugin_') !== 0) { + parent::postprocess_chunk($data); + return; + } + } + // Arrived here, all the tags correspond to sublplugins and are empty, + // skip the chunk, and debug_developer notice + $this->chunks--; // not counted + debugging('Missing support on restore for ' . clean_param($data['path'], PARAM_PATH) . + ' subplugin (' . implode(', ', array_keys($data['tags'])) .')', DEBUG_DEVELOPER); + } + + protected function dispatch_chunk($data) { + $this->converter->process($data); + } +} \ No newline at end of file diff --git a/backup/util/includes/convert_includes.php b/backup/util/includes/convert_includes.php index 2da8b585537..dab23a6340b 100644 --- a/backup/util/includes/convert_includes.php +++ b/backup/util/includes/convert_includes.php @@ -19,10 +19,13 @@ require_once($CFG->dirroot.'/backup/util/plan/convert_step.class.php'); require_once($CFG->dirroot.'/backup/util/plan/convert_task.class.php'); require_once($CFG->dirroot.'/backup/util/plan/convert_structure_step.class.php'); require_once($CFG->dirroot.'/backup/util/plan/convert_execution_step.class.php'); -require_once($CFG->dirroot.'/backup/util/plan/convert_.class.php'); -require_once($CFG->dirroot.'/backup/util/plan/convert_.class.php'); -require_once($CFG->dirroot.'/backup/util/plan/convert_.class.php'); +require_once($CFG->dirroot.'/backup/util/structure/restore_path_element.class.php'); +require_once($CFG->dirroot.'/backup/util/structure/convert_path_element.class.php'); +require_once($CFG->dirroot.'/backup/util/plan/convert_execution_step.class.php'); +require_once($CFG->dirroot.'/backup/util/xml/parser/processors/grouped_parser_processor.class.php'); +require_once($CFG->dirroot.'/backup/util/helper/convert_structure_parser_processor.class.php'); require_once($CFG->dirroot.'/backup/moodle2/convert_stepslib.php'); +require_once($CFG->dirroot.'/backup/util/xml/parser/progressive_parser.class.php'); // And some moodle stuff too require_once($CFG->libdir.'/fileslib.php'); \ No newline at end of file diff --git a/backup/util/plan/convert_plan.class.php b/backup/util/plan/convert_plan.class.php index 55fc4ded767..d05d822acc0 100644 --- a/backup/util/plan/convert_plan.class.php +++ b/backup/util/plan/convert_plan.class.php @@ -26,6 +26,13 @@ class convert_plan extends base_plan implements loggable { return $this->converter->get_convertdir(); } + /** + * @return plan_converter + */ + public function get_converter() { + return $this->converter; + } + public function get_converterid() { return $this->converter->get_id(); } diff --git a/backup/util/plan/convert_step.class.php b/backup/util/plan/convert_step.class.php index 71207666f38..1379e2ea339 100644 --- a/backup/util/plan/convert_step.class.php +++ b/backup/util/plan/convert_step.class.php @@ -16,4 +16,15 @@ abstract class convert_step extends base_step { } return $this->task->get_convertid(); } + + /** + * @throws backup_exception + * @return plan_converter + */ + protected function get_converter() { + if (!$this->task instanceof convert_task) { + throw new backup_exception('not_specified_convert_task'); // @todo Define string + } + return $this->task->get_converter(); + } } \ No newline at end of file diff --git a/backup/util/plan/convert_structure_step.class.php b/backup/util/plan/convert_structure_step.class.php index 9af22e61226..3ebd1bd4e67 100644 --- a/backup/util/plan/convert_structure_step.class.php +++ b/backup/util/plan/convert_structure_step.class.php @@ -1,79 +1,23 @@ filename = $filename; - $this->contenttransformer = null; - parent::__construct($name, $task); - } - - public function execute() { + final public function execute() { if (!$this->execute_condition()) { // Check any condition to execute this return; } - $fullpath = $this->task->get_taskbasepath(); - - // We MUST have one fullpath here, else, error - if (empty($fullpath)) { - throw new backup_step_exception('backup_structure_step_undefined_fullpath'); - } - - // Append the filename to the fullpath - $fullpath = rtrim($fullpath, '/') . '/' . $this->filename; - - // Create output, transformer, writer, processor - $xo = new file_xml_output($fullpath); - $xt = null; - if (class_exists('backup_xml_transformer')) { - $xt = new backup_xml_transformer($this->get_courseid()); - $this->contenttransformer = $xt; // Save the reference to the transformer - // as far as we are going to need it out - // from xml_writer (blame serialized data!) - } - $xw = new xml_writer($xo, $xt); - $pr = new backup_structure_processor($xw); - - // Set processor variables from settings - foreach ($this->get_settings() as $setting) { - $pr->set_var($setting->get_name(), $setting->get_value()); - } - // Add backupid as one more var for processor - $pr->set_var(backup::VAR_BACKUPID, $this->get_backupid()); - - // Get structure definition + // Get restore_path elements array adapting and preparing it for processing $structure = $this->define_structure(); - if (! $structure instanceof backup_nested_element) { - throw new backup_step_exception('backup_structure_step_wrong_structure'); + if (!is_array($structure)) { + throw new restore_step_exception('restore_step_structure_not_array', $this->get_name()); // @todo Change exception } - - // Start writer - $xw->start(); - - // Process structure definition - $structure->process($pr); - - // Close everything - $xw->stop(); - - // Destroy the structure. It helps PHP 5.2 memory a lot! - $structure->destroy(); + $this->get_converter()->add_structures($this, $structure); } /** - * As far as backup structure steps are implementing backup_plugin stuff, they need to + * As far as restore structure steps are implementing restore_plugin stuff, they need to * have the parent task available for wrapping purposes (get course/context....) */ public function get_task() { @@ -81,40 +25,14 @@ abstract class convert_structure_step extends convert_step { } /** - * Add plugin structure to any element in the structure backup tree + * This method will be executed after the whole structure step have been processed * - * @param string $plugintype type of plugin as defined by get_plugin_types() - * @param backup_nested_element $element element in the structure backup tree that - * we are going to add plugin information to - * @param bool $multiple to define if multiple plugins can produce information - * for each instance of $element (true) or no (false) + * After execution method for code needed to be executed after the whole structure + * has been processed. Useful for cleaning tasks, files process and others. Simply + * overwrite in in your steps if needed */ - protected function add_plugin_structure($plugintype, $element, $multiple) { - - global $CFG; - - // Check the requested plugintype is a valid one - if (!array_key_exists($plugintype, get_plugin_types($plugintype))) { - throw new backup_step_exception('incorrect_plugin_type', $plugintype); - } - - // Arrived here, plugin is correct, let's create the optigroup - $optigroupname = $plugintype . '_' . $element->get_name() . '_plugin'; - $optigroup = new backup_optigroup($optigroupname, null, $multiple); - $element->add_child($optigroup); // Add optigroup to stay connected since beginning - - // Get all the optigroup_elements, looking across all the plugin dirs - $pluginsdirs = get_plugin_list($plugintype); - foreach ($pluginsdirs as $name => $plugindir) { - $classname = 'backup_' . $plugintype . '_' . $name . '_plugin'; - $backupfile = $plugindir . '/backup/moodle2/' . $classname . '.class.php'; - if (file_exists($backupfile)) { - require_once($backupfile); - $backupplugin = new $classname($plugintype, $name, $optigroup, $this); - // Add plugin returned structure to optigroup - $backupplugin->define_plugin_structure($element->get_name()); - } - } + protected function after_execute() { + // do nothing by default } /** @@ -130,8 +48,8 @@ abstract class convert_structure_step extends convert_step { } /** - * Function that will return the structure to be processed by this backup_step. - * Must return one backup_nested_element + * Function that will return the structure to be processed by this convert_step. + * Must return one array of @convert_path_element elements */ abstract protected function define_structure(); -} \ No newline at end of file +} diff --git a/backup/util/plan/convert_task.class.php b/backup/util/plan/convert_task.class.php index 756be73c7fc..62f8bb81932 100644 --- a/backup/util/plan/convert_task.class.php +++ b/backup/util/plan/convert_task.class.php @@ -10,4 +10,11 @@ abstract class convert_task extends base_task { public function get_convertid() { return $this->plan->get_backupid(); } + + /** + * @return plan_converter + */ + public function get_converter() { + return $this->plan->get_converter(); + } } \ No newline at end of file diff --git a/backup/util/structure/convert_path_element.class.php b/backup/util/structure/convert_path_element.class.php new file mode 100644 index 00000000000..75a9c0d4c08 --- /dev/null +++ b/backup/util/structure/convert_path_element.class.php @@ -0,0 +1,7 @@ +get_name(); + } +} \ No newline at end of file