Backup 2.0 for developers
Moodle 2.0
Note: This page is a work-in-progress. Feedback and suggested improvements are welcome. Please join the discussion on moodle.org or use the page comments.
Introduction
This page tries to explain, from a development perspective, how to implement the backup feature for various Moodle 2.x plugins, mainly, modules and blocks.
Note that, at the time of writing this, the backup & restore subsystem itself is under development, so still there are some missing bits, specially about the way to handle subplugins (question types, data fields...) under the new backup infrastructure, so any module using such artifacts, like data, assignment, quiz, workshop, aren't good candidates right now. This will be addressed (and this Docs updated) once we have determined how each subplugin is expected to work (from a DB / backup perspective).
Everything in backup is about tasks and steps (see Backup 2.0 general architecture for more information), all them conforming one backup plan. Each module instance and each block instance will be backup by one backup task instance that is, basically, one collection of backup steps. How steps are organized within the task will dictate to the backup system what to do and in which order.
Another important point is that Moodle backup 2.0, supports multiple backup formats ("moodle2", "imscc"...) each one having its own tasks/steps, completely unrelated between them. In any case, for now, we are focusing all the explanations below in the "moodle2" format, that is the one required in order to keep any module/block transportable between Moodle instances without any loss of data. Surely, for the rest of formats, we'll end with other documents describing them, since each one can have its own particularities.
Talking about backup steps we must differentiate two type of steps:
- Execution steps: That, simply, execute arbitrary PHP code. They are useful to prepare different structures, create directories, whatever have to be done not involving the generation of XML files. Normally you won't need them.
- Structure steps: That, using one PHP API (detailed below) define completely the XML structure to be exported and its contents. Hopefully, "normal" modules only will have to use one step of this type in order to have the backup functionality 100% implemented.
Said that, in the next steps we are going through all the steps necessary to create the backup of one simple module and one block, in order to get all the possibilities covered.
How to backup one module
For this section, we have selected one simple module (choice) that requires practically all the backup "machinery" to be used, so it will get explained as we progress in the development. The only point not covered here are the "subplugin" facilities, covered later in a separate section.
Prerequisites
In order to achieve the backup implementation for the module, some prerequisites should be fulfilled. They are default recommendations that are especially helpful while getting used to the backup process.
- Learn about the module. If you aren't the creator of the module, spend some time playing with the module, creating and using it, exploring each one of its functionalities. By doing this you will end with some "real" data in the module instances that will be really useful when testing / debugging how the module backup is being generated.
- Draw one schema of the module DB structures. While you are playing with the module, look continuously to the DB, how records are saved and what the relationships are between the module's tables. Since a backup is, basically, one "selective dump" of those tables, knowing the maximum about them is highly recommended. At the end, you must end with one tree structure will be the basis for the generated XML file.
- Annotate which tables contain user-related info and which ones don't. One of the core functionalities that must be present on each module is the ability to include user related information or skip it, as you will need that information later.
Tip: If the module already existed before Moodle 2.0, it can be a good idea to look at its 1.9 backuplib.php file, since the structure is already defined there and can help to understand the organization better and, at the same time, keeping the XML structure as similar as possible, that will, definitively, make things easier when converting 1.9 backup files to the new backup 2.0 format.
Note: It's important to highlight that the structure schema, should be as stable as possible moving forward, because any change in the structure makes restore much more complex to implement. There aren't problems with adding/deleting fields, nor adding new elements to the structure. The structure (tree) itself must persist as stable as possible, so be careful when deciding it, as it will have implications on future maintenance.
Applying these pre-requisites to our candidate module (choice), here it's the corresponding schema. We'll use it along the whole process.
Schema
In the schema, you must try to put as much information as possible, so that will produce the coding process later to be quicker and easier while keeping the final results free from errors and missing bits. So, once more, don't start coding immediately, instead spend some time with the requisites above, understanding how the module works and designing the final structure that represents it better.
The (correct) candidate
The tree on the left shows the ER/DB structure of the choice module, where one choice have one or more options and each option is answered by users one or more times (note: ignore cardinality accuracy in the previous phrase).
And that's the best schema representing the structure of the module, with each element properly nested so, we won't have any problem with restore since the order required by restore (1, 2, 3) are naturally given.
Looks easy, cool, but continue reading… you will get surprised! :-)
The (chosen) candidate
Instead we are going to use the (complete this time) diagram below. See the rationale about that after the image.
The main reason to use this tree (instead of the "correct" one) is that this is the structure that has been used by Moodle 1.9 backup since ages ago for the choice module and, of course have done its work ok. As commented above we must try to reduce the number of structural changes in one module backup in order to keep the restore operations working along the time (the same is applicable for the conversion of 1.9 backup files to the new 2.0 format). Finally, this is a good example about how one module can have different XML representations and we need to try to get that best one (this is not the case) on each case. So, once more, spending some time analyzing the activity is worth it.
Let's analyze the schema with some detail:
- Detecting user information: We must be able to define the entities (tables) in the diagram that are used to store user information. One of the core features of the backup subsystem since its early days have been the ability to produce backup with and without user information. So we need that info. Hence, the "no user info" in the choice and choice_options elements (they are configuration, user-independent), while the choice_answers is marked as "user info" (contains user's answers to the choice).
- Determining the correct order of backup: This, while simple, is critical too (especially from a restore perspective). Since the restore reads progressively the xml file and performs actions in that order, we must guarantee that the "read order" is the correct one, fulfilling any possible dependency. Back to our schema it's clear that we need to backup the "choice" element at first, and then the "choice_options" and "choice_answers" ones. Moreover, the "choice_options" must be backup before "choice_answers", since the later needs to save the values of the former (the "optionid" information). Note that, as commented some paragraphs above, the "correct" alternative really gave us that order information easily. Doesn't matter, since we have been able to establish it also in the "chosen" schema.
- Attributes and elements: Now it's time to decide which fields will be considered attributes in the resulting XML file and which ones will be child elements (tags). The rule is simple: All the "id" fields must (should) be defined as attributes. Note this is just one arbitrary rule without much rationale behind it since, from a restore perspective, everything (attributes and child tags) will be handled in the same way (object attributes). So, in our schema, all the "id" fields have been marked as "attr".
- Not needed elements: If we have designed the schema properly, we'll find that some fields aren't necessary, since such information is already included in some parent element. In our schema, the field "choiceid", pointing to the "choice->id", both in the options and in the answer elements, have been marked as "not needed" since their parent "choice" already contains it. Something similar happens with the "course" field in the choice element. It doesn't need to be included in the backup file since something above it (course element, outside the module scope) already has it defined. Finally, there is one element marked as "needed" that shows us, once more, that the schema we are using is not the best. Since "choice_answers" isn't nested under "choice_options", we must keep the "choice_answers->optionid" field in the backup, or restore won't know to which option each answer belongs to. Instead, with the "correct" schema, where answers are nested under options, that field is not needed. Summarizing, any field except those already existing in parent elements must be included in backup.
- Detecting file areas used by the module: Along the module we can be using various file areas in different elements and fields. We need to know exactly which file area is handled by which element and the (optional) itemid information used for that file area. In general, anything being one text field, or anything looking like one attachment has high chances to have one (hidden) file area associated. In our schema, we have one file area (choice_intro) corresponding to the introduction of the module and available to put any images or whatever in that field. Also, in general, all the "xxx_intro" file areas use to have no itemid, since the module's context is enough to define them without ambiguities. So, we mark the choice->intro as "choice_intro" file area and "no itemid"). From our expertise playing with the module we know there are no more file areas associated with this module.
- Annotating some important bits: Due to the modularity of the backup and in order to know exactly which information must be saved (because it's used) and which one can be skipped, it's important to annotate some important elements along the whole backup process. So, back to our schema, we have marked the "choice_answer->userid" field as "annotation" (so backup will, automatically, add all the information for that user). Here it's the list of elements that we must not forget to annotate (or we could end with non-restorable backups). Note that we must, always, be annotating "id" values and not other types of data:
- user: Any field pointing to one user->id present along the schema (as said above, our schema has one).
- grouping: Any field pointing to one grouping->id
- group: Any field pointing to one group->id
- role: Any field pointing to one role->id
- scale: Any field pointing to one scale->id
- outcome: Any field pointing to one outcome->id
And this is all the information we need to know, before starting to code. Surely, once used to backup and restore, you will be able to start coding sooner, but don't forget about the importance of choosing one good and stable structure before anything else. It's really the critical part of any module's backup.
That said, let's see how to code all this information in order to get one cool backup for our beloved module.
Coding
Already here? Have you read, at least once, all the explanations and comments in the previous sections? Yes? Sure? I can imagine it's hard to read so much text, just imagine how hard is to write it! Go, go, go and read it!
Jokes aside, in this section we are going to code the module's (choice, in our example) backup code. As you'll see soon, all the information gathered in previous steps is really important and will make the coding task easier and less prone to errors.
Setting up the environment
By default, any backup operation will end with one .zip file stored in some course / section / activity area. That means that, each time you execute one backup, you'll need to go across the web interface to that file area, download the generated .zip file, uncompress it, and then see how things have been generated. And you will be executing a bunch of backup files until you get everything working as expected, so the whole develop / test process isn't really "agile". To improve things a bit, there is one $CFG setting that you should consider using in your development environment. Just put this in your config.php:
$CFG->keeptempdirectoriesonbackup = true;
With this setting enabled, backup won't delete the temporary directory where everything is calculated, so you will be able to access to it directly, skipping all the ui / download / unzip steps above. Those temp directories are under $CFG->dataroot/temp/backup. Note that, for each backup invocation, a new directory is created.
Also, in order to be able to execute backups quickly (instead of navigating along the UI), you can, simply, put one script like this in your $CFG->dirroot directory:
<?php
define('CLI_SCRIPT', 1);
require_once('config.php');
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
$course_module_to_backup = XX; // Set this to one existing choice cmid in your dev site
$user_doing_the_backup = YY; // Set this to the id of your admin account
$bc = new backup_controller(backup::TYPE_1ACTIVITY, $course_module_to_backup, backup::FORMAT_MOODLE,
backup::INTERACTIVE_NO, backup::MODE_GENERAL, $user_doing_the_backup);
$bc->execute_plan();
Just set proper values for XX and YY above and execute it from the command line. If you get one error about one not found class (with the name of your module) everything is ok.
To get the analogous restore code please see: https://docs.moodle.org/dev/Restore_2.0_for_developers#Automatically_triggering_restore_in_code
Required stuff
The first thing you need to make is to, explicitly, declare that your module (choice in our example) is going to support the MOODLE2 backup format, to do so, you need to go to mod/choice/lib.php and, in the choice_supports() function add one new feature by adding this line:
case FEATURE_BACKUP_MOODLE2: return true;
If you execute another backup (with the script provided above, you will continue getting one error, since we haven't still coded anything related to backup, that's ok.
Next step is to create the directory where all the backup code for the choice module will be. Just create the directory(s) mod/choice/backup/moodle2
At this point, we have all the required stuff ready and all the pending tasks will be done under that recently created directory.
Settings, Steps and Tasks
In the introduction of the tutorial, we commented about backup being structured into tasks, each one being one collection of steps (and potentially using some custom settings). Those are, exactly, the objects that we need to create to achieve the backup functionality in our module.
First of all, lets' create the settings file, where all the custom settings to be used by our module will be defined and implemented. It must be named mod/choice/backup/moodle2/backup_choice_settingslib.php and their contents are really meaningful:
<?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/>.
/**
* @package moodlecore
* @subpackage backup-moodle2
* @copyright 2010 onwards YOUR_NAME_GOES_HERE {@link YOUR_URL_GOES_HERE}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
// This activity has not particular settings but the inherited from the generic
// backup_activity_task so here there isn't any class definition, like the ones
// existing in /backup/moodle2/backup_settingslib.php (activities section)
Looks simple, isn't it? Nothing but a few comments. This is looking really easy. For now, we aren't going to introduce any custom setting for any module, only the "core activity settings" will be used. Once backup and Moodle 2.0 is stable we can start analyzing useful settings to customize how the module's backup is performed. For now, just leave it blank, please. In fact, if it's empty, you can safely not create it at all. It's here just for explanation purposes.
Side note: To save some space, we only will be showing the © message/license in the code above. Just add it to each file created.
Now we are going to create the steps file, where all the steps to be executed by our backup activity task will be defined and implemented. It must be named mod/choice/backup/moodle2/backup_choice_stepslib.php and their contents are these:
<?php
/**
* Define all the backup steps that will be used by the backup_choice_activity_task
*/
Yes, it's another empty file. No worries we'll fill it later.
Finally we need to create the task file, where our just created settings and steps files will be included and used. It must be named mod/choice/backup/moodle2/backup_choice_activity_task.class.php and their contents are these:
<?php
require_once($CFG->dirroot . '/mod/choice/backup/moodle2/backup_choice_stepslib.php'); // Because it exists (must)
require_once($CFG->dirroot . '/mod/choice/backup/moodle2/backup_choice_settingslib.php'); // Because it exists (optional)
/**
* choice backup task that provides all the settings and steps to perform one
* complete backup of the activity
*/
class backup_choice_activity_task extends backup_activity_task {
/**
* Define (add) particular settings this activity can have
*/
protected function define_my_settings() {
// No particular settings for this activity
}
/**
* Define (add) particular steps this activity can have
*/
protected function define_my_steps() {
// Choice only has one structure step
}
/**
* Code the transformations to perform in the activity in
* order to get transportable (encoded) links
*/
static public function encode_content_links($content) {
return $content;
}
}
This is the main file (class) of the choice backup and it will be used to define any setting (define_my_settings() method) and any step (define_my_steps() method) to be executed in the backup process. It also contains one 3rd method (encode_content_links($content)) that will allow manually URLs pointing to the choice to be properly converted when the choice is moved to other site / course using the backup / restore functionality.
As you see, for now, the three required methods are doing nothing. Just execute the backup again. Wow, no errors anymore. We have already fulfilled all the minimum coding needs in order to have the choice backup working.
Now you should go to your $CFG->dataroot/temp/backup/xxxx directory (the more recent) and spend some time looking what has been created under the activities/choice_XX directory. There is already a lot of stuff that backup has generated for you: comments, blocks, logs, grades, module info, roles... some of them empty and others already showing real information.
In any case, now, we need to add one important file there, the "choice.xml" where the real information for your module will be present, following the specs given by the schema we decided some sections above.
So, let's go, it's time to create our choice structure step. To do so, we edit the mod/choice/backup/moodle2/backup_choice_stepslib.php file and add this code after the existing comments:
/**
* Define the complete choice structure for backup, with file and id annotations
*/
class backup_choice_activity_structure_step extends backup_activity_structure_step {
protected function define_structure() {
// To know if we are including userinfo
$userinfo = $this->get_setting_value('userinfo');
// Define each element separated
// Build the tree
// Define sources
// Define id annotations
// Define file annotations
// Return the root element (choice), wrapped into standard activity structure
}
}
So, we have defined one structure step that will be the responsible, using one PHP API to provide backup with all the information needed to generate the choice.xml file. Now, go back to the task file and add these contents in the define_my_steps() methods:
$this->add_step(new backup_choice_activity_structure_step('choice_structure', 'choice.xml'));
That way, our choice task knows it must execute one new step, the one involving the generation of the choice.xml file. Let's execute one new backup and see results.You should be getting one new error with something like that "backup_structure_step_wrong_structure". It means that everything is ok up to now, the task has tried to execute the structure step, but this is not properly defined.
Defining each element
So, next step is about to define the choice structure. Going back to our step, under the // Define each element separate comment we'll add this code:
$choice = new backup_nested_element('choice', array('id'), array(
'name', 'intro', 'introformat', 'publish',
'showresults', 'display', 'allowupdate', 'allowunanswered',
'limitanswers', 'timeopen', 'timeclose', 'timemodified'));
$options = new backup_nested_element('options');
$option = new backup_nested_element('option', array('id'), array(
'text', 'maxanswers', 'timemodified'));
$answers = new backup_nested_element('answers');
$answer = new backup_nested_element('answer', array('id'), array(
'userid', 'optionid', 'timemodified'));
Return the root element
And also, in order to get it working, let's define the return element, so, after the // Return the root element comment, add this code:
return $this->prepare_activity_structure($choice);
Now we can execute the backup again and, as long as we have already defined the root element of the module, should have now one choice.xml file created (without proper contents but must be there).
About the code above, all we have done is to define, separately each element that will be part of the choice.xml file. Special note about the $options and $answers elements. Since Moodle 1.9 we use to enclose real elements (the singular ones) inside one extra plural element, hence we create them here (as empty elements without attributes nor child tags). About the rest (the singular ones) we use 3 params in the instantiation:
- The name of the element
- One array of attributes of the element
- One array of child tags (fields) of the element.
And we include all the fields that previously we had defined in our schema but the ones marked as not needed.
Building the tree
Now it's time to declare the relations between all those elements, so, after the // Build the tree comment we'll add this code:
$choice->add_child($options);
$options->add_child($option);
$choice->add_child($answers);
$answers->add_child($answer);
Self explanatory, using $choice as root element, we define the whole tree using the add_child() method. And, of course, we respect the order that we had decided, so $options will be physically before $answers in the tree (and in the generated XML).
Defining the sources
After this, it's the moment to instruct our tree about how to fetch information from DB in order to generate the choice.xml file, so after the // Define sources comment, we'll add this code:
$choice->set_source_table('choice', array('id' => backup::VAR_ACTIVITYID));
$option->set_source_sql('
SELECT *
FROM {choice_options}
WHERE choiceid = ?',
array(backup::VAR_PARENTID));
// All the rest of elements only happen if we are including user info
if ($userinfo) {
$answer->set_source_table('choice_answers', array('choiceid' => '../../id'));
}
So we define the source for the $choice element as the information present in the 'choice' table for the id that is being backup (backup::VAR_ACTIVITYID). Or we define the source for the $option element like one SQL (could have been one table too, just to show more possibilities of the API, using the parent (the choice one) as value for the query (backup::VAR_PARENTID).
And finally, conditionally, if user information is going to be included, we define the source for the $answer (note we had already annotated in our schema that this element was dependent of that).
And we define the source condition as having the 'choiceid' field matching the value of the 'id' field two levels above (../../id). If you follow the tree created in the previous code section, that's exactly the choice->id (remember we have introduced one extra level (the plural one between the 'choice' and the 'answer' singular elements). Note this is 100% equivalent to use backup::VAR_PARENTID (as we have done in the $option source) just used to show possibilities of the API.
Summarizing, with the API, we have these 3 method for defining sources:
- set_source_table($tablename, array $conditions): When the information is get straight from one table (vast majority of cases in backup)
- set_source_sql($sql, array $params): When the information doesn't map one table directly and we need something more complex
- set_source_array($array): When we have some fixed information to backup. Not really useful in nested information, but used by core here and there.
Note that both the $conditions and $params above only accept one limited number of constants or values (not all available in all backups!), mainly:
- backup::VAR_COURSEID: The id of the course this activity belongs to / the course id being backup
- backup::VAR_SECTIONID: The id of the section this activity belongs to / the section id being backup
- backup::VAR_ACTIVITYID: The id of the activity id being backup
- backup::VAR_MODID: The id of the course_module being backup
- backup::VAR_MODULENAME: The name of the module being backup
- backup::VAR_BLOCKID: The id of the block being backup
- backup::VAR_BLOCKNAME: The name of the block being backup
- backup::VAR_CONTEXTID: The context id of the activity / course being backup
- backup::VAR_PARENTID: The value of the first parent id found in the structure.
- ../some/path/to/parent: To manually point to other value of any parent.
Is important to note that, in 99% of the case we should be using, exclusively, some of the constants above, and backup, automatically will handle them properly. In case we have any other condition or param to be added like, for example, the number 23, or the string 'user', we cannot add them directly, but enclose them with the helper backup_helper::is_sqlparam(23) or backup_helper::is_sqlparam('user'). That way backup will know they are raw SQL params not needing any special handling, like the constants above.
Well, now it's time to run backup again and take a look to our activities/choice_XX/choice.xml file. Now everything should be there, properly nested, the choice, the options and the answers. We are really near finishing now.
Annotating IDs
Now it's time to perform all the annotations that we had already detected in our analysis of the module. So, after the // Define id annotations comment we'll be adding:
$answer->annotate_ids('user', 'userid');
It simply means, annotate for each $answer element, the value of the 'userid' field as one 'user' to be backup. In other words we are instructing backup that there is one new user to include later, when generating the users information and, at the same time, we are determining that those users are needed for the choice XX, so, if on restore we decide to skip that choice and such user isn't necessary for any other module / subsystem, it won't be restored. All this "uses" information is handled by the "inforef.xml" files within each activity backup, just in case you're interested to take a look to them. Else, just consider them, "dark magic".
Note: If you're not sure about which elements must be annotated, review "Annotating some important bits" above for details about must-be-done annotations.
Annotating files
And, finally, reviewing our elaborate and detailed schema, the last thing we need to take care of are the file areas used, so, after the // Define file annotations comment we'll add:
$choice->annotate_files('mod_choice', 'intro', null, $contextid = null); // This file area does not have an itemid.
That means that, within the $choice element we have one file area in use, of course belonging to the 'mod_choice' component (where all the choice module files belong to), named 'intro' and not using itemid (null). Note that, since it is possible to have multiple file areas in the same element (table), you may end up having multiple calls to the annotate_files() method, one for each filearea to be added to backup. The third parameter, if it is needed, must be the name of one of the attributes or fields of the $choice element (usually, in the vast majority of cases, the 'id' of the element), otherwise we'll use null. The fourth parameter is optional and will default to the context id of the backup, but if you want to specify the context id you do so here.
One encoded wor(l)d
Already out from the step definition, that should be working ok now, with the choice.xml file being generated and all the annotations being processed and added to the inforef.xml file, there is one more point to fulfill before considering choice's backup 100% finished.
And it's about how to provide the ability to transform some links to the choice module when the backup file is restored into another server / course. That is automatically handled by a two-step approach:
- On backup, we transform as many well-know URLs as possible to one encoded form.
- On restore, we transform those encoded URLS back to their original form, but pointing to their new targets.
So, in backup, we must provide services for the point 1 above, and that is done by adding some "encoding" conversions to the encode_content_links() method in our backup_choice_activity_task. So, define it to look like this
/**
* Code the transformations to perform in the activity in
* order to get transportable (encoded) links
*/
static public function encode_content_links($content) {
global $CFG;
$base = preg_quote($CFG->wwwroot,"/");
// Link to the list of choices
$search="/(".$base."\/mod\/choice\/index.php\?id\=)([0-9]+)/";
$content= preg_replace($search, '$@CHOICEINDEX*$2@$', $content);
// Link to choice view by moduleid
$search="/(".$base."\/mod\/choice\/view.php\?id\=)([0-9]+)/";
$content= preg_replace($search, '$@CHOICEVIEWBYID*$2@$', $content);
return $content;
}
Básically, it gets any URL (manually written) in any content of the backup being generated and transform some (well-known) URLs to one encoded alternative. In this case we are encoding:
- Any URL pointing to the list of choices in a course == is changed to ==> $@CHOICEINDEX*XX@$
- Any URL pointing to one exact choice == is changed to ==> $@CHOICEVIEWBYID*YY@$
In restore, well have the opposite conversions happening, replacing the XX and YY above to their new equivalents, so those links will be transportable via backup/restore without any need to edit them manually later.
Tip: Don't forget to look to the module's 1.9 backup code, as far as there you'll find which conversions must be considered to be added here.
Final notes
- Don't get stressed, it's really more difficult / longer to explain than to do it. 100% guaranteed, else we'll return your money. :-P
- If you've become lost, or your code is not working properly, you always can see the complete choice working code in git. Or, alternatively, look to other well known modules, like forum or also the big core library of steps, where you will find all sort of uses of the API.
- If you are going to implement backup for module XXXX, just copy this document, replace any "choice" occurrence within it by XXXX and follow it from the beginning to the end. Should work.
- If the module already existed in Moodle 1.x, try to follow the same structure if possible, that will make things easier for restore / conversion.
- Right now there are some pending tasks related with different exceptions that will be thrown if you code something wrongly (bad nesting, incorrect field names, bad constant/sql param uses...) and how they are shown. Fix for that will be coming soon.
- Also, as stated at the beginning of this tutorial, we are still missing "subplugins" final support in modules, so try to avoid implementing backup on them for now. Once ready, there will be one new section in this document about them.
- Any question / improvement / comment, feel free to contact with Moodle HQ, better if using the Tracker or, alternatively, forums / email / direct contact with your very-best-developer-friend. Note this is version x.0 (dot zero) of the backup API, so sure there are possibilities to improve it along the time.
Other components that can be backed up
Most plugins which can be added to a course can also include data in a course backup. Some examples:
- Course formats
- Themes: Backup 2.0 theme data
- Plagiarism plugins