Added course format section backup/restore support

From: Brandon Turner <brandont@thinkwell.com>

Course formats can override the default backup/restore section task by
subclassing the backup_section_task and restore_section_task classes.

The backup/restore task factories check to see if the current course format
supports overriding the default backup/restore section tasks via the
callback_xxx_uses_section_backup() method.

This allows course formats to append data to sections during backup/restore.
This functionality isn't available in a normal course format backup/restore
because new sections are not created until after the course-format restore is
completed.

To properly implement course-format section backup support, the format should:
1) Implement callback_xxx_uses_section_backup() in course/format/xxx/lib.php.
   In lieu of course format support in plugin_supports() this returns true if
   the format supports backup/restore.  If the method isn't implemented, false
   is assumed.
2) Add a course/format/xxx/backup/moodle2 folder
3) Add backup_format_xxx_section_task.class.php in the backup/moodle2 folder.
   This should extend backup_section_task.
4) Add restore_format_xxx_section_task.class.php in the backup/moodle2 folder.
   This should extend restore_section_task.
---
 backup/moodle2/backup_plan_builder.class.php    |    7 ++++++-
 backup/moodle2/backup_section_task.class.php    |   13 +++++++++++++
 backup/moodle2/restore_plan_builder.class.php   |    7 ++++++-
 backup/moodle2/restore_section_task.class.php   |   13 +++++++++++++
 backup/util/factories/backup_factory.class.php  |   12 ++++++++++--
 backup/util/factories/restore_factory.class.php |   12 ++++++++++--
 course/lib.php                                  |   16 ++++++++++++++++
 7 files changed, 74 insertions(+), 6 deletions(-)

diff --git a/backup/moodle2/backup_plan_builder.class.php b/backup/moodle2/backup_plan_builder.class.php
index 21e20c8..a9b46c4 100644
--- a/backup/moodle2/backup_plan_builder.class.php
+++ b/backup/moodle2/backup_plan_builder.class.php
@@ -65,6 +65,10 @@ foreach ($formats as $format => $formatdir) {
     if (course_format_uses_backup($format) && file_exists($taskpath)) {
         require_once($taskpath);
     }
+    $taskpath = $formatdir . '/backup/moodle2/backup_format_' . $format . '_section_task.class.php';
+    if (course_format_uses_section_backup($format) && file_exists($taskpath)) {
+        require_once($taskpath);
+    }
 }
 
 /**
@@ -140,10 +144,11 @@ abstract class backup_plan_builder {
     static protected function build_section_plan($controller, $id) {
 
         $plan = $controller->get_plan();
+        $courseformat = $controller->get_courseformat();
 
         // Add the section task, responsible for outputting
         // all the section related information
-        $plan->add_task(backup_factory::get_backup_section_task($controller->get_format(), $id));
+        $plan->add_task(backup_factory::get_backup_section_task($controller->get_format(), $id, $courseformat));
 
         // For the given section, add as many activity tasks as necessary
         $coursemodules = backup_plan_dbops::get_modules_from_sectionid($id);
diff --git a/backup/moodle2/backup_section_task.class.php b/backup/moodle2/backup_section_task.class.php
index 8986d8c..c9d6cfa 100644
--- a/backup/moodle2/backup_section_task.class.php
+++ b/backup/moodle2/backup_section_task.class.php
@@ -85,6 +85,9 @@ class backup_section_task extends backup_task {
         // Migrate the already exported inforef entries to final ones
         $this->add_step(new move_inforef_annotations_to_final('migrate_inforef'));
 
+        // Add sub-class steps, if any
+        $this->define_my_steps();
+
         // At the end, mark it as built
         $this->built = true;
     }
@@ -163,5 +166,15 @@ class backup_section_task extends backup_task {
         $users->add_dependency($section_userinfo);
         // Look for "section_included" section setting
         $section_included->add_dependency($section_userinfo);
+
+        // Add sub-class settings, if any
+        $this->define_my_settings();
     }
+
+
+    // Course formats can override these if needed:
+
+    protected function define_my_settings() {}
+
+    protected function define_my_steps() {}
 }
diff --git a/backup/moodle2/restore_plan_builder.class.php b/backup/moodle2/restore_plan_builder.class.php
index 4219ca7..b6bd148 100644
--- a/backup/moodle2/restore_plan_builder.class.php
+++ b/backup/moodle2/restore_plan_builder.class.php
@@ -63,6 +63,10 @@ foreach ($formats as $format => $formatdir) {
     if (course_format_uses_backup($format) && file_exists($taskpath)) {
         require_once($taskpath);
     }
+    $taskpath = $formatdir . '/backup/moodle2/restore_format_' . $format . '_section_task.class.php';
+    if (course_format_uses_section_backup($format) && file_exists($taskpath)) {
+        require_once($taskpath);
+    }
 }
 
 /**
@@ -148,10 +152,11 @@ abstract class restore_plan_builder {
         $plan = $controller->get_plan();
         $info = $controller->get_info();
         $infosection = $info->sections[$sectionid];
+        $courseformat = $info->courseformat;
 
         // Add the section task, responsible for restoring
         // all the section related information
-        $plan->add_task(restore_factory::get_restore_section_task($infosection));
+        $plan->add_task(restore_factory::get_restore_section_task($infosection, $courseformat));
         // For the given section, add as many activity tasks as necessary
         foreach ($info->activities as $activityid => $activity) {
             if ($activity->sectionid != $infosection->sectionid) {
diff --git a/backup/moodle2/restore_section_task.class.php b/backup/moodle2/restore_section_task.class.php
index 75bf7c3..1dc0e51 100644
--- a/backup/moodle2/restore_section_task.class.php
+++ b/backup/moodle2/restore_section_task.class.php
@@ -67,6 +67,9 @@ class restore_section_task extends restore_task {
             $this->add_step(new restore_section_structure_step('course_info', 'section.xml'));
         }
 
+        // Add sub-class steps, if any
+        $this->define_my_steps();
+
         // At the end, mark it as built
         $this->built = true;
     }
@@ -172,5 +175,15 @@ class restore_section_task extends restore_task {
         $users->add_dependency($section_userinfo);
         // Look for "section_included" section setting
         $section_included->add_dependency($section_userinfo);
+
+        // Add sub-class settings, if any
+        $this->define_my_settings();
     }
+
+
+    // Course formats can override these if needed:
+
+    protected function define_my_settings() {}
+
+    protected function define_my_steps() {}
 }
diff --git a/backup/util/factories/backup_factory.class.php b/backup/util/factories/backup_factory.class.php
index 872dda4..0b614a5 100644
--- a/backup/util/factories/backup_factory.class.php
+++ b/backup/util/factories/backup_factory.class.php
@@ -131,7 +131,7 @@ abstract class backup_factory {
     /**
      * Given one format and one section id, return the corresponding backup_section_task()
      */
