MDL-46455 backup: Extend support for subplugins to any plugin.

In order to implement the backup and restore of log stores, that
are created as subplugins of the tool_log plugin , we need to
extend subplugins support from activities to virtually any plugin.

Basically that implies moving the add_subplugin_structure() method from
its current, restricted, activity level to general backup_structure_step.

This commit implements the change in backup, covered with tests verifying
old, bc behavior and also new, general one.
This commit is contained in:
Eloy Lafuente (stronk7) 2015-03-08 19:38:44 +01:00 committed by Mark Nelson
parent 74fad2ce3d
commit ba66edd074
4 changed files with 212 additions and 58 deletions

View File

@ -91,64 +91,10 @@ class create_taskbasepath_directory extends backup_execution_step {
/**
* Abstract structure step, parent of all the activity structure steps. Used to wrap the
* activity structure definition within the main <activity ...> tag. Also provides
* subplugin support for activities (that must be properly declared)
* activity structure definition within the main <activity ...> tag.
*/
abstract class backup_activity_structure_step extends backup_structure_step {
/**
* Add subplugin structure to any element in the activity backup tree
*
* @param string $subplugintype type of subplugin as defined in activity db/subplugins.php
* @param backup_nested_element $element element in the activity backup tree that
* we are going to add subplugin information to
* @param bool $multiple to define if multiple subplugins can produce information
* for each instance of $element (true) or no (false)
* @return void
*/
protected function add_subplugin_structure($subplugintype, $element, $multiple) {
global $CFG;
// Check the requested subplugintype is a valid one
$subpluginsfile = $CFG->dirroot . '/mod/' . $this->task->get_modulename() . '/db/subplugins.php';
if (!file_exists($subpluginsfile)) {
throw new backup_step_exception('activity_missing_subplugins_php_file', $this->task->get_modulename());
}
include($subpluginsfile);
if (!array_key_exists($subplugintype, $subplugins)) {
throw new backup_step_exception('incorrect_subplugin_type', $subplugintype);
}
// Arrived here, subplugin is correct, let's create the optigroup
$optigroupname = $subplugintype . '_' . $element->get_name() . '_subplugin';
$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 subplugin dirs
$subpluginsdirs = core_component::get_plugin_list($subplugintype);
foreach ($subpluginsdirs as $name => $subpluginsdir) {
$classname = 'backup_' . $subplugintype . '_' . $name . '_subplugin';
$backupfile = $subpluginsdir . '/backup/moodle2/' . $classname . '.class.php';
if (file_exists($backupfile)) {
require_once($backupfile);
$backupsubplugin = new $classname($subplugintype, $name, $optigroup, $this);
// Add subplugin returned structure to optigroup
$backupsubplugin->define_subplugin_structure($element->get_name());
}
}
}
/**
* As far as activity backup steps are implementing backup_subplugin stuff, they need to
* have the parent task available for wrapping purposes (get course/context....)
*
* @return backup_activity_task
*/
public function get_task() {
return $this->task;
}
/**
* Wraps any activity backup structure within the common 'activity' element
* that will include common to all activities information like id, context...

View File

@ -162,6 +162,83 @@ abstract class backup_structure_step extends backup_step {
}
}
/**
* Add subplugin structure for a given plugin to any element in the structure backup tree.
*
* This method allows the injection of subplugins (of a specified plugin) data to any
* element in any backup structure.
*
* NOTE: Initially subplugins were only available for activities (mod), so only the
* {@link backup_activity_structure_step} class had support for them, always
* looking for /mod/modulenanme subplugins. This new method is a generalization of the
* existing one for activities, supporting all subplugins injecting information everywhere.
*
* @param string $subplugintype type of subplugin as defined in plugin's db/subplugins.php.
* @param backup_nested_element $element element in the backup tree (anywhere) that
* we are going to add subplugin information to.
* @param bool $multiple to define if multiple subplugins can produce information
* for each instance of $element (true) or no (false).
* @param string $plugintype type of the plugin.
* @param string $pluginname name of the plugin.
* @return void
*/
protected function add_subplugin_structure($subplugintype, $element, $multiple, $plugintype = null, $pluginname = null) {
// Verify if this is a BC call for an activity backup. See NOTE above for this special case.
if ($plugintype === null and $pluginname === null) {
$plugintype = 'mod';
$pluginname = $this->task->get_modulename();
// TODO: Once all the calls have been changed to add both not null plugintype and pluginname, add a debugging here.
}
// Check the requested plugintype is a valid one.
if (!array_key_exists($plugintype, core_component::get_plugin_types())) {
throw new backup_step_exception('incorrect_plugin_type', $plugintype);
}
// Check the requested pluginname, for the specified plugintype, is a valid one.
if (!array_key_exists($pluginname, core_component::get_plugin_list($plugintype))) {
throw new backup_step_exception('incorrect_plugin_name', array($plugintype, $pluginname));
}
// Check the requested subplugintype is a valid one.
$subpluginsfile = core_component::get_component_directory($plugintype . '_' . $pluginname) . '/db/subplugins.php';
if (!file_exists($subpluginsfile)) {
throw new backup_step_exception('plugin_missing_subplugins_php_file', array($plugintype, $pluginname));
}
include($subpluginsfile);
if (!array_key_exists($subplugintype, $subplugins)) {
throw new backup_step_exception('incorrect_subplugin_type', $subplugintype);
}
// Arrived here, subplugin is correct, let's create the optigroup.
$optigroupname = $subplugintype . '_' . $element->get_name() . '_subplugin';
$optigroup = new backup_optigroup($optigroupname, null, $multiple);
$element->add_child($optigroup); // Add optigroup to stay connected since beginning.
// Every subplugin optionally can have a common/parent subplugin
// class for shared stuff.
$parentclass = 'backup_' . $plugintype . '_' . $pluginname . '_' . $subplugintype . '_subplugin';
$parentfile = core_component::get_component_directory($plugintype . '_' . $pluginname) .
'/backup/moodle2/' . $parentclass . '.class.php';
if (file_exists($parentfile)) {
require_once($parentfile);
}
// Get all the optigroup_elements, looking over all the subplugin dirs.
$subpluginsdirs = core_component::get_plugin_list($subplugintype);
foreach ($subpluginsdirs as $name => $subpluginsdir) {
$classname = 'backup_' . $subplugintype . '_' . $name . '_subplugin';
$backupfile = $subpluginsdir . '/backup/moodle2/' . $classname . '.class.php';
if (file_exists($backupfile)) {
require_once($backupfile);
$backupsubplugin = new $classname($subplugintype, $name, $optigroup, $this);
// Add subplugin returned structure to optigroup.
$backupsubplugin->define_subplugin_structure($element->get_name());
}
}
}
/**
* To conditionally decide if one step will be executed or no
*
@ -175,8 +252,9 @@ abstract class backup_structure_step extends backup_step {
}
/**
* Function that will return the structure to be processed by this backup_step.
* Must return one backup_nested_element
* Define the structure to be processed by this backup step.
*
* @return backup_nested_element
*/
abstract protected function define_structure();
}

View File

@ -61,6 +61,9 @@ class mock_backup_step extends backup_step {
*/
class mock_backup_task_basepath extends backup_task {
/** @var string name of the mod plugin (activity) being used in the tests */
private $modulename;
public function build() {
// Nothing to do
}
@ -73,6 +76,14 @@ class mock_backup_task_basepath extends backup_task {
global $CFG;
return $CFG->tempdir . '/test';
}
public function set_modulename($modulename) {
$this->modulename = $modulename;
}
public function get_modulename() {
return $this->modulename;
}
}
/**
@ -80,7 +91,7 @@ class mock_backup_task_basepath extends backup_task {
*/
class mock_backup_structure_step extends backup_structure_step {
protected function define_structure() {
public function define_structure() {
// Create really simple structure (1 nested with 1 attr and 2 fields)
$test = new backup_nested_element('test',
@ -91,6 +102,14 @@ class mock_backup_structure_step extends backup_structure_step {
return $test;
}
public function add_plugin_structure($plugintype, $element, $multiple) {
parent::add_plugin_structure($plugintype, $element, $multiple);
}
public function add_subplugin_structure($subplugintype, $element, $multiple, $plugintype = null, $pluginname = null) {
parent::add_subplugin_structure($subplugintype, $element, $multiple, $plugintype, $pluginname);
}
}
/**

View File

@ -134,6 +134,117 @@ class backup_step_testcase extends advanced_testcase {
@remove_dir(dirname($file));
}
/**
* Verify the add_plugin_structure() backup method behavior and created structures.
*/
public function test_backup_structure_step_add_plugin_structure() {
// Create mocked task, step and element.
$bt = new mock_backup_task_basepath('taskname');
$bs = new mock_backup_structure_step('steptest', null, $bt);
$el = new backup_nested_element('question', array('id'), array('one', 'two', 'qtype'));
// Wrong plugintype.
try {
$bs->add_plugin_structure('fakeplugin', $el, true);
$this->assertTrue(false, 'base_step_exception expected');
} catch (exception $e) {
$this->assertTrue($e instanceof backup_step_exception);
$this->assertEquals('incorrect_plugin_type', $e->errorcode);
}
// Correct plugintype qtype call (@ 'question' level).
$bs->add_plugin_structure('qtype', $el, false);
$ch = $el->get_children();
$this->assertEquals(1, count($ch));
$og = reset($ch);
$this->assertTrue($og instanceof backup_optigroup);
$ch = $og->get_children();
$this->assertTrue(array_key_exists('optigroup_qtype_calculatedsimple_question', $ch));
$this->assertTrue($ch['optigroup_qtype_calculatedsimple_question'] instanceof backup_plugin_element);
}
/**
* Verify the add_subplugin_structure() backup method behavior and created structures.
*/
public function test_backup_structure_step_add_subplugin_structure() {
// Create mocked task, step and element.
$bt = new mock_backup_task_basepath('taskname');
$bs = new mock_backup_structure_step('steptest', null, $bt);
$el = new backup_nested_element('workshop', array('id'), array('one', 'two', 'qtype'));
// Wrong plugin type.
try {
$bs->add_subplugin_structure('fakesubplugin', $el, true, 'fakeplugintype', 'fakepluginname');
$this->assertTrue(false, 'base_step_exception expected');
} catch (exception $e) {
$this->assertTrue($e instanceof backup_step_exception);
$this->assertEquals('incorrect_plugin_type', $e->errorcode);
}
// Wrong plugin type.
try {
$bs->add_subplugin_structure('fakesubplugin', $el, true, 'mod', 'fakepluginname');
$this->assertTrue(false, 'base_step_exception expected');
} catch (exception $e) {
$this->assertTrue($e instanceof backup_step_exception);
$this->assertEquals('incorrect_plugin_name', $e->errorcode);
}
// Wrong plugin not having subplugins.
try {
$bs->add_subplugin_structure('fakesubplugin', $el, true, 'mod', 'page');
$this->assertTrue(false, 'base_step_exception expected');
} catch (exception $e) {
$this->assertTrue($e instanceof backup_step_exception);
$this->assertEquals('plugin_missing_subplugins_php_file', $e->errorcode);
}
// Wrong BC (defaulting to mod and modulename) use not having subplugins.
try {
$bt->set_modulename('page');
$bs->add_subplugin_structure('fakesubplugin', $el, true);
$this->assertTrue(false, 'base_step_exception expected');
} catch (exception $e) {
$this->assertTrue($e instanceof backup_step_exception);
$this->assertEquals('plugin_missing_subplugins_php_file', $e->errorcode);
}
// Wrong subplugin type.
try {
$bs->add_subplugin_structure('fakesubplugin', $el, true, 'mod', 'workshop');
$this->assertTrue(false, 'base_step_exception expected');
} catch (exception $e) {
$this->assertTrue($e instanceof backup_step_exception);
$this->assertEquals('incorrect_subplugin_type', $e->errorcode);
}
// Wrong BC subplugin type.
try {
$bt->set_modulename('workshop');
$bs->add_subplugin_structure('fakesubplugin', $el, true);
$this->assertTrue(false, 'base_step_exception expected');
} catch (exception $e) {
$this->assertTrue($e instanceof backup_step_exception);
$this->assertEquals('incorrect_subplugin_type', $e->errorcode);
}
// Correct call to workshopform subplugin (@ 'workshop' level).
$bs->add_subplugin_structure('workshopform', $el, true, 'mod', 'workshop');
$ch = $el->get_children();
$this->assertEquals(1, count($ch));
$og = reset($ch);
$this->assertTrue($og instanceof backup_optigroup);
$ch = $og->get_children();
$this->assertTrue(array_key_exists('optigroup_workshopform_accumulative_workshop', $ch));
$this->assertTrue($ch['optigroup_workshopform_accumulative_workshop'] instanceof backup_subplugin_element);
// Correct BC call to workshopform subplugin (@ 'assessment' level).
$el = new backup_nested_element('assessment', array('id'), array('one', 'two', 'qtype'));
$bt->set_modulename('workshop');
$bs->add_subplugin_structure('workshopform', $el, true);
$ch = $el->get_children();
$this->assertEquals(1, count($ch));
$og = reset($ch);
$this->assertTrue($og instanceof backup_optigroup);
$ch = $og->get_children();
$this->assertTrue(array_key_exists('optigroup_workshopform_accumulative_assessment', $ch));
$this->assertTrue($ch['optigroup_workshopform_accumulative_assessment'] instanceof backup_subplugin_element);
// TODO: Add some test covering a non-mod subplugin once we have some implemented in core.
}
/**
* wrong base_step class tests
*/