Note:

If you want to create a new page for developers, you should create it on the Moodle Developer Resource site.

Backup 2.0 course report data

From MoodleDocs

Moodle 2.0

This page requires updating. Please do so and remove this template when finished.


Notice: Since Moodle 2.2, the admin reports and course report have been merged into a single reports subsystem. This page does not reflect this change yet. It is valid for 2.0 and 2.1 versions.

Introduction

In Moodle 2, course reports can have data tables and files which can be backed up with a course. (To create data tables in a course report, use the standard db folder.)

None of the standard course reports have any course-related data so it is not obvious how to code the backup and restore for them. This page gives an example theme as a case study which you can use to implement your own backup code for a course report which contains per-course data.

Note: Course report data backup requires Moodle 2.0.4 or later.

Example: the 'lazystudents' report

As an example I am using a theoretical report which checks the time that each user visits Moodle on each day over the past week. The report produces a list of students who did not access Moodle before a certain time on any day last week. For example, this might include all students who never accessed Moodle before 10:00 on any day last week.

The report has two configuration options which can both be set per-course:

  • The hour after which students are considered 'lazy' (e.g. 10).
  • A custom image to display with the report (e.g. some courses might prefer a tortoise picture while others prefer a sloth).

The hour is stored in the table coursereport_lazystudents which has 3 fields id, courseid, and lazyhour.

The image file is stored using the Moodle file API at the course context, in the coursereport_lazystudents component and the image file area.

Backup

The backup code is in the file course/report/lazystudents/backup/moodle2/backup_coursereport_lazystudents_plugin.class.php. Leaving out the headers, this file looks like:


class backup_coursereport_lazystudents_plugin extends backup_coursereport_plugin {

    /**
     * Returns the course report information to attach to course element
     */
    protected function define_course_plugin_structure() {
        // Define virtual plugin element
        $plugin = $this->get_plugin_element(null, $this->get_include_condition(), 'include');

        // Create plugin container element with standard name
        $pluginwrapper = new backup_nested_element($this->get_recommended_name());

        // Add wrapper to plugin
        $plugin->add_child($pluginwrapper);

        // Set up course report's own structure and add to wrapper
        $lazystudents = new backup_nested_element('lazystudents', array('id'), array(
            'lazyhour'));
        $pluginwrapper->add_child($lazystudents);

        // Use database to get source
        $lazystudents->set_source_table('coursereport_lazystudents',
                array('courseid' => backup::VAR_COURSEID));

        // Include files which have coursereport_lazystudents and area image and no itemid
        $lazystudents->annotate_files('coursereport_lazystudents', 'image', null);

        return $plugin;
    }

   /**
    * Returns a condition for whether we include this report in the backup
    * or not. We do that based on if it has any settings in the table.
    * @return array Condition array
    */
   protected function get_include_condition() {
       global $DB;
       if ($DB->record_exists('coursereport_lazystudents',
               array('courseid' => $this->task->get_courseid()))) {
           $result = 'include';
       } else {
           $result = ;
       }
       return array('sqlparam' => $result);
   }
}

As you can see this works in a similar way to backup for modules and other plugins. The first line:

$plugin = $this->get_plugin_element(null, $this->get_include_condition(), 'include');

means that the format data will only be backed up if the get_include_condition function returns true (i.e. it won't bother backing up data for a course report that does not have settings on the particular course).

You can see how you would change the code to use a different course report name, database table, fields, or file area. It's also possible to add a more complicated data structured in the same way as for other plugins. You could even store user data if required.

Testing

To test the backup, carry it out and then look inside the resulting zip file. In the course folder there should be a file called course.xml. If course report data is backed up, it will include a section like the following:

<plugin_coursereport_lazystudents_course>
 <lazystudents id="2">
  <lazyhour>10</lazyhour>
 </lazystudents>
</plugin_coursereport_lazystudents_course>

Additionally, if the file backup worked, you should be able to find any files from within the specified file area inside the 'files' section of the backup (it will help if you know the hash code of the file; otherwise, note the size and make sure the test course doesn't have many other files to look through).

If there is no data in the table, the course report will not be backed up at all and this section will not be included in course.xml.

Restore

Unsurprisingly, restore code goes in the file course/report/lazystudents/backup/moodle2/restore_coursereport_lazystudents_plugin.class.php. Leaving out the headers, this file is:

class restore_coursereport_lazystudents_plugin extends restore_coursereport_plugin {

    /**
     * Returns the paths to be handled by the plugin at course level
     */
    protected function define_course_plugin_structure() {
        $paths = array();

        // Because of using get_recommended_name() it is able to find the
        // correct path just by using the part inside the element name (which
        // only has a /lazystudents element).
        $elepath = $this->get_pathfor('/lazystudents');

        // The 'lazystudents' here defines that it will use the
        // process_lazystudents function to restore its element.
        $paths[] = new restore_path_element('lazystudents', $elepath);

        return $paths;
    }

    /**
     * Called after this runs for a course.
     */
    function after_execute_course() {
        // Need to restore file
        $this->add_related_files('coursereport_lazystudents', 'image', null);
    }

    /**
     * Process the 'lazystudents' element
     */
    public function process_lazystudents($data) {
        global $DB;

        // Get data record ready to insert in database
        $data = (object)$data;
        $data->courseid = $this->task->get_courseid();

        // See if there is an existing record for this course
        $existingid = $DB->get_field('coursereport_lazystudents', 'id',
                array('courseid'=>$data->courseid));
        if ($existingid) {
            $data->id = $existingid;
            $DB->update_record('coursereport_lazystudents', data);
        } else {
            $DB->insert_record('coursereport_lazystudents', $data);
        }

        // No need to record the old/new id as nothing ever refers to
        // the id of this table.
    }
}

Again this is just the same as restoring a module. To change this to support your own course report, you would change the word 'lazystudents' to your own course report's name, change references to the coursereport_lazystudents table to your own data table (or otherwise change what exactly it does when restoring), and change the file area details in the add_related_files call.

The restore code always runs if the data was included in the backup.

Testing

It is probably obvious how to test restore :) Once you've verified that backup is producing the correct XML data, just check that restore recreates the necessary data for you.

In our case, we know restore works if the lazy hour value for the new course is correctly set, and the sloth picture is correctly displayed.