Merge branch 'MDL-67713-master' of git://github.com/ferranrecio/moodle
authorSara Arjona <sara@moodle.com>
Mon, 4 May 2020 17:30:45 +0000 (19:30 +0200)
committerSara Arjona <sara@moodle.com>
Mon, 4 May 2020 17:30:45 +0000 (19:30 +0200)
mod/h5pactivity/backup/moodle2/backup_h5pactivity_stepslib.php
mod/h5pactivity/backup/moodle2/restore_h5pactivity_stepslib.php
mod/h5pactivity/tests/restore_test.php [new file with mode: 0644]

index 8f40e33..636e3ae 100644 (file)
@@ -44,10 +44,38 @@ class backup_h5pactivity_activity_structure_step extends backup_activity_structu
                 'introformat', 'grade', 'displayoptions'];
         $root = new backup_nested_element('h5pactivity', $attributes, $finalelements);
 
+        $attempts = new backup_nested_element('attempts');
+
+        $attempt = new backup_nested_element('attempt', ['id'],
+            ['h5pactivityid', 'userid', 'timecreated', 'timemodified', 'attempt', 'rawscore', 'maxscore']
+        );
+
+        $results = new backup_nested_element('results');
+
+        $result = new backup_nested_element('result', ['id'],
+            [
+                'attemptid', 'subcontent', 'timecreated', 'interactiontype', 'description',
+                'correctpattern', 'response', 'additionals', 'rawscore', 'maxscore'
+            ]
+        );
+
+        // Build the tree.
+        $root->add_child($attempts);
+        $attempts->add_child($attempt);
+        $attempt->add_child($results);
+        $results->add_child($result);
+
         // Define the source tables for the elements.
         $root->set_source_table('h5pactivity', ['id' => backup::VAR_ACTIVITYID]);
 
+        // All the rest of elements only happen if we are including user info.
+        if ($userinfo) {
+            $attempt->set_source_table('h5pactivity_attempts', ['h5pactivityid' => backup::VAR_PARENTID], 'id ASC');
+            $result->set_source_table('h5pactivity_attempts_results', ['attemptid' => backup::VAR_PARENTID], 'id ASC');
+        }
+
         // Define id annotations.
+        $attempt->annotate_ids('user', 'userid');
 
         // Define file annotations.
         $root->annotate_files('mod_h5pactivity', 'intro', null); // This file area hasn't itemid.
index 4c4ec6a..3bd63d3 100644 (file)
@@ -38,17 +38,21 @@ class restore_h5pactivity_activity_structure_step extends restore_activity_struc
         $paths = [];
         $userinfo = $this->get_setting_value('userinfo');
         $paths[] = new restore_path_element('h5pactivity', '/activity/h5pactivity');
+        if ($userinfo) {
+            $paths[] = new restore_path_element('h5pactivity_attempt', '/activity/h5pactivity/attempts/attempt');
+            $paths[] = new restore_path_element('h5pactivity_attempt_result', '/activity/h5pactivity/attempts/attempt/results/result');
+        }
         return $this->prepare_activity_structure($paths);
     }
 
     /**
-     * Processes the elt restore data.
+     * Processes the h5pactivity restore data.
      *
      * @param array $data Parsed element data.
      */
     protected function process_h5pactivity(array $data): void {
         global $DB;
-        $data = (object) $data;
+        $data = (object)$data;
         $data->course = $this->get_courseid();
         // Insert the record.
         $newitemid = $DB->insert_record('h5pactivity', $data);
@@ -56,6 +60,39 @@ class restore_h5pactivity_activity_structure_step extends restore_activity_struc
         $this->apply_activity_instance($newitemid);
     }
 
+    /**
+     * Processes the h5pactivity_attempts restore data.
+     *
+     * @param array $data Parsed element data.
+     */
+    protected function process_h5pactivity_attempt(array $data): void {
+        global $DB;
+        $data = (object)$data;
+
+        $oldid = $data->id;
+        $data->h5pactivityid = $this->get_new_parentid('h5pactivity');
+        $data->userid = $this->get_mappingid('user', $data->userid);
+
+        $newitemid = $DB->insert_record('h5pactivity_attempts', $data);
+        $this->set_mapping('h5pactivity_attempt', $oldid, $newitemid);
+    }
+
+    /**
+     * Processes the h5pactivity_attempts_results restore data.
+     *
+     * @param array $data Parsed element data.
+     */
+    protected function process_h5pactivity_attempt_result(array $data): void {
+        global $DB;
+        $data = (object)$data;
+
+        $oldid = $data->id;
+        $data->attemptid = $this->get_new_parentid('h5pactivity_attempt');
+
+        $newitemid = $DB->insert_record('h5pactivity_attempts_results', $data);
+        $this->set_mapping('h5pactivity_attempt_result', $oldid, $newitemid);
+    }
+
     /**
      * Defines post-execution actions.
      */
