MDL-27376 MDL-27377 Backup converters API refactored

* Several base_converter methods made protected when there was no obvious
reason why they should be public (subject of eventual change still).
* The conversion chain now constructed in advance before any converter
class is instantiated, using Dijkstra's algorithm.
This commit is contained in:
David Mudrak 2011-05-05 02:21:23 +02:00
parent e48477d94f
commit 0164592b8c
13 changed files with 616 additions and 175 deletions

View File

@ -40,6 +40,8 @@ abstract class backup implements checksumable {
// Backup format
const FORMAT_MOODLE = 'moodle2';
const FORMAT_MOODLE1 = 'moodle1';
const FORMAT_IMSCC = 'imscc';
const FORMAT_UNKNOWN = 'unknown';
// Interactive

View File

@ -377,17 +377,17 @@ class restore_controller extends backup implements loggable {
}
/**
* convert from current format to backup::MOODLE format
* Converts from current format to backup::MOODLE format
*/
public function convert() {
global $CFG;
require_once($CFG->dirroot . '/backup/util/includes/convert_includes.php');
if ($this->status != backup::STATUS_REQUIRE_CONV) {
throw new restore_controller_exception('cannot_convert_not_required_status');
}
require_once($CFG->dirroot.'/backup/util/includes/convert_includes.php');
// Run conversion until we have the proper format
// Run conversion to the proper format
convert_helper::to_moodle2_format($this->get_tempdir(), $this->format);
// If no exceptions were thrown, then we are in the proper format

View File

@ -16,8 +16,6 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* All of the task and step classes specific to moodle1 conversion
*
* @package core
* @subpackage backup-convert
* @copyright 2011 Mark Nielsen <mark@moodlerooms.com>
@ -26,49 +24,47 @@
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot.'/backup/converter/moodle1/taskslib.php');
require_once($CFG->dirroot.'/backup/converter/moodle1/stepslib.php');
require_once($CFG->dirroot . '/backup/converter/moodle1/taskslib.php');
require_once($CFG->dirroot . '/backup/converter/moodle1/stepslib.php');
/**
* This will be the Moodle 1 to Moodle 2 Converter
* Converter of Moodle 1.9 backup into Moodle 2.x format
*/
class moodle1_converter extends plan_converter {
/**
* The current module being processed
*
* @var string
*/
/** @var string the current module being processed */
protected $currentmod = '';
/**
* The current block being processed
*
* @var string
*/
/** @var string the current block being processed */
protected $currentblock = '';
/**
* @return boolean
* Detects the Moodle 1.9 format of the backup directory
*
* @param string $tempdir the name of the backup directory
* @return null|string backup::FORMAT_MOODLE1 if the Moodle 1.9 is detected, null otherwise
*/
public function can_convert() {
// Then look for MOODLE1 (moodle1) format
$filepath = $this->get_tempdir() . '/moodle.xml';
if (file_exists($filepath)) { // Looks promising, lets load some information
$handle = fopen($filepath, "r");
$first_chars = fread($handle,200);
public static function detect_format($tempdir) {
global $CFG;
$filepath = $CFG->dataroot . '/temp/backup/' . $tempdir . '/moodle.xml';
if (file_exists($filepath)) {
// looks promising, lets load some information
$handle = fopen($filepath, 'r');
$first_chars = fread($handle, 200);
fclose($handle);
// Check if it has the required strings
if (strpos($first_chars,'<?xml version="1.0" encoding="UTF-8"?>') !== false &&
strpos($first_chars,'<MOODLE_BACKUP>') !== false &&
// check if it has the required strings
if (strpos($first_chars,'<?xml version="1.0" encoding="UTF-8"?>') !== false and
strpos($first_chars,'<MOODLE_BACKUP>') !== false and
strpos($first_chars,'<INFO>') !== false) {
return true;
return backup::FORMAT_MOODLE1;
}
}
return false;
}
return null;
}
/**
* Path transformation for modules and blocks. Here we
@ -116,7 +112,7 @@ class moodle1_converter extends plan_converter {
public function build_plan() {
$this->xmlparser = new progressive_parser();
$this->xmlparser->set_file($this->get_tempdir() . '/moodle.xml');
$this->xmlparser->set_file($this->get_tempdir_path() . '/moodle.xml');
$this->xmlprocessor = new convert_structure_parser_processor($this); // @todo Probably move this
$this->xmlparser->set_processor($this->xmlprocessor);

View File

@ -26,15 +26,13 @@
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot.'/backup/util/includes/convert_includes.php');
require_once($CFG->dirroot . '/backup/converter/moodle1/converter.class.php');
class moodle1_converter_test extends UnitTestCase {
public static $includecoverage = array();
/**
* @var string
*/
/** @var string the name of the directory containing the unpacked Moodle 1.9 backup */
protected $tempdir;
public function setUp() {
@ -43,7 +41,7 @@ class moodle1_converter_test extends UnitTestCase {
$this->tempdir = convert_helper::generate_id('simpletest');
check_dir_exists("$CFG->dataroot/temp/backup/$this->tempdir");
copy(
$CFG->dirroot.'/backup/converter/moodle1/simpletest/files/moodle.xml',
"$CFG->dirroot/backup/converter/moodle1/simpletest/files/moodle.xml",
"$CFG->dataroot/temp/backup/$this->tempdir/moodle.xml"
);
}
@ -55,10 +53,9 @@ class moodle1_converter_test extends UnitTestCase {
}
}
public function test_can_convert() {
$converter = convert_factory::converter('moodle1', $this->tempdir);
$this->assertIsA($converter, 'moodle1_converter');
$this->assertTrue($converter->can_convert());
public function test_detect_format() {
$detected = moodle1_converter::detect_format($this->tempdir);
$this->assertEqual(backup::FORMAT_MOODLE1, $detected);
}
public function test_convert() {

View File

@ -24,6 +24,8 @@
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/backup/util/includes/convert_includes.php');
class moodle1_root_task extends convert_task {
/**
* Function responsible for building the steps of any task

View File

@ -25,95 +25,195 @@
defined('MOODLE_INTERNAL') || die();
/**
* Base Abstract Converter
* Base abstract converter
*
* @throws backup_exception|Exception|null
*/
abstract class base_converter {
/** @var string unique identifier of this converter instance */
protected $id;
/** @var string the name of the directory containing the unpacked backup being converted */
protected $tempdir;
protected $convertdir;
/** @var string the name of the directory where the backup is converted to */
protected $workdir;
// do we want absolute path instead of tempdir?
// Do we need to create a new tempdir to convert into? EG: target...
/**
* Constructor
*
* @param string $tempdir the relative path to the directory containing the unpacked backup to convert
*/
public function __construct($tempdir) {
$this->tempdir = $tempdir;
$this->convertdir = $this->tempdir.'_'.$this->get_name();
$this->id = convert_helper::generate_id($this->convertdir);
$this->id = convert_helper::generate_id($this->workdir);
$this->workdir = $tempdir . '_' . $this->get_name() . '_' . $this->id;
$this->init();
}
public function init() {
}
/**
* Get instance identifier
*
* @return string the unique identifier of this converter instance
*/
public function get_id() {
return $this->id;
}
/**
* Get converter name
*
* @return string the system name of the converter
*/
public function get_name() {
return array_shift(explode('_', get_class($this)));
}
public function get_convertdir() {
global $CFG;
return "$CFG->dataroot/temp/backup/$this->convertdir";
}
public function get_tempdir() {
global $CFG;
return "$CFG->dataroot/temp/backup/$this->tempdir";
}
public function delete_convertdir() {
fulldelete($this->get_convertdir());
}
public function create_convertdir() {
$this->delete_convertdir();
if (!check_dir_exists($this->get_convertdir())) {
throw new backup_exception('failedtomakeconvertdir'); // @todo Define this string
}
}
public function replace_tempdir() {
fulldelete($this->get_tempdir());
if (!rename($this->get_convertdir(), $this->get_tempdir())) {
throw new backup_exception('failedmoveconvertedintoplace'); // @todo Define this string
}
}
/**
* @abstract
* @return boolean
* Converts the backup directory
*/
abstract public function can_convert();
// Kicks things off
public function convert() {
$e = NULL;
$e = null;
// try to execute the converter
try {
$this->create_convertdir();
$this->create_workdir();
$this->execute();
$this->replace_tempdir();
} catch (Exception $e) {
}
// Do cleanup...
// clean-up stuff if needed
$this->destroy();
// eventually re-throw the execution exception
if ($e instanceof Exception) {
throw $e;
}
}
abstract public function execute();
/// public static methods //////////////////////////////////////////////////
public function destroy() {
$this->delete_convertdir();
/**
* Makes sure that this converter is available at this site
*
* This is intended for eventual PHP extensions check, environment check etc.
* All checks that do not depend on actual backup data should be done here.
*
* @return boolean true if this converter should be considered as available
*/
public static function is_available() {
return true;
}
/**
* Detects the format of the backup directory
*
* Moodle 2.x format is being detected by the core itself. The converters are
* therefore supposed to detect the source format. Eventually, if the target
* format os not {@link backup::FORMAT_MOODLE} then they should be able to
* detect both source and target formats.
*
* @param string $tempdir the name of the backup directory
* @return null|string null if not recognized, backup::FORMAT_xxx otherwise
*/
public static function detect_format($tempdir) {
return null;
}
/**
* Returns the basic information about the converter
*
* The returned array must contain the following keys:
* 'from' - the supported source format, eg. backup::FORMAT_MOODLE1
* 'to' - the supported target format, eg. backup::FORMAT_MOODLE
* 'cost' - the cost of the conversion, non-negative non-zero integer
*/
public static function description() {
return array(
'from' => null,
'to' => null,
'cost' => null,
);
}
/// end of public API //////////////////////////////////////////////////////
/**
* Initialize the instance if needed, called by the constructor
*/
protected function init() {
}
/**
* Converts the contents of the tempdir into the target format in the workdir
*/
protected abstract function execute();
/**
* @return string the full path to the working directory
*/
protected function get_workdir_path() {
global $CFG;
return "$CFG->dataroot/temp/backup/$this->workdir";
}
/**
* @return string the full path to the directory with the source backup
*/
protected function get_tempdir_path() {
global $CFG;
return "$CFG->dataroot/temp/backup/$this->tempdir";
}
/**
* Prepares a new empty working directory
*/
protected function create_workdir() {
fulldelete($this->get_workdir_path());
if (!check_dir_exists($this->get_workdir_path())) {
throw new backup_exception('failedtocreateworkdir');
}
}
/**
* Replaces the source backup directory with the converted version
*
* If $CFG->keeptempdirectoriesonbackup is defined, the original source
* source backup directory is kept for debugging purposes.
*/
protected function replace_tempdir() {
global $CFG;
if (empty($CFG->keeptempdirectoriesonbackup)) {
fulldelete($this->get_tempdir_path);
} else {
if (!rename($this->get_tempdir_path, $this->get_tempdir_path . '_' . $this->get_name() . '_' . $this->id . '_source')) {
throw new backup_exception('failedrenamesourcetempdir');
}
}
if (!rename($this->get_workdir_path(), $this->get_tempdir_path())) {
throw new backup_exception('failedmoveconvertedintoplace');
}
}
/**
* Cleans up stuff after the execution
*
* Note that we do not know if the execution was successful or not.
* An exception might have been thrown.
*/
protected function destroy() {
global $CFG;
if (empty($CFG->keeptempdirectoriesonbackup)) {
fulldelete($this->get_workdir_path);
}
}
}

View File

@ -25,40 +25,31 @@
defined('MOODLE_INTERNAL') || die();
/**
* Plan based abstract converter
* Base class for all converters using plan/tasks/steps pattern
*
* All backup converters that use {@link convert_plan} should extend this class.
* All converters that use {@link convert_plan} must extend this class.
*/
abstract class plan_converter extends base_converter {
abstract class planned_converter extends base_converter {
/**
* @var convert_plan
*/
/** @var convert_plan */
protected $plan;
/**
* @var progressive_parser
*/
/** @var progressive_parser */
protected $xmlparser;
/**
* @var convert_structure_parser_processor
*/
/** @var convert_structure_parser_processor */
protected $xmlprocessor;
/** @var array path elements to process */
protected $pathelements = array();
/** @todo needed? redo? path currently locking processing of children */
protected $pathlock;
/**
* @var array
* Instructs the dispatcher to ignore all children below path processor returning it
*/
protected $pathelements = array(); // Array of pathelements to process
// @todo needed? redo?
protected $pathlock; // Path currently locking processing of children
// @todo IDK what this is really...
const SKIP_ALL_CHILDREN = -991399; // To instruct the dispatcher about to ignore
// all children below path processor returning it
const SKIP_ALL_CHILDREN = -991399;
/**
* Return the plan instance, instatinate it if it does not exist yet
*
* @return convert_plan
*/
public function get_plan() {

View File

@ -56,23 +56,6 @@ abstract class convert_factory {
return new $classname($tempdir);
}
/**
* Instantiates a list of all installed converters operating on a given directory
*
* @param string $tempdir The temp directory to operate on
* @return array
*/
public static function converters($tempdir) {
global $CFG;
$converters = array();
$plugins = get_list_of_plugins('backup/converter');
foreach ($plugins as $name) {
$converters[$name] = self::converter($name, $tempdir);
}
return $converters;
}
/**
* Runs through all plugins of a specific type and instantiates their task class
*

View File

@ -228,42 +228,34 @@ abstract class backup_general_helper extends backup_helper {
}
/**
* Given one temp/backup/xxx dir, detect its format
* Detects the format of the given unpacked backup directory
*
* TODO: Move harcoded detection here to delegated classes under backup/format (moodle1, imscc..)
* conversion code will be there too.
* @param string $tempdir the name of the backup directory
* @return string one of backup::FORMAT_xxx constants
*/
public static function detect_backup_format($tempdir) {
global $CFG;
$filepath = $CFG->dataroot . '/temp/backup/' . $tempdir . '/moodle_backup.xml';
require_once($CFG->dirroot . '/backup/util/includes/convert_includes.php');
// Does tempdir exist and is dir
if (!is_dir(dirname($filepath))) {
throw new backup_helper_exception('tmp_backup_directory_not_found', dirname($filepath));
}
// First look for MOODLE (moodle2) format
if (file_exists($filepath)) { // Looks promising, lets load some information
$handle = fopen ($filepath, "r");
$first_chars = fread($handle,200);
$status = fclose ($handle);
// Check if it has the required strings
if (strpos($first_chars,'<?xml version="1.0" encoding="UTF-8"?>') !== false &&
strpos($first_chars,'<moodle_backup>') !== false &&
strpos($first_chars,'<information>') !== false) {
if (convert_helper::detect_moodle2_format($tempdir)) {
return backup::FORMAT_MOODLE;
}
// see if a converter can identify the format
$converters = convert_factory::available_converters();
foreach ($converters as $name) {
$classname = "{$name}_converter";
if (!class_exists($classname)) {
throw new coding_exception("available_converters() is supposed to load
converter classes but class $classname not found");
}
// See if a converter can identify the format as its own
$converters = convert_factory::converters($tempdir);
foreach ($converters as $name => $converter) {
if ($converter->can_convert()) {
return $name;
$detected = call_user_func($classname .'::detect_format', $tempdir);
if (!empty($detected)) {
return $detected;
}
}
// Arrived here, unknown format
return backup::FORMAT_UNKNOWN;
}
}

View File

@ -16,6 +16,8 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Provides {@link convert_helper} class
*
* @package core
* @subpackage backup-convert
* @copyright 2011 Mark Nielsen <mark@moodlerooms.com>
@ -25,37 +27,129 @@
defined('MOODLE_INTERNAL') || die();
/**
* General convert helper
* Provides various functionality via its static methods
*/
abstract class convert_helper {
/**
* @param string $entropy
* @return string random identifier
*/
public static function generate_id($entropy) {
return md5(time() . '-' . $entropy . '-' . random_string(20));
}
/**
* @static
* Returns the list of all available converters and loads their classes
*
* Converter must be installed as a directory in backup/converter/ and its
* method is_available() must return true to get to the list.
*
* @see base_converter::is_available()
* @return array of strings
*/
public static function available_converters() {
global $CFG;
$converters = array();
$plugins = get_list_of_plugins('backup/converter');
foreach ($plugins as $name) {
$classfile = "$CFG->dirroot/backup/converter/$name/converter.class.php";
$classname = "{$name}_converter";
if (!file_exists($classfile)) {
throw new coding_exception("Converter factory error: class file not found $classfile");
}
require_once($classfile);
if (!class_exists($classname)) {
throw new coding_exception("Converter factory error: class not found $classname");
}
if (call_user_func($classname .'::is_available')) {
$converters[] = $name;
}
}
return $converters;
}
/**
* Detects if the given folder contains an unpacked moodle2 backup
*
* @param string $tempdir the name of the backup directory
* @return boolean true if moodle2 format detected, false otherwise
*/
public static function detect_moodle2_format($tempdir) {
global $CFG;
$dirpath = $CFG->dataroot . '/temp/backup/' . $tempdir;
$filepath = $dirpath . '/moodle_backup.xml';
if (!is_dir($dirpath)) {
throw new backup_helper_exception('tmp_backup_directory_not_found', $dirpath);
}
if (!file_exists($filepath)) {
return false;
}
$handle = fopen($filepath, 'r');
$firstchars = fread($handle, 200);
$status = fclose($handle);
if (strpos($firstchars,'<?xml version="1.0" encoding="UTF-8"?>') !== false and
strpos($firstchars,'<moodle_backup>') !== false and
strpos($firstchars,'<information>') !== false) {
return true;
}
return false;
}
/**
* Converts the given directory with the backup into moodle2 format
*
* @throws coding_exception|restore_controller_exception
* @param string $tempdir The directory to convert
* @param string $format The current format, if already detected
* @return void
*/
public static function to_moodle2_format($tempdir, $format = NULL) {
public static function to_moodle2_format($tempdir, $format = null) {
if (is_null($format)) {
$format = backup_general_helper::detect_backup_format($tempdir);
}
while (!in_array($format, array(backup::FORMAT_MOODLE, backup::FORMAT_UNKNOWN))) {
$converter = convert_factory::converter($format, $tempdir);
if (!$converter->can_convert()) {
throw new coding_exception('Converter detection failed, the loaded converter cannot convert this format');
// get the supported conversion paths from all available converters
$converters = convert_factory::available_converters();
$descriptions = array();
foreach ($converters as $name) {
$classname = "{$name}_converter";
if (!class_exists($classname)) {
throw new coding_exception("available_converters() is supposed to load
converter classes but class $classname not found");
}
$descriptions[$name] = call_user_func($classname .'::description');
}
// choose the best conversion path for the given format
$path = self::choose_conversion_path($format, $descriptions);
if (empty($path)) {
// unable to convert
// todo throwing exception is not a good way to control the flow here
throw new coding_exception('Unable to find conversion path');
}
foreach ($path as $name) {
$converter = convert_factory::converter($name, $tempdir);
$converter->convert();
// Re-detect format
$format = backup_general_helper::detect_backup_format($tempdir);
}
if ($format == backup::FORMAT_UNKNOWN) {
throw new restore_controller_exception('cannot_convert_from_unknown_format'); // @todo Change exception class
// make sure we ended with moodle2 format
if (!self::detect_moodle2_format($tempdir)) {
throw new coding_exception('Conversion failed');
}
}
@ -64,8 +158,6 @@ abstract class convert_helper {
*/
public static function set_inforef($contextid) {
global $DB;
}
public static function get_inforef($contextid) {
@ -122,4 +214,143 @@ abstract class convert_helper {
throw new Exception(sprintf("Could not insert context record into temp table: %s", $msg));
}
}
/// end of public API //////////////////////////////////////////////////////
/**
* Choose the best conversion path for the given format
*
* Given the source format and the list of available converters and their properties,
* this methods picks the most effective way how to convert the source format into
* the target moodle2 format. The method returns a list of converters that should be
* called, in order.
*
* This implementation uses Dijkstra's algorithm to find the shortest way through
* the oriented graph.
*
* @see http://en.wikipedia.org/wiki/Dijkstra's_algorithm
* @param string $format the source backup format, one of backup::FORMAT_xxx
* @param array $descriptions list of {@link base_converter::description()} indexed by the converter name
* @return array ordered list of converter names to call (may be empty if not reachable)
*/
protected static function choose_conversion_path($format, array $descriptions) {
// construct an oriented graph of conversion paths. backup formats are nodes
// and the the converters are edges of the graph.
$paths = array(); // [fromnode][tonode] => converter
foreach ($descriptions as $converter => $description) {
$from = $description['from'];
$to = $description['to'];
$cost = $description['cost'];
if (is_null($from) or $from === backup::FORMAT_UNKNOWN or
is_null($to) or $to === backup::FORMAT_UNKNOWN or
is_null($cost) or $cost <= 0) {
throw new coding_exception('Invalid converter description:' . $converter);
}
if (!isset($paths[$from][$to])) {
$paths[$from][$to] = $converter;
} else {
// if there are two converters available for the same conversion
// path, choose the one with the lowest cost. if there are more
// available converters with the same cost, the chosen one is
// undefined (depends on the order of processing)
if ($descriptions[$paths[$from][$to]]['cost'] > $cost) {
$paths[$from][$to] = $converter;
}
}
}
if (empty($paths)) {
// no conversion paths available
return array();
}
// now use Dijkstra's algorithm and find the shortest conversion path
$dist = array(); // list of nodes and their distances from the source format
$prev = array(); // list of previous nodes in optimal path from the source format
foreach ($paths as $fromnode => $tonodes) {
$dist[$fromnode] = null; // infinitive distance, can't be reached
$prev[$fromnode] = null; // unknown
foreach ($tonodes as $tonode => $converter) {
$dist[$tonode] = null; // infinitive distance, can't be reached
$prev[$tonode] = null; // unknown
}
}
if (!array_key_exists($format, $dist)) {
return array();
} else {
$dist[$format] = 0;
}
$queue = array_flip(array_keys($dist));
while (!empty($queue)) {
// find the node with the smallest distance from the source in the queue
// in the first iteration, this will find the original format node itself
$closest = null;
foreach ($queue as $node => $undefined) {
if (is_null($dist[$node])) {
continue;
}
if (is_null($closest) or ($dist[$node] < $dist[$closest])) {
$closest = $node;
}
}
if (is_null($closest) or is_null($dist[$closest])) {
// all remaining nodes are inaccessible from source
break;
}
if ($closest === backup::FORMAT_MOODLE) {
// bingo we can break now
break;
}
unset($queue[$closest]);
// visit all neighbors and update distances to them eventually
if (!isset($paths[$closest])) {
continue;
}
$neighbors = array_keys($paths[$closest]);
// keep just neighbors that are in the queue yet
foreach ($neighbors as $ix => $neighbor) {
if (!array_key_exists($neighbor, $queue)) {
unset($neighbors[$ix]);
}
}
foreach ($neighbors as $neighbor) {
// the alternative distance to the neighbor if we went thru the
// current $closest node
$alt = $dist[$closest] + $descriptions[$paths[$closest][$neighbor]]['cost'];
if (is_null($dist[$neighbor]) or $alt < $dist[$neighbor]) {
// we found a shorter way to the $neighbor, remember it
$dist[$neighbor] = $alt;
$prev[$neighbor] = $closest;
}
}
}
if (is_null($dist[backup::FORMAT_MOODLE])) {
// unable to find a conversion path, the target format not reachable
return array();
}
// reconstruct the optimal path from the source format to the target one
$conversionpath = array();
$target = backup::FORMAT_MOODLE;
while (isset($prev[$target])) {
array_unshift($conversionpath, $paths[$prev[$target]][$target]);
$target = $prev[$target];
}
return $conversionpath;
}
}

View File

@ -0,0 +1,146 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Unit tests for backup/util/helper/convert_helper.class.php
*
* @package core
* @subpackage backup-convert
* @copyright 2011 David Mudrak <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/backup/util/includes/convert_includes.php');
/**
* Provides access to the protected methods we need to test
*/
class testable_convert_helper extends convert_helper {
public static function choose_conversion_path($format, array $descriptions) {
return parent::choose_conversion_path($format, $descriptions);
}
}
/**
* Defines the test methods
*/
class convert_helper_test extends UnitTestCase {
public static $includecoverage = array();
public function test_choose_conversion_path() {
// no converters available
$descriptions = array();
$path = testable_convert_helper::choose_conversion_path(backup::FORMAT_MOODLE1, $descriptions);
$this->assertEqual($path, array());
// missing source and/or targets
$descriptions = array(
// some custom converter
'exporter' => array(
'from' => backup::FORMAT_MOODLE1,
'to' => 'some_custom_format',
'cost' => 10,
),
// another custom converter
'converter' => array(
'from' => 'yet_another_crazy_custom_format',
'to' => backup::FORMAT_MOODLE,
'cost' => 10,
),
);
$path = testable_convert_helper::choose_conversion_path(backup::FORMAT_MOODLE1, $descriptions);
$this->assertEqual($path, array());
$path = testable_convert_helper::choose_conversion_path('some_other_custom_format', $descriptions);
$this->assertEqual($path, array());
// single step conversion
$path = testable_convert_helper::choose_conversion_path('yet_another_crazy_custom_format', $descriptions);
$this->assertEqual($path, array('converter'));
// no conversion needed - this is supposed to be detected by the caller
$path = testable_convert_helper::choose_conversion_path(backup::FORMAT_MOODLE, $descriptions);
$this->assertEqual($path, array());
// two alternatives
$descriptions = array(
// standard moodle 1.9 -> 2.x converter
'moodle1' => array(
'from' => backup::FORMAT_MOODLE1,
'to' => backup::FORMAT_MOODLE,
'cost' => 10,
),
// alternative moodle 1.9 -> 2.x converter
'alternative' => array(
'from' => backup::FORMAT_MOODLE1,
'to' => backup::FORMAT_MOODLE,
'cost' => 8,
)
);
$path = testable_convert_helper::choose_conversion_path(backup::FORMAT_MOODLE1, $descriptions);
$this->assertEqual($path, array('alternative'));
// complex case
$descriptions = array(
// standard moodle 1.9 -> 2.x converter
'moodle1' => array(
'from' => backup::FORMAT_MOODLE1,
'to' => backup::FORMAT_MOODLE,
'cost' => 10,
),
// alternative moodle 1.9 -> 2.x converter
'alternative' => array(
'from' => backup::FORMAT_MOODLE1,
'to' => backup::FORMAT_MOODLE,
'cost' => 8,
),
// custom converter from 1.9 -> custom 'CFv1' format
'cc1' => array(
'from' => backup::FORMAT_MOODLE1,
'to' => 'CFv1',
'cost' => 2,
),
// custom converter from custom 'CFv1' format -> moodle 2.0 format
'cc2' => array(
'from' => 'CFv1',
'to' => backup::FORMAT_MOODLE,
'cost' => 5,
),
// custom converter from CFv1 -> CFv2 format
'cc3' => array(
'from' => 'CFv1',
'to' => 'CFv2',
'cost' => 2,
),
// custom converter from CFv2 -> moodle 2.0 format
'cc4' => array(
'from' => 'CFv2',
'to' => backup::FORMAT_MOODLE,
'cost' => 2,
),
);
// ask the helper to find the most effective way
$path = testable_convert_helper::choose_conversion_path(backup::FORMAT_MOODLE1, $descriptions);
$this->assertEqual($path, array('cc1', 'cc3', 'cc4'));
}
}

View File

@ -48,7 +48,7 @@ class convert_plan extends base_plan implements loggable {
}
public function get_basepath() {
return $this->converter->get_convertdir();
return $this->converter->get_workdir_path();
}
/**

View File

@ -30,6 +30,7 @@ defined('MOODLE_INTERNAL') || die();
* Convert forum
*/
class moodle1_forum_activity_structure_step extends convert_structure_step {
/**
* Function that will return the structure to be processed by this convert_step.
* Must return one array of @convert_path_element elements