-    public static function get_backup_section_task($format, $sectionid) {
+    public static function get_backup_section_task($format, $sectionid, $courseformat) {
         global $DB;
 
         // Check section exists
@@ -139,7 +139,15 @@ abstract class backup_factory {
             throw new backup_task_exception('section_task_section_not_found', $sectionid);
         }
 
-        return new backup_section_task(empty($section->name) ? $section->section : $section->name, $sectionid);
+        $classname = 'backup_section_task';
+        if($courseformat && course_format_uses_section_backup($courseformat)) {
+            $testname = 'backup_format_' . $courseformat . '_section_task';
+            if (class_exists($testname)) {
+                $classname = $testname;
+            }
+        }
+
+        return new $classname(empty($section->name) ? $section->section : $section->name, $sectionid);
     }
 
     /**
diff --git a/backup/util/factories/restore_factory.class.php b/backup/util/factories/restore_factory.class.php
index 652640a..6272cb6 100644
--- a/backup/util/factories/restore_factory.class.php
+++ b/backup/util/factories/restore_factory.class.php
@@ -51,9 +51,17 @@ abstract class restore_factory {
         return new $classname($blockname, $basepath);
     }
 
-    public static function get_restore_section_task($info) {
+    public static function get_restore_section_task($info, $courseformat) {
 
-        return new restore_section_task($info->title, $info);
+        $classname = 'restore_section_task';
+        if($courseformat && course_format_uses_section_backup($courseformat)) {
+            $testname = 'restore_format_' . $courseformat . '_section_task';
+            if (class_exists($testname)) {
+                $classname = $testname;
+            }
+        }
+
+        return new $classname($info->title, $info);
     }
 
     public static function get_restore_course_task($info, $courseid) {
diff --git a/course/lib.php b/course/lib.php
index 032b9c2..3660c64 100644
--- a/course/lib.php
+++ b/course/lib.php
@@ -3398,6 +3398,22 @@ function course_format_uses_backup($format) {
     return false;
 }
 
+function course_format_uses_section_backup($format) {
+    global $CFG;
+
+    $featurefile = $CFG->dirroot.'/course/format/'.$format.'/lib.php';
+    $featurefunction = 'callback_'.$format.'_uses_section_backup';
+    if (!function_exists($featurefunction) && file_exists($featurefile)) {
+        require_once $featurefile;
+    }
+    if (function_exists($featurefunction)) {
+        return $featurefunction();
+    }
+
+    return false;
+}
+
+
 /**
  * Can the current user delete this course?
  * Course creators have exception,
