More backup-converter API improvements

Added write_xml() helper that writes the given tree-ish structure into
the current xml writer. Improved get_contextid() so that it can use
indices for searching data. Added set_stash() and get_stash() helper
methods.
This commit is contained in:
David Mudrak 2011-05-12 15:32:36 +02:00
parent a5fe591280
commit beb7de3736
3 changed files with 135 additions and 72 deletions

View File

@ -174,12 +174,51 @@ abstract class moodle1_xml_handler extends moodle1_handler {
/**
* Dumps the data into the XML file
*
* @param string $element the name of the root element of the tree
* @param array $data the associative array of data to write
* @param array $attribs list of additional fields written as attributes instead of nested elements (all 'id' are there automatically)
* @param string $parent used internally during the recursion, do not set yourself
*/
public function write_xml(array $data) {
public function write_xml($element, array $data, array $attribs = array(), $parent = '/') {
$mypath = $parent . $element;
$myattribs = array();
// detect properties that should be rendered as element's attributes instead of children
foreach ($data as $name => $value) {
if (!is_array($value)) {
if ($name === 'id' or in_array($mypath . '/' . $name, $attribs)) {
$myattribs[$name] = $value;
unset($data[$name]);
}
}
}
// reorder the $data so that all sub-branches are at the end (needed by our parser)
$leaves = array();
$branches = array();
foreach ($data as $name => $value) {
if (is_array($value)) {
$branches[$name] = $value;
} else {
$leaves[$name] = $value;
}
}
$data = array_merge($leaves, $branches);
$this->xmlwriter->begin_tag($element, $myattribs);
foreach ($data as $name => $value) {
$this->xmlwriter->full_tag($name, $value);
if (is_array($value)) {
// recursively call self
$this->write_xml($name, $value, $attribs, $mypath);
} else {
$this->xmlwriter->full_tag($name, $value);
}
}
$this->xmlwriter->end_tag($element);
}
}
@ -267,19 +306,15 @@ class moodle1_root_handler extends moodle1_handler {
/**
* Handles the conversion of /MOODLE_BACKUP/INFO paths
*
* We do not produce any XML file here, just storing the data in the temp
* table so thay can be used by a later handler.
*/
class moodle1_info_handler extends moodle1_handler {
public function get_paths() {
return array(
new convert_path(
'info', '/MOODLE_BACKUP/INFO',
array(
'newfields' => array(
'mnet_remoteusers' => 0,
),
)
),
new convert_path('info', '/MOODLE_BACKUP/INFO'),
new convert_path('info_details', '/MOODLE_BACKUP/INFO/DETAILS'),
new convert_path('info_details_mod', '/MOODLE_BACKUP/INFO/DETAILS/MOD'),
new convert_path('info_details_mod_instance', '/MOODLE_BACKUP/INFO/DETAILS/MOD/INSTANCES/INSTANCE'),
@ -327,6 +362,8 @@ class moodle1_course_header_handler extends moodle1_xml_handler {
'enablecompletion' => 0,
'completionstartonenrol' => 0,
'completionnotify' => 0,
'tags' => array(),
'allowed_modules' => array(),
),
'dropfields' => array(
'roles_overrides',
@ -352,7 +389,14 @@ class moodle1_course_header_handler extends moodle1_xml_handler {
)
)
),
new convert_path('course_header_category', '/MOODLE_BACKUP/COURSE/HEADER/CATEGORY'),
new convert_path(
'course_header_category', '/MOODLE_BACKUP/COURSE/HEADER/CATEGORY',
array(
'newfields' => array(
'description' => null,
)
)
),
);
}
@ -372,22 +416,23 @@ class moodle1_course_header_handler extends moodle1_xml_handler {
public function on_course_header_end() {
$contextid = convert_helper::get_contextid($this->course['id'], 'course', $this->converter->get_id());
$contextid = $this->converter->get_contextid(CONTEXT_COURSE, $this->course['id']);
// stash the information needed by other handlers
$info = array(
'original_course_id' => $this->course['id'],
'original_course_fullname' => $this->course['fullname'],
'original_course_shortname' => $this->course['shortname'],
'original_course_startdate' => $this->course['startdate'],
'original_course_contextid' => $contextid
);
$this->converter->set_stash('original_course_info', $info);
$this->course['contextid'] = $contextid;
$this->course['category'] = $this->category;
$this->open_xml_writer('course/course.xml');
$this->xmlwriter->begin_tag('course', array(
'id' => $this->course['id'],
'contextid' => $contextid,
));
$this->write_xml($this->course);
$this->xmlwriter->begin_tag('category', array('id' => $this->category['id']));
$this->xmlwriter->full_tag('name', $this->category['name']);
$this->xmlwriter->full_tag('description', null);
$this->xmlwriter->end_tag('category');
$this->xmlwriter->full_tag('tags', null);
$this->xmlwriter->full_tag('allowed_modules', null);
$this->xmlwriter->end_tag('course');
$this->write_xml('course', $this->course, array('/course/contextid'));
$this->close_xml_writer();
}
}

View File

@ -244,10 +244,10 @@ class moodle1_converter extends base_converter {
throw new convert_exception('missing_path_handler', $data['path']);
}
$element = $this->pathelements[$data['path']];
$object = $element->get_processing_object();
$method = $element->get_processing_method();
$rdata = null; // data returned by the processing method, if any
$element = $this->pathelements[$data['path']];
$object = $element->get_processing_object();
$method = $element->get_processing_method();
$returned = null; // data returned by the processing method, if any
if (empty($object)) {
throw new convert_exception('missing_processing_object', $object);
@ -261,13 +261,14 @@ class moodle1_converter extends base_converter {
// if the path is not locked, apply the element's recipes and dispatch
// the cooked tags to the processing method
if (is_null($this->pathlock)) {
$cooked = $element->apply_recipes($data['tags']);
$rdata = $object->$method($cooked, $data['tags']);
$rawdatatags = $data['tags'];
$data['tags'] = $element->apply_recipes($data['tags']);
$returned = $object->$method($data['tags'], $rawdatatags);
}
// if the dispatched method returned SKIP_ALL_CHILDREN, remember the current path
// and lock it so that its children are not dispatched
if ($rdata === self::SKIP_ALL_CHILDREN) {
if ($returned === self::SKIP_ALL_CHILDREN) {
// check we haven't any previous lock
if (!is_null($this->pathlock)) {
throw new convert_exception('already_locked_path', $data['path']);
@ -276,8 +277,8 @@ class moodle1_converter extends base_converter {
$this->pathlock = $data['path'] . '/';
// if the method has returned any info, set element data to it
} else if (!is_null($rdata)) {
$element->set_data($rdata);
} else if (!is_null($returned)) {
$element->set_data($returned);
// use just the cooked parsed data otherwise
} else {
@ -384,6 +385,61 @@ class moodle1_converter extends base_converter {
return restore_dbops::get_backup_ids_record($this->get_id(), $itemname, $itemid);
}
/**
* Store some information for later processing
*
* This implenentation uses backup_ids_temp table to store data. Make
* sure that the stash name is unique.
*
* @param string $stashname name of the stash
* @param mixed $info information to stash
*/
public function set_stash($stashname, $info) {
$this->set_backup_ids_record($stashname, 0, 0, null, $info);
}
/**
* Restores a given stash stored previously by {@link self::set_stash()}
*
* @param string $stashname name of the stash
* @return mixed stashed data
*/
public function get_stash($stashname) {
return $this->get_backup_ids_record($stashname, 0);
}
/**
* Generates an artificial context id
*
* Moodle 1.9 backups do not contain any context information. But we need them
* in Moodle 2.x format so here we generate fictive context id for every given
* context level + instance combo.
*
* This implementation maps context level and instanceid to the columns
* of the backup_ids_temp table and uses the id of the record in that table
* as the context id.
*
* @see get_context_instance()
* @param int $level the context level, like CONTEXT_COURSE or CONTEXT_MODULE
* @param int $instance the instance id, for example $course->id for courses or $cm->id for activity modules
* @return int the context id
*/
public function get_contextid($level, $instance) {
$itemname = 'context' . $level;
$itemid = $instance;
$existing = $this->get_backup_ids_record($itemname, $itemid);
if (empty($existing)) {
// this context level + instance is required for the first time
// store it and re-read to obtain its record id
$this->set_backup_ids_record($itemname, $itemid);
$existing = $this->get_backup_ids_record($itemname, $itemid);
}
return $existing->id;
}
}

View File

@ -176,44 +176,6 @@ abstract class convert_helper {
return implode(", ", array_map($mapper, array_keys($fields), array_values($fields)));
}
/**
* Generate an artificial context ID
*
* @param int $instance The moodle component instance ID, same value used for get_context_instance()
* @param string $component The moodle component, like block_html, mod_quiz, etc
* @param string $converterid The converter ID
* @return int
* @todo Add caching?
* @todo Can we make the lookup faster? Not taking advantage of indexes
*/
public static function get_contextid($instance, $component = 'moodle', $converterid = NULL) {
global $DB;
// Attempt to retrieve the contextid
$contextid = $DB->get_field_select('backup_ids_temp', 'id',
$DB->sql_compare_text('info', 100).' = '.$DB->sql_compare_text('?', 100).' AND itemid = ? AND itemname = ?',
array($component, $instance, 'context'));
if (!empty($contextid)) {
return $contextid;
}
$context = new stdClass();
$context->itemid = $instance;
$context->itemname = 'context';
$context->info = $component;
if (!is_null($converterid)) {
$context->backupid = $converterid;
}
if ($id = $DB->insert_record('backup_ids_temp', $context)) {
return $id;
} else {
$msg = self::obj_to_readable($context);
throw new convert_helper_exception('failed_insert_record', $msg);
}
}
/// end of public API //////////////////////////////////////////////////////
/**