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.

This commit is contained in:
Mark Nielsen 2011-03-11 10:32:26 -08:00 committed by David Mudrak
parent c5c8b3503a
commit 8450e2c41f
9 changed files with 256 additions and 100 deletions

View File

@ -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?
}
}

View File

@ -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);
}
}
}

View File

@ -0,0 +1,72 @@
<?php
class convert_structure_parser_processor extends grouped_parser_processor {
/**
* @var plan_converter
*/
protected $converter;
public function __construct(plan_converter $converter) {
$this->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', '&amp;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);
}
}

View File

@ -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');

View File

@ -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();
}

View File

@ -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();
}
}

View File

@ -1,79 +1,23 @@
<?php
// @todo This is copied from backup_structure_step - not tested/modified for convert yet
abstract class convert_structure_step extends convert_step {
protected $filename; // Name of the file to be generated
protected $contenttransformer; // xml content transformer being used
// (need it here, apart from xml_writer,
// thanks to serialized data to process -
// say thanks to blocks!)
/**
* Constructor - instantiates one object of this class
*/
public function __construct($name, $filename, convert_task $task = null) {
$this->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();
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1,7 @@
<?php
class convert_path_element extends restore_path_element {
public function get_processing_method() {
return 'convert_' . $this->get_name();
}
}