diff --git a/mod/h5pactivity/tests/restore_test.php b/mod/h5pactivity/tests/restore_test.php
new file mode 100644 (file)
index 0000000..95664b5
--- /dev/null
@@ -0,0 +1,212 @@
+<?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/>.
+
+/**
+ * mod_h5pactivity generator tests
+ *
+ * @package    mod_h5pactivity
+ * @category   test
+ * @copyright  2020 Ferran Recio <ferran@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace mod_h5pactivity;
+
+use advanced_testcase;
+use backup;
+use backup_controller;
+use backup_setting;
+use restore_controller;
+use restore_dbops;
+use stdClass;
+
+/**
+ * Genarator tests class for mod_h5pactivity.
+ *
+ * @package    mod_h5pactivity
+ * @category   test
+ * @copyright  2020 Ferran Recio <ferran@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mod_h5pactivity_restore_testcase extends advanced_testcase {
+
+    /**
+     * Setup to ensure that fixtures are loaded.
+     */
+    public static function setupBeforeClass(): void {
+        global $CFG;
+        require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
+        require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
+    }
+
+    /**
+     * Test on H5P activity backup and restore.
+     *
+     * @dataProvider backup_restore_data
+     * @param bool $content if has to create attempts
+     * @param bool $userdata if backup have userdata
+     * @param array $result1 data to check on original course
+     * @param array $result2 data to check on resotred course
+     */
+    public function test_backup_restore(bool $content, bool $userdata, array $result1, array $result2) {
+        global $DB;
+        $this->resetAfterTest();
+
+        $this->setAdminUser();
+
+        $course = $this->getDataGenerator()->create_course();
+        $user = $this->getDataGenerator()->create_and_enrol($course, 'student');
+
+        // Create one activity.
+        $this->assertFalse($DB->record_exists('h5pactivity', ['course' => $course->id]));
+        $activity = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $course]);
+        $cm = get_coursemodule_from_id('h5pactivity', $activity->cmid, 0, false, MUST_EXIST);
+
+        if ($content) {
+            $generator = $this->getDataGenerator()->get_plugin_generator('mod_h5pactivity');
+            $params = ['cmid' => $cm->id, 'userid' => $user->id];
+            $generator->create_content($activity, $params);
+        }
+
+        $this->assertEquals($result1[0], $DB->count_records('h5pactivity', ['course' => $course->id]));
+        $this->assertEquals($result1[1], $DB->count_records('h5pactivity_attempts', ['h5pactivityid' => $activity->id]));
+        $attemptid = $DB->get_field('h5pactivity_attempts', 'id', ['h5pactivityid' => $activity->id]);
+        $this->assertEquals($result1[2], $DB->count_records('h5pactivity_attempts_results', ['attemptid' => $attemptid]));
+
+        // Execute course backup and restore.
+        $newcourseid = $this->backup_and_restore($course, $userdata);
+
+        // Check original activity.
+        $this->assertEquals($result1[0], $DB->count_records('h5pactivity', ['course' => $course->id]));
+        $this->assertEquals($result1[1], $DB->count_records('h5pactivity_attempts', ['h5pactivityid' => $activity->id]));
+        $attempt = $DB->get_record('h5pactivity_attempts', ['h5pactivityid' => $activity->id]);
+        $attemptid = $attempt->id ?? 0;
+        $this->assertEquals($result1[2], $DB->count_records('h5pactivity_attempts_results', ['attemptid' => $attemptid]));
+
+        // Check original activity.
+        $this->assertEquals($result2[0], $DB->count_records('h5pactivity', ['course' => $newcourseid]));
+        $activity2 = $DB->get_record('h5pactivity', ['course' => $newcourseid]);
+        $this->assertEquals($result2[1], $DB->count_records('h5pactivity_attempts', ['h5pactivityid' => $activity2->id]));
+        $attempt2 = $DB->get_record('h5pactivity_attempts', ['h5pactivityid' => $activity2->id]);
+        $attempt2id = $attempt2->id ?? 0;
+        $this->assertEquals($result2[2], $DB->count_records('h5pactivity_attempts_results', ['attemptid' => $attempt2id]));
+
+        // Compare activities.
+        $this->assertEquals($newcourseid, $activity2->course);
+        $this->assertEquals($activity->name, $activity2->name);
+        $this->assertEquals($activity->intro, $activity2->intro);
+        $this->assertEquals($activity->introformat, $activity2->introformat);
+        $this->assertEquals($activity->grade, $activity2->grade);
+        $this->assertEquals($activity->displayoptions, $activity2->displayoptions);
+
+        // Compare attempts.
+        if ($content && $userdata) {
+            $this->assertEquals($activity2->id, $attempt2->h5pactivityid);
+            $this->assertEquals($attempt->userid, $attempt2->userid);
+            $this->assertEquals($attempt->timecreated, $attempt2->timecreated);
+            $this->assertEquals($attempt->timemodified, $attempt2->timemodified);
+            $this->assertEquals($attempt->attempt, $attempt2->attempt);
+            $this->assertEquals($attempt->rawscore, $attempt2->rawscore);
+            $this->assertEquals($attempt->maxscore, $attempt2->maxscore);
+
+            // Compare results.
+            $results = $DB->get_records('h5pactivity_attempts_results', ['attemptid' => $attempt->id]);
+            foreach ($results as $result) {
+                $result2 = $DB->get_record('h5pactivity_attempts_results', [
+                    'subcontent' => $result->subcontent, 'attemptid' => $attempt2->id
+                ]);
+                $this->assertNotFalse($result2);
+                $this->assertEquals($result->timecreated, $result2->timecreated);
+                $this->assertEquals($result->interactiontype, $result2->interactiontype);
+                $this->assertEquals($result->description, $result2->description);
+                $this->assertEquals($result->correctpattern, $result2->correctpattern);
+                $this->assertEquals($result->response, $result2->response);
+                $this->assertEquals($result->additionals, $result2->additionals);
+                $this->assertEquals($result->rawscore, $result2->rawscore);
+                $this->assertEquals($result->maxscore, $result2->maxscore);
+            }
+        }
+
+    }
+
+    /**
+     * Data provider for test_backup_restore.
+     *
+     * @return array
+     */
+    public function backup_restore_data(): array {
+        return [
+            'Activity attempts and restore with userdata' => [
+                true, true, [1, 1, 3], [1, 1, 3]
+            ],
+            'No activity attempts and restore with userdata' => [
+                false, true, [1, 0, 0], [1, 0, 0]
+            ],
+            'Activity attempts and restore with no userdata' => [
+                true, false, [1, 1, 3], [1, 0, 0]
+            ],
+            'No activity attempts and restore with no userdata' => [
+                false, false, [1, 0, 0], [1, 0, 0]
+            ],
+        ];
+    }
+
+    /**
+     * Backs a course up and restores it.
+     *
+     * @param stdClass $srccourse Course object to backup
+     * @param bool $userdata if the backup must be with user data
+     * @return int ID of newly restored course
+     */
+    private function backup_and_restore(stdClass $srccourse, bool $userdata): int {
+        global $USER, $CFG;
+
+        require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
+
+        // Turn off file logging, otherwise it can't delete the file (Windows).
+        $CFG->backup_file_logger_level = backup::LOG_NONE;
+
+        // Do backup with default settings. MODE_IMPORT means it will just
+        // create the directory and not zip it.
+        $bc = new backup_controller(backup::TYPE_1COURSE, $srccourse->id,
+                backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_IMPORT,
+                $USER->id);
+
+        $bc->get_plan()->get_setting('users')->set_status(backup_setting::NOT_LOCKED);
+        $bc->get_plan()->get_setting('users')->set_value($userdata);
+
+        $backupid = $bc->get_backupid();
+        $bc->execute_plan();
+        $bc->destroy();
+
+        // Do restore to new course with default settings.
+        $newcourseid = restore_dbops::create_new_course(
+            $srccourse->fullname, $srccourse->shortname . '_2', $srccourse->category
+        );
+        $rc = new restore_controller($backupid, $newcourseid,
+                backup::INTERACTIVE_NO, backup::MODE_GENERAL, $USER->id,
+                backup::TARGET_NEW_COURSE);
+
+        $rc->get_plan()->get_setting('users')->set_status(backup_setting::NOT_LOCKED);
+        $rc->get_plan()->get_setting('users')->set_value($userdata);
+
+        $this->assertTrue($rc->execute_precheck());
+        $rc->execute_plan();
+        $rc->destroy();
+
+        return $newcourseid;
+    }
+}