From 0d14fcbc604633833397dfcf56da811fcdfa3e28 Mon Sep 17 00:00:00 2001 From: Ankit Agarwal Date: Thu, 29 Jun 2017 13:38:47 +0530 Subject: [PATCH] MDL-9367 restore: Roll only configuration dates It was decided to roll only configuration dates and any date related to user content such as 'timecreated' , 'timemodified' etc should not be rolled forward. --- backup/util/plan/restore_step.class.php | 2 + lib/phpunit/classes/restore_date_testcase.php | 163 +++++++++++++++ .../moodle2/restore_assign_stepslib.php | 7 +- mod/assign/locallib.php | 2 + mod/assign/tests/restore_date_test.php | 92 +++++++++ .../backup/moodle2/restore_book_stepslib.php | 3 + mod/book/lib.php | 3 + .../backup/moodle2/restore_chat_stepslib.php | 4 +- mod/chat/lib.php | 2 + mod/chat/tests/restore_date_test.php | 68 +++++++ .../moodle2/restore_choice_stepslib.php | 5 +- mod/choice/lib.php | 2 + mod/choice/tests/restore_date_test.php | 71 +++++++ .../backup/moodle2/restore_data_stepslib.php | 9 +- mod/data/lib.php | 5 +- mod/data/tests/restore_date_test.php | 93 +++++++++ mod/feedback/backup/moodle1/lib.php | 2 + .../moodle2/restore_feedback_stepslib.php | 4 +- mod/feedback/lib.php | 2 + mod/feedback/tests/restore_date_test.php | 64 ++++++ .../moodle2/restore_folder_stepslib.php | 4 +- mod/folder/lib.php | 4 + mod/folder/tests/restore_date_test.php | 49 +++++ .../backup/moodle2/restore_forum_stepslib.php | 7 +- mod/forum/lib.php | 2 + mod/forum/tests/restore_date_test.php | 110 ++++++++++ .../moodle2/restore_glossary_stepslib.php | 7 +- mod/glossary/lib.php | 2 + mod/glossary/tests/restore_date_test.php | 91 +++++++++ .../backup/moodle2/restore_imscp_stepslib.php | 4 +- mod/imscp/lib.php | 4 + mod/imscp/tests/restore_date_test.php | 49 +++++ .../backup/moodle2/restore_label_stepslib.php | 3 + mod/label/lib.php | 4 + .../moodle2/restore_lesson_stepslib.php | 12 +- mod/lesson/lib.php | 2 + mod/lesson/tests/restore_date_test.php | 190 ++++++++++++++++++ .../backup/moodle2/restore_lti_stepslib.php | 3 + .../backup/moodle2/restore_page_stepslib.php | 3 + mod/page/lib.php | 4 + .../backup/moodle2/restore_quiz_stepslib.php | 11 +- mod/quiz/lib.php | 2 + mod/quiz/tests/restore_date_test.php | 116 +++++++++++ .../moodle2/restore_resource_stepslib.php | 4 +- mod/resource/lib.php | 4 + mod/resource/tests/restore_date_test.php | 51 +++++ .../backup/moodle2/restore_scorm_stepslib.php | 4 +- mod/scorm/tests/restore_date_test.php | 65 ++++++ .../moodle2/restore_survey_stepslib.php | 6 +- mod/survey/lib.php | 5 +- mod/survey/tests/restore_date_test.php | 79 ++++++++ .../backup/moodle2/restore_url_stepslib.php | 3 + mod/url/lib.php | 4 + .../backup/moodle2/restore_wiki_stepslib.php | 7 +- mod/wiki/tests/restore_date_test.php | 73 +++++++ .../moodle2/restore_workshop_stepslib.php | 14 +- mod/workshop/tests/restore_date_test.php | 86 ++++++++ 57 files changed, 1617 insertions(+), 74 deletions(-) create mode 100644 lib/phpunit/classes/restore_date_testcase.php create mode 100644 mod/assign/tests/restore_date_test.php create mode 100644 mod/chat/tests/restore_date_test.php create mode 100644 mod/choice/tests/restore_date_test.php create mode 100644 mod/data/tests/restore_date_test.php create mode 100644 mod/feedback/tests/restore_date_test.php create mode 100644 mod/folder/tests/restore_date_test.php create mode 100644 mod/forum/tests/restore_date_test.php create mode 100644 mod/glossary/tests/restore_date_test.php create mode 100644 mod/imscp/tests/restore_date_test.php create mode 100644 mod/lesson/tests/restore_date_test.php create mode 100644 mod/quiz/tests/restore_date_test.php create mode 100644 mod/resource/tests/restore_date_test.php create mode 100644 mod/scorm/tests/restore_date_test.php create mode 100644 mod/survey/tests/restore_date_test.php create mode 100644 mod/wiki/tests/restore_date_test.php create mode 100644 mod/workshop/tests/restore_date_test.php diff --git a/backup/util/plan/restore_step.class.php b/backup/util/plan/restore_step.class.php index c72f3390336..da9a1035bf4 100644 --- a/backup/util/plan/restore_step.class.php +++ b/backup/util/plan/restore_step.class.php @@ -51,6 +51,8 @@ abstract class restore_step extends base_step { * Note we are using one static cache here, but *by restoreid*, so it's ok for concurrence/multiple * executions in the same request * + * Note: The policy is to roll date only for configurations and not for user data. see MDL-9367. + * * @param int $value Time value (seconds since epoch), or empty for nothing * @return int Time value after applying the date offset, or empty for nothing */ diff --git a/lib/phpunit/classes/restore_date_testcase.php b/lib/phpunit/classes/restore_date_testcase.php new file mode 100644 index 00000000000..bc80ae79af7 --- /dev/null +++ b/lib/phpunit/classes/restore_date_testcase.php @@ -0,0 +1,163 @@ +. + +/** + * Restore dates test case. + * + * @package core + * @category test + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php'); +require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php'); + + +/** + * Advanced PHPUnit test case customised for testing restore dates in Moodle. + * + * @package core + * @category test + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +abstract class restore_date_testcase extends advanced_testcase { + /** + * @var int Course start date. + */ + protected $startdate; + + /** + * @var int Course restore date. + */ + protected $restorestartdate; + + /** + * Setup. + */ + public function setUp() { + global $CFG; + + parent::setUp(); + $this->resetAfterTest(); + $this->setAdminUser(); + $this->startdate = strtotime('1 Jan 2017 00:00 GMT'); + $this->restorestartdate = strtotime('1 Feb 2017 00:00 GMT'); + $CFG->enableavailability = true; + } + + /** + * Backs a course up and restores it. + * + * @param stdClass $course Course object to backup + * @param int $newdate If non-zero, specifies custom date for new course + * @return int ID of newly restored course + */ + protected function backup_and_restore($course, $newdate = 0) { + global $USER, $CFG; + + // 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. + set_config('backup_general_users', 1, 'backup'); + $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, + backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_GENERAL, + $USER->id); + $bc->execute_plan(); + $results = $bc->get_results(); + $file = $results['backup_destination']; + $fp = get_file_packer('application/vnd.moodle.backup'); + $filepath = $CFG->dataroot . '/temp/backup/test-restore-course'; + $file->extract_to_pathname($fp, $filepath); + $bc->destroy(); + + // Do restore to new course with default settings. + $newcourseid = restore_dbops::create_new_course( + $course->fullname, $course->shortname . '_2', $course->category); + $rc = new restore_controller('test-restore-course', $newcourseid, + backup::INTERACTIVE_NO, backup::MODE_GENERAL, $USER->id, + backup::TARGET_NEW_COURSE); + + if (empty($newdate)) { + $newdate = $this->restorestartdate; + } + + $rc->get_plan()->get_setting('course_startdate')->set_value($newdate); + $this->assertTrue($rc->execute_precheck()); + $rc->execute_plan(); + $rc->destroy(); + + return $newcourseid; + } + + /** + * Helper method to create a course and a module. + * + * @param string $modulename + * @param array|stdClass $record + * @return array + */ + protected function create_course_and_module($modulename, $record = []) { + // Create a course with specific start date. + $record = (array)$record; + $generator = $this->getDataGenerator(); + $course = $generator->create_course(['startdate' => $this->startdate]); + $record = array_merge(['course' => $course->id], $record); + $module = $this->getDataGenerator()->create_module($modulename, $record); + return [$course, $module]; + } + + /** + * Verify that the given properties are not rolled. + * + * @param stdClass $oldinstance + * @param stdClass $newinstance + * @param [] $props + */ + protected function assertFieldsNotRolledForward($oldinstance, $newinstance, $props) { + foreach ($props as $prop) { + $this->assertEquals($oldinstance->$prop, $newinstance->$prop, "'$prop' should not roll forward."); + } + } + + /** + * Verify that the given properties are rolled. + * + * @param stdClass $oldinstance + * @param stdClass $newinstance + * @param [] $props + */ + protected function assertFieldsRolledForward($oldinstance, $newinstance, $props) { + $diff = $this->get_diff(); + foreach ($props as $prop) { + $this->assertEquals(($oldinstance->$prop + $diff), $newinstance->$prop, "'$prop' doesn't roll as expected."); + } + } + + /** + * Get time diff between start date and restore date in seconds. + * + * @return mixed + */ + protected function get_diff() { + return ($this->restorestartdate - $this->startdate); + } + +} \ No newline at end of file diff --git a/mod/assign/backup/moodle2/restore_assign_stepslib.php b/mod/assign/backup/moodle2/restore_assign_stepslib.php index 64bc4b529d5..981ad27d132 100644 --- a/mod/assign/backup/moodle2/restore_assign_stepslib.php +++ b/mod/assign/backup/moodle2/restore_assign_stepslib.php @@ -87,7 +87,8 @@ class restore_assign_activity_structure_step extends restore_activity_structure_ $oldid = $data->id; $data->course = $this->get_courseid(); - $data->timemodified = $this->apply_date_offset($data->timemodified); + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. $data->allowsubmissionsfromdate = $this->apply_date_offset($data->allowsubmissionsfromdate); $data->duedate = $this->apply_date_offset($data->duedate); @@ -159,8 +160,6 @@ class restore_assign_activity_structure_step extends restore_activity_structure_ $data->assignment = $this->get_new_parentid('assign'); - $data->timemodified = $this->apply_date_offset($data->timemodified); - $data->timecreated = $this->apply_date_offset($data->timecreated); if ($data->userid > 0) { $data->userid = $this->get_mappingid('user', $data->userid); } @@ -220,8 +219,6 @@ class restore_assign_activity_structure_step extends restore_activity_structure_ $data->assignment = $this->get_new_parentid('assign'); - $data->timemodified = $this->apply_date_offset($data->timemodified); - $data->timecreated = $this->apply_date_offset($data->timecreated); $data->userid = $this->get_mappingid('user', $data->userid); $data->grader = $this->get_mappingid('user', $data->grader); diff --git a/mod/assign/locallib.php b/mod/assign/locallib.php index 5a1644274b2..003229bcf10 100644 --- a/mod/assign/locallib.php +++ b/mod/assign/locallib.php @@ -1035,6 +1035,8 @@ class assign { WHERE assignid =? AND cutoffdate <> 0", array($data->timeshift, $this->get_instance()->id)); + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. shift_course_mod_dates('assign', array('duedate', 'allowsubmissionsfromdate', 'cutoffdate'), $data->timeshift, diff --git a/mod/assign/tests/restore_date_test.php b/mod/assign/tests/restore_date_test.php new file mode 100644 index 00000000000..c6c50b991ae --- /dev/null +++ b/mod/assign/tests/restore_date_test.php @@ -0,0 +1,92 @@ +. + +/** + * Restore date tests. + * + * @package mod_assign + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . "/phpunit/classes/restore_date_testcase.php"); +require_once($CFG->dirroot . '/mod/assign/tests/base_test.php'); + +/** + * Restore date tests. + * + * @package mod_assign + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_assign_restore_date_testcase extends restore_date_testcase { + + /** + * Test restore dates. + */ + public function test_restore_dates() { + global $DB, $USER; + + $record = ['cutoffdate' => 100, 'allowsubmissionsfromdate' => 100, 'duedate' => 100, 'timemodified' => 100]; + list($course, $assign) = $this->create_course_and_module('assign', $record); + $cm = $DB->get_record('course_modules', ['course' => $course->id, 'instance' => $assign->id]); + $assignobj = new testable_assign(context_module::instance($cm->id), $cm, $course); + $submission = $assignobj->get_user_submission($USER->id, true); + $grade = $assignobj->get_user_grade($USER->id, true); + + // User override. + $override = (object)[ + 'assignid' => $assign->id, + 'groupid' => 0, + 'userid' => $USER->id, + 'sortorder' => 1, + 'allowsubmissionsfromdate' => 100, + 'duedate' => 200, + 'cutoffdate' => 300 + ]; + $DB->insert_record('assign_overrides', $override); + + // Do backup and restore. + $newcourseid = $this->backup_and_restore($course); + $newassign = $DB->get_record('assign', ['course' => $newcourseid]); + + $this->assertFieldsNotRolledForward($assign, $newassign, ['timemodified']); + $props = ['allowsubmissionsfromdate', 'duedate', 'cutoffdate']; + $this->assertFieldsRolledForward($assign, $newassign, $props); + + $newsubmission = $DB->get_record('assign_submission', ['assignment' => $newassign->id]); + $newoverride = $DB->get_record('assign_overrides', ['assignid' => $newassign->id]); + $newgrade = $DB->get_record('assign_grades', ['assignment' => $newassign->id]); + + // Assign submission time checks. + $this->assertEquals($submission->timecreated, $newsubmission->timecreated); + $this->assertEquals($submission->timemodified, $newsubmission->timemodified); + + // Assign override time checks. + $diff = $this->get_diff(); + $this->assertEquals($override->duedate + $diff, $newoverride->duedate); + $this->assertEquals($override->cutoffdate + $diff, $newoverride->cutoffdate); + $this->assertEquals($override->allowsubmissionsfromdate + $diff, $newoverride->allowsubmissionsfromdate); + + // Assign grade time checks. + $this->assertEquals($grade->timecreated, $newgrade->timecreated); + $this->assertEquals($grade->timemodified, $newgrade->timemodified); + + } +} diff --git a/mod/book/backup/moodle2/restore_book_stepslib.php b/mod/book/backup/moodle2/restore_book_stepslib.php index 076a24f5515..8987706acb1 100644 --- a/mod/book/backup/moodle2/restore_book_stepslib.php +++ b/mod/book/backup/moodle2/restore_book_stepslib.php @@ -51,6 +51,9 @@ class restore_book_activity_structure_step extends restore_activity_structure_st $oldid = $data->id; $data->course = $this->get_courseid(); + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. + $newitemid = $DB->insert_record('book', $data); $this->apply_activity_instance($newitemid); } diff --git a/mod/book/lib.php b/mod/book/lib.php index c3bd4d0bd2a..ffc7eb7a3d6 100644 --- a/mod/book/lib.php +++ b/mod/book/lib.php @@ -164,6 +164,9 @@ function book_print_recent_activity($course, $viewfullnames, $timestart) { * @return array status array */ function book_reset_userdata($data) { + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. + return array(); } diff --git a/mod/chat/backup/moodle2/restore_chat_stepslib.php b/mod/chat/backup/moodle2/restore_chat_stepslib.php index 582227f02b8..95be95a1053 100644 --- a/mod/chat/backup/moodle2/restore_chat_stepslib.php +++ b/mod/chat/backup/moodle2/restore_chat_stepslib.php @@ -51,8 +51,9 @@ class restore_chat_activity_structure_step extends restore_activity_structure_st $oldid = $data->id; $data->course = $this->get_courseid(); + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. $data->chattime = $this->apply_date_offset($data->chattime); - $data->timemodified = $this->apply_date_offset($data->timemodified); // Insert the chat record. $newitemid = $DB->insert_record('chat', $data); @@ -69,7 +70,6 @@ class restore_chat_activity_structure_step extends restore_activity_structure_st $data->userid = $this->get_mappingid('user', $data->userid); $data->groupid = $this->get_mappingid('group', $data->groupid); $data->message = $data->message_text; - $data->timestamp = $this->apply_date_offset($data->timestamp); $newitemid = $DB->insert_record('chat_messages', $data); $this->set_mapping('chat_message', $oldid, $newitemid); // Because of decode. diff --git a/mod/chat/lib.php b/mod/chat/lib.php index 7a917999bd2..ef91dc1bf6d 100644 --- a/mod/chat/lib.php +++ b/mod/chat/lib.php @@ -1257,6 +1257,8 @@ function chat_reset_userdata($data) { // Updating dates - shift may be negative too. if ($data->timeshift) { + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. shift_course_mod_dates('chat', array('chattime'), $data->timeshift, $data->courseid); $status[] = array('component' => $componentstr, 'item' => get_string('datechanged'), 'error' => false); } diff --git a/mod/chat/tests/restore_date_test.php b/mod/chat/tests/restore_date_test.php new file mode 100644 index 00000000000..94043a40eb9 --- /dev/null +++ b/mod/chat/tests/restore_date_test.php @@ -0,0 +1,68 @@ +. + +/** + * Restore date tests. + * + * @package mod_chat + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . "/phpunit/classes/restore_date_testcase.php"); + +/** + * Restore date tests. + * + * @package mod_chat + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_chat_restore_date_testcase extends restore_date_testcase { + + public function test_restore_dates() { + global $DB; + + list($course, $chat) = $this->create_course_and_module('chat'); + $result = mod_chat_external::login_user($chat->id); + $result = external_api::clean_returnvalue(mod_chat_external::login_user_returns(), $result); + $chatsid = $result['chatsid']; + + $result = mod_chat_external::send_chat_message($chatsid, 'hello!'); + $result = external_api::clean_returnvalue(mod_chat_external::send_chat_message_returns(), $result); + $message = $DB->get_record('chat_messages', ['id' => $result['messageid']]); + $timestamp = 1000; + $DB->set_field('chat_messages', 'timestamp', $timestamp); + + // Do backup and restore. + $newcourseid = $this->backup_and_restore($course); + $newchat = $DB->get_record('chat', ['course' => $newcourseid]); + + $this->assertFieldsNotRolledForward($chat, $newchat, ['timemodified']); + $props = ['chattime']; + $this->assertFieldsRolledForward($chat, $newchat, $props); + + $newmessages = $DB->get_records('chat_messages', ['chatid' => $newchat->id]); + + foreach ($newmessages as $message) { + $this->assertEquals($timestamp, $message->timestamp); + } + + } +} diff --git a/mod/choice/backup/moodle2/restore_choice_stepslib.php b/mod/choice/backup/moodle2/restore_choice_stepslib.php index bcba1a10919..0b0703c5b6f 100644 --- a/mod/choice/backup/moodle2/restore_choice_stepslib.php +++ b/mod/choice/backup/moodle2/restore_choice_stepslib.php @@ -53,9 +53,10 @@ class restore_choice_activity_structure_step extends restore_activity_structure_ $oldid = $data->id; $data->course = $this->get_courseid(); + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. $data->timeopen = $this->apply_date_offset($data->timeopen); $data->timeclose = $this->apply_date_offset($data->timeclose); - $data->timemodified = $this->apply_date_offset($data->timemodified); // insert the choice record $newitemid = $DB->insert_record('choice', $data); @@ -70,7 +71,6 @@ class restore_choice_activity_structure_step extends restore_activity_structure_ $oldid = $data->id; $data->choiceid = $this->get_new_parentid('choice'); - $data->timemodified = $this->apply_date_offset($data->timemodified); $newitemid = $DB->insert_record('choice_options', $data); $this->set_mapping('choice_option', $oldid, $newitemid); @@ -84,7 +84,6 @@ class restore_choice_activity_structure_step extends restore_activity_structure_ $data->choiceid = $this->get_new_parentid('choice'); $data->optionid = $this->get_mappingid('choice_option', $data->optionid); $data->userid = $this->get_mappingid('user', $data->userid); - $data->timemodified = $this->apply_date_offset($data->timemodified); $newitemid = $DB->insert_record('choice_answers', $data); // No need to save this mapping as far as nothing depend on it diff --git a/mod/choice/lib.php b/mod/choice/lib.php index 124e08139e9..2689910b179 100644 --- a/mod/choice/lib.php +++ b/mod/choice/lib.php @@ -754,6 +754,8 @@ function choice_reset_userdata($data) { /// updating dates - shift may be negative too if ($data->timeshift) { + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. shift_course_mod_dates('choice', array('timeopen', 'timeclose'), $data->timeshift, $data->courseid); $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false); } diff --git a/mod/choice/tests/restore_date_test.php b/mod/choice/tests/restore_date_test.php new file mode 100644 index 00000000000..8c49f471fd8 --- /dev/null +++ b/mod/choice/tests/restore_date_test.php @@ -0,0 +1,71 @@ +. + +/** + * Restore date tests. + * + * @package mod_choice + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . "/phpunit/classes/restore_date_testcase.php"); + +/** + * Restore date tests. + * + * @package mod_choice + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_choice_restore_date_testcase extends restore_date_testcase { + + public function test_restore_dates() { + global $DB, $USER; + + $time = 100000; + $record = ['timeopen' => $time, 'timeclose' => $time]; + list($course, $choice) = $this->create_course_and_module('choice', $record); + + $options = $DB->get_records('choice_options', ['choiceid' => $choice->id]); + $DB->set_field('choice_options', 'timemodified', $time); + $option = reset($options); + $cm = $DB->get_record('course_modules', ['id' => $choice->cmid]); + choice_user_submit_response($option->id, $choice, $USER->id, $course, $cm); + $answer = $DB->get_record('choice_answers', ['choiceid' => $choice->id]); + + // Do backup and restore. + $newcourseid = $this->backup_and_restore($course); + $newchoice = $DB->get_record('choice', ['course' => $newcourseid]); + $newoptions = $DB->get_records('choice_options', ['choiceid' => $newchoice->id]); + + $this->assertFieldsNotRolledForward($choice, $newchoice, ['timemodified']); + $props = ['timeopen', 'timeclose']; + $this->assertFieldsRolledForward($choice, $newchoice, $props); + + // Options check. + foreach ($newoptions as $newoption) { + $this->assertEquals($time, $newoption->timemodified); + } + + // Answers check. + $newanswer = $DB->get_record('choice_answers', ['choiceid' => $newchoice->id]); + $this->assertEquals($answer->timemodified, $newanswer->timemodified); + } +} diff --git a/mod/data/backup/moodle2/restore_data_stepslib.php b/mod/data/backup/moodle2/restore_data_stepslib.php index dd71cf2b892..7ac138b8ff6 100644 --- a/mod/data/backup/moodle2/restore_data_stepslib.php +++ b/mod/data/backup/moodle2/restore_data_stepslib.php @@ -55,14 +55,14 @@ class restore_data_activity_structure_step extends restore_activity_structure_st $oldid = $data->id; $data->course = $this->get_courseid(); + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. $data->timeavailablefrom = $this->apply_date_offset($data->timeavailablefrom); $data->timeavailableto = $this->apply_date_offset($data->timeavailableto); $data->timeviewfrom = $this->apply_date_offset($data->timeviewfrom); $data->timeviewto = $this->apply_date_offset($data->timeviewto); $data->assesstimestart = $this->apply_date_offset($data->assesstimestart); $data->assesstimefinish = $this->apply_date_offset($data->assesstimefinish); - // Added in 3.1, hence conditional. - $data->timemodified = isset($data->timemodified) ? $this->apply_date_offset($data->timemodified) : time(); if ($data->scale < 0) { // scale found, get mapping $data->scale = -($this->get_mappingid('scale', abs($data->scale))); @@ -98,9 +98,6 @@ class restore_data_activity_structure_step extends restore_activity_structure_st $data = (object)$data; $oldid = $data->id; - $data->timecreated = $this->apply_date_offset($data->timecreated); - $data->timemodified = $this->apply_date_offset($data->timemodified); - $data->userid = $this->get_mappingid('user', $data->userid); $data->groupid = $this->get_mappingid('group', $data->groupid); $data->dataid = $this->get_new_parentid('data'); @@ -137,8 +134,6 @@ class restore_data_activity_structure_step extends restore_activity_structure_st } $data->rating = $data->value; $data->userid = $this->get_mappingid('user', $data->userid); - $data->timecreated = $this->apply_date_offset($data->timecreated); - $data->timemodified = $this->apply_date_offset($data->timemodified); // We need to check that component and ratingarea are both set here. if (empty($data->component)) { diff --git a/mod/data/lib.php b/mod/data/lib.php index 2d50f84d3a6..498a5c01338 100644 --- a/mod/data/lib.php +++ b/mod/data/lib.php @@ -2872,7 +2872,10 @@ function data_reset_userdata($data) { // updating dates - shift may be negative too if ($data->timeshift) { - shift_course_mod_dates('data', array('timeavailablefrom', 'timeavailableto', 'timeviewfrom', 'timeviewto'), $data->timeshift, $data->courseid); + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. + shift_course_mod_dates('data', array('timeavailablefrom', 'timeavailableto', + 'timeviewfrom', 'timeviewto', 'assesstimestart', 'assesstimefinish'), $data->timeshift, $data->courseid); $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false); } diff --git a/mod/data/tests/restore_date_test.php b/mod/data/tests/restore_date_test.php new file mode 100644 index 00000000000..fcf751b97a4 --- /dev/null +++ b/mod/data/tests/restore_date_test.php @@ -0,0 +1,93 @@ +. + +/** + * Restore date tests. + * + * @package mod_data + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . "/phpunit/classes/restore_date_testcase.php"); +require_once($CFG->dirroot . '/rating/lib.php'); + +/** + * Restore date tests. + * + * @package mod_data + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_data_restore_date_testcase extends restore_date_testcase { + + /** + * Test restore dates. + */ + public function test_restore_dates() { + global $DB, $USER; + + $gg = $this->getDataGenerator()->get_plugin_generator('mod_data'); + $record = ['assesstimefinish' => 100, 'assesstimestart' => 100, 'ratingtime' => 1, 'assessed' => 2, 'scale' => 1, + 'timeavailablefrom' => 100, 'timeavailableto' => 100, 'timeviewfrom' => 100, 'timeviewto' => 100]; + list($course, $data) = $this->create_course_and_module('data', $record); + + // Data field/record. + $timestamp = 996699; + $diff = $this->get_diff(); + $record = new StdClass(); + $record->name = 'field-1'; + $record->type = 'text'; + $field = $gg->create_field($record, $data); + $datarecordid = $gg->create_entry($data, [$field->field->id => 'NERDS NERDS EVERYWHERE, NO BRAIN TO THINK']); + $datarecord = $DB->get_record('data_records', ['id' => $datarecordid]); + + // Ratings. + $ratingoptions = new stdClass; + $ratingoptions->context = context_module::instance($data->cmid); + $ratingoptions->ratingarea = 'entry'; + $ratingoptions->component = 'mod_data'; + $ratingoptions->itemid = $datarecord->id; + $ratingoptions->scaleid = 2; + $ratingoptions->userid = $USER->id; + $rating = new rating($ratingoptions); + $rating->update_rating(2); + $rating = $DB->get_record('rating', ['itemid' => $datarecord->id]); + + // Do backup and restore. + $newcourseid = $this->backup_and_restore($course); + $newdata = $DB->get_record('data', ['course' => $newcourseid]); + + $this->assertFieldsNotRolledForward($data, $newdata, ['timemodified']); + $props = ['assesstimefinish', 'assesstimestart', 'timeavailablefrom', 'timeavailableto', 'timeviewfrom', 'timeviewto']; + $this->assertFieldsRolledForward($data, $newdata, $props); + + $newdatarecord = $DB->get_record('data_records', ['dataid' => $newdata->id]); + $newcm = $DB->get_record('course_modules', ['course' => $newcourseid, 'instance' => $newdata->id]); + + // Data record time checks. + $this->assertEquals($datarecord->timecreated, $newdatarecord->timecreated); + $this->assertEquals($datarecord->timemodified, $newdatarecord->timemodified); + + // Rating test. + $newrating = $DB->get_record('rating', ['contextid' => context_module::instance($newcm->id)->id]); + $this->assertEquals($rating->timecreated, $newrating->timecreated); + $this->assertEquals($rating->timemodified, $newrating->timemodified); + } +} diff --git a/mod/feedback/backup/moodle1/lib.php b/mod/feedback/backup/moodle1/lib.php index 320e8471527..fb859ca3fcf 100644 --- a/mod/feedback/backup/moodle1/lib.php +++ b/mod/feedback/backup/moodle1/lib.php @@ -93,6 +93,8 @@ class moodle1_mod_feedback_handler extends moodle1_mod_handler { $this->moduleid = $cminfo['id']; $contextid = $this->converter->get_contextid(CONTEXT_MODULE, $this->moduleid); + + // get a fresh new file manager for this instance $this->fileman = $this->converter->get_file_manager($contextid, 'mod_feedback'); diff --git a/mod/feedback/backup/moodle2/restore_feedback_stepslib.php b/mod/feedback/backup/moodle2/restore_feedback_stepslib.php index f190b56d52c..1de792de8be 100644 --- a/mod/feedback/backup/moodle2/restore_feedback_stepslib.php +++ b/mod/feedback/backup/moodle2/restore_feedback_stepslib.php @@ -53,9 +53,10 @@ class restore_feedback_activity_structure_step extends restore_activity_structur $oldid = $data->id; $data->course = $this->get_courseid(); + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. $data->timeopen = $this->apply_date_offset($data->timeopen); $data->timeclose = $this->apply_date_offset($data->timeclose); - $data->timemodified = $this->apply_date_offset($data->timemodified); // insert the feedback record $newitemid = $DB->insert_record('feedback', $data); @@ -84,7 +85,6 @@ class restore_feedback_activity_structure_step extends restore_activity_structur $oldid = $data->id; $data->feedback = $this->get_new_parentid('feedback'); $data->userid = $this->get_mappingid('user', $data->userid); - $data->timemodified = $this->apply_date_offset($data->timemodified); if ($this->task->is_samesite() && !empty($data->courseid)) { $data->courseid = $data->courseid; } else if ($this->get_courseid() == SITEID) { diff --git a/mod/feedback/lib.php b/mod/feedback/lib.php index ce93172a655..0946089fbd3 100644 --- a/mod/feedback/lib.php +++ b/mod/feedback/lib.php @@ -695,6 +695,8 @@ function feedback_reset_userdata($data) { // Updating dates - shift may be negative too. if ($data->timeshift) { + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. $shifterror = !shift_course_mod_dates('feedback', array('timeopen', 'timeclose'), $data->timeshift, $data->courseid); $status[] = array('component' => $componentstr, 'item' => get_string('datechanged'), 'error' => $shifterror); } diff --git a/mod/feedback/tests/restore_date_test.php b/mod/feedback/tests/restore_date_test.php new file mode 100644 index 00000000000..9642bfab3a7 --- /dev/null +++ b/mod/feedback/tests/restore_date_test.php @@ -0,0 +1,64 @@ +. + +/** + * Restore date tests. + * + * @package mod_feedback + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . "/phpunit/classes/restore_date_testcase.php"); + +/** + * Restore date tests. + * + * @package mod_feedback + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_feedback_restore_date_testcase extends restore_date_testcase { + + public function test_restore_dates() { + global $DB, $USER; + + $time = 10000; + list($course, $feedback) = $this->create_course_and_module('feedback', ['timeopen' => $time, 'timeclose' => $time]); + + // Create response. + $response = new stdClass(); + $response->feedback = $feedback->id; + $response->userid = $USER->id; + $response->anonymous_response = FEEDBACK_ANONYMOUS_NO; + $response->timemodified = $time; + $completedid = $DB->insert_record('feedback_completed', $response); + $response = $DB->get_record('feedback_completed', array('id' => $completedid), '*', MUST_EXIST); + + // Do backup and restore. + $newcourseid = $this->backup_and_restore($course); + $newfeedback = $DB->get_record('feedback', ['course' => $newcourseid]); + $newresponse = $DB->get_record('feedback_completed', ['feedback' => $newfeedback->id]); + + $this->assertFieldsNotRolledForward($feedback, $newfeedback, ['timemodified']); + $props = ['timeopen', 'timeclose']; + $this->assertFieldsRolledForward($feedback, $newfeedback, $props); + $this->assertEquals($response->timemodified, $newresponse->timemodified); + } +} diff --git a/mod/folder/backup/moodle2/restore_folder_stepslib.php b/mod/folder/backup/moodle2/restore_folder_stepslib.php index 450f882f987..7fc25592d5e 100644 --- a/mod/folder/backup/moodle2/restore_folder_stepslib.php +++ b/mod/folder/backup/moodle2/restore_folder_stepslib.php @@ -46,7 +46,9 @@ class restore_folder_activity_structure_step extends restore_activity_structure_ $data = (object)$data; $oldid = $data->id; $data->course = $this->get_courseid(); - $data->timemodified = $this->apply_date_offset($data->timemodified); + + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. // If showexpanded is not set, apply site default. if (!isset($data->showexpanded)) { diff --git a/mod/folder/lib.php b/mod/folder/lib.php index f07e63fdac1..4c76c551565 100644 --- a/mod/folder/lib.php +++ b/mod/folder/lib.php @@ -65,6 +65,10 @@ function folder_get_extra_capabilities() { * @return array status array */ function folder_reset_userdata($data) { + + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. + return array(); } diff --git a/mod/folder/tests/restore_date_test.php b/mod/folder/tests/restore_date_test.php new file mode 100644 index 00000000000..b18dd6dfb24 --- /dev/null +++ b/mod/folder/tests/restore_date_test.php @@ -0,0 +1,49 @@ +. + +/** + * Restore date tests. + * + * @package mod_folder + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . "/phpunit/classes/restore_date_testcase.php"); + +/** + * Restore date tests. + * + * @package mod_folder + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_folder_restore_date_testcase extends restore_date_testcase { + + public function test_restore_dates() { + global $DB; + + list($course, $folder) = $this->create_course_and_module('folder'); + + // Do backup and restore. + $newcourseid = $this->backup_and_restore($course); + $newfolder = $DB->get_record('folder', ['course' => $newcourseid]); + $this->assertFieldsNotRolledForward($folder, $newfolder, ['timemodified']); + } +} diff --git a/mod/forum/backup/moodle2/restore_forum_stepslib.php b/mod/forum/backup/moodle2/restore_forum_stepslib.php index c0261232474..f7521c02ace 100644 --- a/mod/forum/backup/moodle2/restore_forum_stepslib.php +++ b/mod/forum/backup/moodle2/restore_forum_stepslib.php @@ -60,6 +60,8 @@ class restore_forum_activity_structure_step extends restore_activity_structure_s $oldid = $data->id; $data->course = $this->get_courseid(); + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. $data->assesstimestart = $this->apply_date_offset($data->assesstimestart); $data->assesstimefinish = $this->apply_date_offset($data->assesstimefinish); if ($data->scale < 0) { // scale found, get mapping @@ -83,7 +85,6 @@ class restore_forum_activity_structure_step extends restore_activity_structure_s $data->course = $this->get_courseid(); $data->forum = $this->get_new_parentid('forum'); - $data->timemodified = $this->apply_date_offset($data->timemodified); $data->timestart = $this->apply_date_offset($data->timestart); $data->timeend = $this->apply_date_offset($data->timeend); $data->userid = $this->get_mappingid('user', $data->userid); @@ -101,8 +102,6 @@ class restore_forum_activity_structure_step extends restore_activity_structure_s $oldid = $data->id; $data->discussion = $this->get_new_parentid('forum_discussion'); - $data->created = $this->apply_date_offset($data->created); - $data->modified = $this->apply_date_offset($data->modified); $data->userid = $this->get_mappingid('user', $data->userid); // If post has parent, map it (it has been already restored) if (!empty($data->parent)) { @@ -148,8 +147,6 @@ class restore_forum_activity_structure_step extends restore_activity_structure_s } $data->rating = $data->value; $data->userid = $this->get_mappingid('user', $data->userid); - $data->timecreated = $this->apply_date_offset($data->timecreated); - $data->timemodified = $this->apply_date_offset($data->timemodified); // We need to check that component and ratingarea are both set here. if (empty($data->component)) { diff --git a/mod/forum/lib.php b/mod/forum/lib.php index 929af39ac1e..13e2edc5092 100644 --- a/mod/forum/lib.php +++ b/mod/forum/lib.php @@ -7117,6 +7117,8 @@ function forum_reset_userdata($data) { /// updating dates - shift may be negative too if ($data->timeshift) { + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. shift_course_mod_dates('forum', array('assesstimestart', 'assesstimefinish'), $data->timeshift, $data->courseid); $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false); } diff --git a/mod/forum/tests/restore_date_test.php b/mod/forum/tests/restore_date_test.php new file mode 100644 index 00000000000..64e447cd47d --- /dev/null +++ b/mod/forum/tests/restore_date_test.php @@ -0,0 +1,110 @@ +. + +/** + * Restore date tests. + * + * @package mod_forum + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . "/phpunit/classes/restore_date_testcase.php"); +require_once($CFG->dirroot . '/rating/lib.php'); + +/** + * Restore date tests. + * + * @package mod_forum + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_forum_restore_date_testcase extends restore_date_testcase { + + /** + * Test restore dates. + */ + public function test_restore_dates() { + global $DB, $USER; + + $gg = $this->getDataGenerator()->get_plugin_generator('mod_forum'); + $record = ['assesstimefinish' => 100, 'assesstimestart' => 100, 'ratingtime' => 1, 'assessed' => 2, 'scale' => 1]; + list($course, $forum) = $this->create_course_and_module('forum', $record); + + // Forum Discussions/posts/ratings. + $timestamp = 996699; + $diff = $this->get_diff(); + $record = new stdClass(); + $record->course = $course->id; + $record->userid = $USER->id; + $record->forum = $forum->id; + $record->timestart = $record->timeend = $record->timemodified = $timestamp; + $discussion = $gg->create_discussion($record); + + $record = new stdClass(); + $record->discussion = $discussion->id; + $record->parent = $discussion->firstpost; + $record->userid = $USER->id; + $record->created = $record->modified = $timestamp; + $post = $gg->create_post($record); + + // Time modified is changed internally. + $DB->set_field('forum_discussions', 'timemodified', $timestamp); + + // Ratings. + $ratingoptions = new stdClass; + $ratingoptions->context = context_module::instance($forum->cmid); + $ratingoptions->ratingarea = 'post'; + $ratingoptions->component = 'mod_forum'; + $ratingoptions->itemid = $post->id; + $ratingoptions->scaleid = 2; + $ratingoptions->userid = $USER->id; + $rating = new rating($ratingoptions); + $rating->update_rating(2); + $rating = $DB->get_record('rating', ['itemid' => $post->id]); + + // Do backup and restore. + $newcourseid = $this->backup_and_restore($course); + $newforum = $DB->get_record('forum', ['course' => $newcourseid]); + + $this->assertFieldsNotRolledForward($forum, $newforum, ['timemodified']); + $props = ['assesstimefinish', 'assesstimestart']; + $this->assertFieldsRolledForward($forum, $newforum, $props); + + $newdiscussion = $DB->get_record('forum_discussions', ['forum' => $newforum->id]); + $newposts = $DB->get_records('forum_posts', ['discussion' => $newdiscussion->id]); + $newcm = $DB->get_record('course_modules', ['course' => $newcourseid, 'instance' => $newforum->id]); + + // Forum discussion time checks. + $this->assertEquals($timestamp + $diff, $newdiscussion->timestart); + $this->assertEquals($timestamp + $diff, $newdiscussion->timeend); + $this->assertEquals($timestamp, $newdiscussion->timemodified); + + // Posts test. + foreach ($newposts as $post) { + $this->assertEquals($timestamp, $post->created); + $this->assertEquals($timestamp, $post->modified); + } + + // Rating test. + $newrating = $DB->get_record('rating', ['contextid' => context_module::instance($newcm->id)->id]); + $this->assertEquals($rating->timecreated, $newrating->timecreated); + $this->assertEquals($rating->timemodified, $newrating->timemodified); + } +} diff --git a/mod/glossary/backup/moodle2/restore_glossary_stepslib.php b/mod/glossary/backup/moodle2/restore_glossary_stepslib.php index ae42eca2ab6..b4f750d0a9b 100644 --- a/mod/glossary/backup/moodle2/restore_glossary_stepslib.php +++ b/mod/glossary/backup/moodle2/restore_glossary_stepslib.php @@ -58,6 +58,8 @@ class restore_glossary_activity_structure_step extends restore_activity_structur $oldid = $data->id; $data->course = $this->get_courseid(); + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. $data->assesstimestart = $this->apply_date_offset($data->assesstimestart); $data->assesstimefinish = $this->apply_date_offset($data->assesstimefinish); if ($data->scale < 0) { // scale found, get mapping @@ -88,9 +90,6 @@ class restore_glossary_activity_structure_step extends restore_activity_structur $data->userid = $this->get_mappingid('user', $data->userid); $data->sourceglossaryid = $this->get_mappingid('glossary', $data->sourceglossaryid); - $data->timecreated = $this->apply_date_offset($data->timecreated); - $data->timemodified = $this->apply_date_offset($data->timemodified); - // insert the entry record $newitemid = $DB->insert_record('glossary_entries', $data); $this->set_mapping('glossary_entry', $oldid, $newitemid, true); // childs and files by itemname @@ -120,8 +119,6 @@ class restore_glossary_activity_structure_step extends restore_activity_structur } $data->rating = $data->value; $data->userid = $this->get_mappingid('user', $data->userid); - $data->timecreated = $this->apply_date_offset($data->timecreated); - $data->timemodified = $this->apply_date_offset($data->timemodified); // Make sure that we have both component and ratingarea set. These were added in 2.1. // Prior to that all ratings were for entries so we know what to set them too. diff --git a/mod/glossary/lib.php b/mod/glossary/lib.php index b1555868923..a942525a265 100644 --- a/mod/glossary/lib.php +++ b/mod/glossary/lib.php @@ -3016,6 +3016,8 @@ function glossary_reset_userdata($data) { /// updating dates - shift may be negative too if ($data->timeshift) { + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. shift_course_mod_dates('glossary', array('assesstimestart', 'assesstimefinish'), $data->timeshift, $data->courseid); $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false); } diff --git a/mod/glossary/tests/restore_date_test.php b/mod/glossary/tests/restore_date_test.php new file mode 100644 index 00000000000..e6d220c3eae --- /dev/null +++ b/mod/glossary/tests/restore_date_test.php @@ -0,0 +1,91 @@ +. + +/** + * Restore date tests. + * + * @package mod_glossary + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . "/phpunit/classes/restore_date_testcase.php"); +require_once($CFG->dirroot . '/rating/lib.php'); + +/** + * Restore date tests. + * + * @package mod_glossary + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_glossary_restore_date_testcase extends restore_date_testcase { + + /** + * Test restore dates. + */ + public function test_restore_dates() { + global $DB, $USER; + + $gg = $this->getDataGenerator()->get_plugin_generator('mod_glossary'); + $record = ['assesstimefinish' => 100, 'assesstimestart' => 100, 'ratingtime' => 1, 'assessed' => 2, 'scale' => 1]; + list($course, $glossary) = $this->create_course_and_module('glossary', $record); + + // Glossary entries. + $entry1 = $gg->create_content($glossary, array('approved' => 1)); + $gg->create_content($glossary, array('approved' => 0, 'userid' => $USER->id)); + $gg->create_content($glossary, array('approved' => 0, 'userid' => -1)); + $gg->create_content($glossary, array('approved' => 1)); + $timestamp = 10000; + $DB->set_field('glossary_entries', 'timecreated', $timestamp); + $DB->set_field('glossary_entries', 'timemodified', $timestamp); + $ratingoptions = new stdClass; + $ratingoptions->context = context_module::instance($glossary->cmid); + $ratingoptions->ratingarea = 'entry'; + $ratingoptions->component = 'mod_glossary'; + $ratingoptions->itemid = $entry1->id; + $ratingoptions->scaleid = 2; + $ratingoptions->userid = $USER->id; + $rating = new rating($ratingoptions); + $rating->update_rating(2); + $rating = $DB->get_record('rating', ['itemid' => $entry1->id]); + + // Do backup and restore. + $newcourseid = $this->backup_and_restore($course); + $newglossary = $DB->get_record('glossary', ['course' => $newcourseid]); + + $this->assertFieldsNotRolledForward($glossary, $newglossary, ['timecreated', 'timemodified']); + $props = ['assesstimefinish', 'assesstimestart']; + $this->assertFieldsRolledForward($glossary, $newglossary, $props); + + $newentries = $DB->get_records('glossary_entries', ['glossaryid' => $newglossary->id]); + $newcm = $DB->get_record('course_modules', ['course' => $newcourseid, 'instance' => $newglossary->id]); + + // Entries test. + foreach ($newentries as $entry) { + $this->assertEquals($timestamp, $entry->timecreated); + $this->assertEquals($timestamp, $entry->timemodified); + } + + // Rating test. + $newrating = $DB->get_record('rating', ['contextid' => context_module::instance($newcm->id)->id]); + $this->assertEquals($rating->timecreated, $newrating->timecreated); + $this->assertEquals($rating->timemodified, $newrating->timemodified); + } +} diff --git a/mod/imscp/backup/moodle2/restore_imscp_stepslib.php b/mod/imscp/backup/moodle2/restore_imscp_stepslib.php index 2f9cd58894d..055aefb597d 100644 --- a/mod/imscp/backup/moodle2/restore_imscp_stepslib.php +++ b/mod/imscp/backup/moodle2/restore_imscp_stepslib.php @@ -46,7 +46,9 @@ class restore_imscp_activity_structure_step extends restore_activity_structure_s $data = (object)$data; $oldid = $data->id; $data->course = $this->get_courseid(); - $data->timemodified = $this->apply_date_offset($data->timemodified); + + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. // Insert the imscp record. $newitemid = $DB->insert_record('imscp', $data); diff --git a/mod/imscp/lib.php b/mod/imscp/lib.php index 7fc8a6e0d5c..1a7eea2c440 100644 --- a/mod/imscp/lib.php +++ b/mod/imscp/lib.php @@ -60,6 +60,10 @@ function imscp_get_extra_capabilities() { * @return array status array */ function imscp_reset_userdata($data) { + + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. + return array(); } diff --git a/mod/imscp/tests/restore_date_test.php b/mod/imscp/tests/restore_date_test.php new file mode 100644 index 00000000000..f324863a30a --- /dev/null +++ b/mod/imscp/tests/restore_date_test.php @@ -0,0 +1,49 @@ +. + +/** + * Restore date tests. + * + * @package mod_imscp + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . "/phpunit/classes/restore_date_testcase.php"); + +/** + * Restore date tests. + * + * @package mod_imscp + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_imscp_restore_date_testcase extends restore_date_testcase { + + public function test_restore_dates() { + global $DB; + + list($course, $imscp) = $this->create_course_and_module('imscp'); + + // Do backup and restore. + $newcourseid = $this->backup_and_restore($course); + $newimscp = $DB->get_record('imscp', ['course' => $newcourseid]); + $this->assertFieldsNotRolledForward($imscp, $newimscp, ['timemodified']); + } +} diff --git a/mod/label/backup/moodle2/restore_label_stepslib.php b/mod/label/backup/moodle2/restore_label_stepslib.php index b08698dea7a..96f9c8a07ed 100644 --- a/mod/label/backup/moodle2/restore_label_stepslib.php +++ b/mod/label/backup/moodle2/restore_label_stepslib.php @@ -47,6 +47,9 @@ class restore_label_activity_structure_step extends restore_activity_structure_s $oldid = $data->id; $data->course = $this->get_courseid(); + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. + // insert the label record $newitemid = $DB->insert_record('label', $data); // immediately after inserting "activity" record, call this diff --git a/mod/label/lib.php b/mod/label/lib.php index fffe2af78f7..c67ec72e208 100644 --- a/mod/label/lib.php +++ b/mod/label/lib.php @@ -156,6 +156,10 @@ function label_get_coursemodule_info($coursemodule) { * @return array status array */ function label_reset_userdata($data) { + + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. + return array(); } diff --git a/mod/lesson/backup/moodle2/restore_lesson_stepslib.php b/mod/lesson/backup/moodle2/restore_lesson_stepslib.php index 1edbf1cf783..d4217296a14 100644 --- a/mod/lesson/backup/moodle2/restore_lesson_stepslib.php +++ b/mod/lesson/backup/moodle2/restore_lesson_stepslib.php @@ -62,9 +62,10 @@ class restore_lesson_activity_structure_step extends restore_activity_structure_ $oldid = $data->id; $data->course = $this->get_courseid(); + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. $data->available = $this->apply_date_offset($data->available); $data->deadline = $this->apply_date_offset($data->deadline); - $data->timemodified = $this->apply_date_offset($data->timemodified); // The lesson->highscore code was removed in MDL-49581. // Remove it if found in the backup file. @@ -110,8 +111,6 @@ class restore_lesson_activity_structure_step extends restore_activity_structure_ $data->lessonid = $this->get_new_parentid('lesson'); // We'll remap all the prevpageid and nextpageid at the end, once all pages have been created - $data->timemodified = $this->apply_date_offset($data->timemodified); - $data->timecreated = $this->apply_date_offset($data->timecreated); $newitemid = $DB->insert_record('lesson_pages', $data); $this->set_mapping('lesson_page', $oldid, $newitemid, true); // Has related fileareas @@ -124,8 +123,6 @@ class restore_lesson_activity_structure_step extends restore_activity_structure_ $data->lessonid = $this->get_new_parentid('lesson'); $data->pageid = $this->get_new_parentid('lesson_page'); $data->answer = $data->answer_text; - $data->timemodified = $this->apply_date_offset($data->timemodified); - $data->timecreated = $this->apply_date_offset($data->timecreated); // Set a dummy mapping to get the old ID so that it can be used by get_old_parentid when // processing attempts. It will be corrected in after_execute @@ -147,7 +144,6 @@ class restore_lesson_activity_structure_step extends restore_activity_structure_ // We use the old answerid here as the answer isn't created until after_execute $data->answerid = $this->get_old_parentid('lesson_answer'); $data->userid = $this->get_mappingid('user', $data->userid); - $data->timeseen = $this->apply_date_offset($data->timeseen); $newitemid = $DB->insert_record('lesson_attempts', $data); $this->set_mapping('lesson_attempt', $oldid, $newitemid, true); // Has related fileareas. @@ -160,7 +156,6 @@ class restore_lesson_activity_structure_step extends restore_activity_structure_ $oldid = $data->id; $data->lessonid = $this->get_new_parentid('lesson'); $data->userid = $this->get_mappingid('user', $data->userid); - $data->completed = $this->apply_date_offset($data->completed); $newitemid = $DB->insert_record('lesson_grades', $data); $this->set_mapping('lesson_grade', $oldid, $newitemid); @@ -174,7 +169,6 @@ class restore_lesson_activity_structure_step extends restore_activity_structure_ $data->lessonid = $this->get_new_parentid('lesson'); $data->pageid = $this->get_new_parentid('lesson_page'); $data->userid = $this->get_mappingid('user', $data->userid); - $data->timeseen = $this->apply_date_offset($data->timeseen); $newitemid = $DB->insert_record('lesson_branch', $data); } @@ -191,8 +185,6 @@ class restore_lesson_activity_structure_step extends restore_activity_structure_ $oldid = $data->id; $data->lessonid = $this->get_new_parentid('lesson'); $data->userid = $this->get_mappingid('user', $data->userid); - $data->starttime = $this->apply_date_offset($data->starttime); - $data->lessontime = $this->apply_date_offset($data->lessontime); // Supply item that maybe missing from previous versions. if (!isset($data->completed)) { $data->completed = 0; diff --git a/mod/lesson/lib.php b/mod/lesson/lib.php index 38aa4b026e9..8562aeb1ebd 100644 --- a/mod/lesson/lib.php +++ b/mod/lesson/lib.php @@ -1120,6 +1120,8 @@ function lesson_reset_userdata($data) { WHERE lessonid IN (SELECT id FROM {lesson} WHERE course = ?) AND deadline <> 0", array($data->timeshift, $data->courseid)); + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. shift_course_mod_dates('lesson', array('available', 'deadline'), $data->timeshift, $data->courseid); $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false); } diff --git a/mod/lesson/tests/restore_date_test.php b/mod/lesson/tests/restore_date_test.php new file mode 100644 index 00000000000..e6a8208a7a5 --- /dev/null +++ b/mod/lesson/tests/restore_date_test.php @@ -0,0 +1,190 @@ +. + +/** + * Restore date tests. + * + * @package mod_lesson + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . "/phpunit/classes/restore_date_testcase.php"); + +/** + * Restore date tests. + * + * @package mod_lesson + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_lesson_restore_date_testcase extends restore_date_testcase { + + /** + * Creates an attempt for the given userwith a correct or incorrect answer and optionally finishes it. + * + * TODO This api can be better extracted to a generator. + * + * @param stdClass $lesson Lesson object. + * @param stdClass $page page object. + * @param boolean $correct If the answer should be correct. + * @param boolean $finished If we should finish the attempt. + * + * @return array the result of the attempt creation or finalisation. + */ + protected function create_attempt($lesson, $page, $correct = true, $finished = false) { + global $DB, $USER; + + // First we need to launch the lesson so the timer is on. + mod_lesson_external::launch_attempt($lesson->id); + + $DB->set_field('lesson', 'feedback', 1, array('id' => $lesson->id)); + $DB->set_field('lesson', 'progressbar', 1, array('id' => $lesson->id)); + $DB->set_field('lesson', 'custom', 0, array('id' => $lesson->id)); + $DB->set_field('lesson', 'maxattempts', 3, array('id' => $lesson->id)); + + $answercorrect = 0; + $answerincorrect = 0; + $p2answers = $DB->get_records('lesson_answers', array('lessonid' => $lesson->id, 'pageid' => $page->id), 'id'); + foreach ($p2answers as $answer) { + if ($answer->jumpto == 0) { + $answerincorrect = $answer->id; + } else { + $answercorrect = $answer->id; + } + } + + $data = array( + array( + 'name' => 'answerid', + 'value' => $correct ? $answercorrect : $answerincorrect, + ), + array( + 'name' => '_qf__lesson_display_answer_form_truefalse', + 'value' => 1, + ) + ); + $result = mod_lesson_external::process_page($lesson->id, $page->id, $data); + $result = external_api::clean_returnvalue(mod_lesson_external::process_page_returns(), $result); + + // Create attempt. + $newpageattempt = [ + 'lessonid' => $lesson->id, + 'pageid' => $page->id, + 'userid' => $USER->id, + 'answerid' => $answercorrect, + 'retry' => 1, // First attempt is always 0. + 'correct' => 1, + 'useranswer' => '1', + 'timeseen' => time(), + ]; + $DB->insert_record('lesson_attempts', (object) $newpageattempt); + + if ($finished) { + $result = mod_lesson_external::finish_attempt($lesson->id); + $result = external_api::clean_returnvalue(mod_lesson_external::finish_attempt_returns(), $result); + } + return $result; + } + + /** + * Test restore dates. + */ + public function test_restore_dates() { + global $DB, $USER; + + // Create lesson data. + $record = ['available' => 100, 'deadline' => 100, 'timemodified' => 100]; + list($course, $lesson) = $this->create_course_and_module('lesson', $record); + $lessongenerator = $this->getDataGenerator()->get_plugin_generator('mod_lesson'); + $page = $lessongenerator->create_content($lesson); + $page2 = $lessongenerator->create_question_truefalse($lesson); + $this->create_attempt($lesson, $page2, true, true); + + $timer = $DB->get_record('lesson_timer', ['lessonid' => $lesson->id]); + // Lesson grade. + $timestamp = 100; + $grade = new stdClass(); + $grade->lessonid = $lesson->id; + $grade->userid = $USER->id; + $grade->grade = 8.9; + $grade->completed = $timestamp; + $grade->id = $DB->insert_record('lesson_grades', $grade); + + // User override. + $override = (object)[ + 'lessonid' => $lesson->id, + 'groupid' => 0, + 'userid' => $USER->id, + 'sortorder' => 1, + 'available' => 100, + 'deadline' => 200 + ]; + $DB->insert_record('lesson_overrides', $override); + + // Set time fields to a constant for easy validation. + $DB->set_field('lesson_pages', 'timecreated', $timestamp); + $DB->set_field('lesson_pages', 'timemodified', $timestamp); + $DB->set_field('lesson_answers', 'timecreated', $timestamp); + $DB->set_field('lesson_answers', 'timemodified', $timestamp); + $DB->set_field('lesson_attempts', 'timeseen', $timestamp); + + // Do backup and restore. + $newcourseid = $this->backup_and_restore($course); + $newlesson = $DB->get_record('lesson', ['course' => $newcourseid]); + + $this->assertFieldsNotRolledForward($lesson, $newlesson, ['timemodified']); + $props = ['available', 'deadline']; + $this->assertFieldsRolledForward($lesson, $newlesson, $props); + + $newpages = $DB->get_records('lesson_pages', ['lessonid' => $newlesson->id]); + $newanswers = $DB->get_records('lesson_answers', ['lessonid' => $newlesson->id]); + $newgrade = $DB->get_record('lesson_grades', ['lessonid' => $newlesson->id]); + $newoverride = $DB->get_record('lesson_overrides', ['lessonid' => $newlesson->id]); + $newtimer = $DB->get_record('lesson_timer', ['lessonid' => $newlesson->id]); + $newattempt = $DB->get_record('lesson_attempts', ['lessonid' => $newlesson->id]); + + // Page time checks. + foreach ($newpages as $newpage) { + $this->assertEquals($timestamp, $newpage->timemodified); + $this->assertEquals($timestamp, $newpage->timecreated); + } + + // Page answers time checks. + foreach ($newanswers as $newanswer) { + $this->assertEquals($timestamp, $newanswer->timemodified); + $this->assertEquals($timestamp, $newanswer->timecreated); + } + + // Lesson override time checks. + $diff = $this->get_diff(); + $this->assertEquals($override->available + $diff, $newoverride->available); + $this->assertEquals($override->deadline + $diff, $newoverride->deadline); + + // Lesson grade time checks. + $this->assertEquals($timestamp, $newgrade->completed); + + // Lesson timer time checks. + $this->assertEquals($timer->starttime, $newtimer->starttime); + $this->assertEquals($timer->lessontime, $newtimer->lessontime); + + // Lesson attempt time check. + $this->assertEquals($timestamp, $newattempt->timeseen); + } +} diff --git a/mod/lti/backup/moodle2/restore_lti_stepslib.php b/mod/lti/backup/moodle2/restore_lti_stepslib.php index 989be59ca10..5adac68d9c7 100644 --- a/mod/lti/backup/moodle2/restore_lti_stepslib.php +++ b/mod/lti/backup/moodle2/restore_lti_stepslib.php @@ -75,6 +75,9 @@ class restore_lti_activity_structure_step extends restore_activity_structure_ste $data->course = $this->get_courseid(); $data->servicesalt = uniqid('', true); + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. + // Grade used to be a float (whole numbers only), restore as int. $data->grade = (int) $data->grade; diff --git a/mod/page/backup/moodle2/restore_page_stepslib.php b/mod/page/backup/moodle2/restore_page_stepslib.php index 9c9e16cbca0..2d1d8dc59c6 100644 --- a/mod/page/backup/moodle2/restore_page_stepslib.php +++ b/mod/page/backup/moodle2/restore_page_stepslib.php @@ -47,6 +47,9 @@ class restore_page_activity_structure_step extends restore_activity_structure_st $oldid = $data->id; $data->course = $this->get_courseid(); + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. + // insert the page record $newitemid = $DB->insert_record('page', $data); // immediately after inserting "activity" record, call this diff --git a/mod/page/lib.php b/mod/page/lib.php index 12bbdfc789b..67135ad02c0 100644 --- a/mod/page/lib.php +++ b/mod/page/lib.php @@ -58,6 +58,10 @@ function page_get_extra_capabilities() { * @return array status array */ function page_reset_userdata($data) { + + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. + return array(); } diff --git a/mod/quiz/backup/moodle2/restore_quiz_stepslib.php b/mod/quiz/backup/moodle2/restore_quiz_stepslib.php index 5c1cc9abd4e..d695ca15138 100644 --- a/mod/quiz/backup/moodle2/restore_quiz_stepslib.php +++ b/mod/quiz/backup/moodle2/restore_quiz_stepslib.php @@ -101,10 +101,11 @@ class restore_quiz_activity_structure_step extends restore_questions_activity_st $oldid = $data->id; $data->course = $this->get_courseid(); + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. + $data->timeopen = $this->apply_date_offset($data->timeopen); $data->timeclose = $this->apply_date_offset($data->timeclose); - $data->timecreated = $this->apply_date_offset($data->timecreated); - $data->timemodified = $this->apply_date_offset($data->timemodified); if (property_exists($data, 'questions')) { // Needed by {@link process_quiz_attempt_legacy}, in which case it will be present. @@ -359,8 +360,6 @@ class restore_quiz_activity_structure_step extends restore_questions_activity_st $data->userid = $this->get_mappingid('user', $data->userid); $data->grade = $data->gradeval; - $data->timemodified = $this->apply_date_offset($data->timemodified); - $DB->insert_record('quiz_grades', $data); } @@ -372,10 +371,6 @@ class restore_quiz_activity_structure_step extends restore_questions_activity_st $data->userid = $this->get_mappingid('user', $data->userid); - $data->timestart = $this->apply_date_offset($data->timestart); - $data->timefinish = $this->apply_date_offset($data->timefinish); - $data->timemodified = $this->apply_date_offset($data->timemodified); - if (!empty($data->timecheckstate)) { $data->timecheckstate = $this->apply_date_offset($data->timecheckstate); } else { diff --git a/mod/quiz/lib.php b/mod/quiz/lib.php index ced1a1b78b9..3940908d2d2 100644 --- a/mod/quiz/lib.php +++ b/mod/quiz/lib.php @@ -1556,6 +1556,8 @@ function quiz_reset_userdata($data) { WHERE quiz IN (SELECT id FROM {quiz} WHERE course = ?) AND timeclose <> 0", array($data->timeshift, $data->courseid)); + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. shift_course_mod_dates('quiz', array('timeopen', 'timeclose'), $data->timeshift, $data->courseid); diff --git a/mod/quiz/tests/restore_date_test.php b/mod/quiz/tests/restore_date_test.php new file mode 100644 index 00000000000..d64f7478a18 --- /dev/null +++ b/mod/quiz/tests/restore_date_test.php @@ -0,0 +1,116 @@ +. + +/** + * Restore date tests. + * + * @package mod_quiz + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . "/phpunit/classes/restore_date_testcase.php"); + +/** + * Restore date tests. + * + * @package mod_quiz + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_quiz_restore_date_testcase extends restore_date_testcase +{ + + /** + * Test restore dates. + */ + public function test_restore_dates() { + global $DB, $USER; + + // Create quiz data. + $record = ['timeopen' => 100, 'timeclose' => 100, 'timemodified' => 100, 'tiemcreated' => 100, 'questionsperpage' => 0, + 'grade' => 100.0, 'sumgrades' => 2]; + list($course, $quiz) = $this->create_course_and_module('quiz', $record); + + // Create questions. + $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); + $cat = $questiongenerator->create_question_category(); + $saq = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id)); + // Add to the quiz. + quiz_add_quiz_question($saq->id, $quiz); + + // Create an attempt. + $timestamp = 100; + $quizobj = quiz::create($quiz->id); + $attempt = quiz_create_attempt($quizobj, 1, false, $timestamp, false); + $quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context()); + $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour); + quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timestamp); + quiz_attempt_save_started($quizobj, $quba, $attempt); + + // Quiz grade. + $grade = new stdClass(); + $grade->quiz = $quiz->id; + $grade->userid = $USER->id; + $grade->grade = 8.9; + $grade->timemodified = $timestamp; + $grade->id = $DB->insert_record('quiz_grades', $grade); + + // User override. + $override = (object)[ + 'quiz' => $quiz->id, + 'groupid' => 0, + 'userid' => $USER->id, + 'sortorder' => 1, + 'timeopen' => 100, + 'timeclose' => 200 + ]; + $DB->insert_record('quiz_overrides', $override); + + // Set time fields to a constant for easy validation. + $DB->set_field('quiz_attempts', 'timefinish', $timestamp); + + // Do backup and restore. + $newcourseid = $this->backup_and_restore($course); + $newquiz = $DB->get_record('quiz', ['course' => $newcourseid]); + + $this->assertFieldsNotRolledForward($quiz, $newquiz, ['timecreated', 'timemodified']); + $props = ['timeclose', 'timeopen']; + $this->assertFieldsRolledForward($quiz, $newquiz, $props); + + $newattempt = $DB->get_record('quiz_attempts', ['quiz' => $newquiz->id]); + $newoverride = $DB->get_record('quiz_overrides', ['quiz' => $newquiz->id]); + $newgrade = $DB->get_record('quiz_grades', ['quiz' => $newquiz->id]); + + // Attempt time checks. + $diff = $this->get_diff(); + $this->assertEquals($timestamp, $newattempt->timemodified); + $this->assertEquals($timestamp, $newattempt->timefinish); + $this->assertEquals($timestamp, $newattempt->timestart); + $this->assertEquals($timestamp + $diff, $newattempt->timecheckstate); // Should this be rolled? + + // Quiz override time checks. + $diff = $this->get_diff(); + $this->assertEquals($override->timeopen + $diff, $newoverride->timeopen); + $this->assertEquals($override->timeclose + $diff, $newoverride->timeclose); + + // Quiz grade time checks. + $this->assertEquals($grade->timemodified, $newgrade->timemodified); + } +} diff --git a/mod/resource/backup/moodle2/restore_resource_stepslib.php b/mod/resource/backup/moodle2/restore_resource_stepslib.php index 2145ba56464..46eeb027ff9 100644 --- a/mod/resource/backup/moodle2/restore_resource_stepslib.php +++ b/mod/resource/backup/moodle2/restore_resource_stepslib.php @@ -46,7 +46,9 @@ class restore_resource_activity_structure_step extends restore_activity_structur $data = (object)$data; $oldid = $data->id; $data->course = $this->get_courseid(); - $data->timemodified = $this->apply_date_offset($data->timemodified); + + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. // insert the resource record $newitemid = $DB->insert_record('resource', $data); diff --git a/mod/resource/lib.php b/mod/resource/lib.php index 104b5f09985..dd5537522ff 100644 --- a/mod/resource/lib.php +++ b/mod/resource/lib.php @@ -58,6 +58,10 @@ function resource_get_extra_capabilities() { * @return array status array */ function resource_reset_userdata($data) { + + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. + return array(); } diff --git a/mod/resource/tests/restore_date_test.php b/mod/resource/tests/restore_date_test.php new file mode 100644 index 00000000000..63166f9c93b --- /dev/null +++ b/mod/resource/tests/restore_date_test.php @@ -0,0 +1,51 @@ +. + +/** + * Restore date tests. + * + * @package mod_resource + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . "/phpunit/classes/restore_date_testcase.php"); + +/** + * Restore date tests. + * + * @package mod_resource + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_resource_restore_date_testcase extends restore_date_testcase { + + public function test_restore_dates() { + global $DB; + + $time = 10000; + + list($course, $resource) = $this->create_course_and_module('resource'); + + // Do backup and restore. + $newcourseid = $this->backup_and_restore($course); + $newresource = $DB->get_record('resource', ['course' => $newcourseid]); + $this->assertFieldsNotRolledForward($resource, $newresource, ['timemodified']); + } +} diff --git a/mod/scorm/backup/moodle2/restore_scorm_stepslib.php b/mod/scorm/backup/moodle2/restore_scorm_stepslib.php index 02b620c5a00..34c9b980772 100644 --- a/mod/scorm/backup/moodle2/restore_scorm_stepslib.php +++ b/mod/scorm/backup/moodle2/restore_scorm_stepslib.php @@ -61,9 +61,10 @@ class restore_scorm_activity_structure_step extends restore_activity_structure_s $data->course = $this->get_courseid(); + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. $data->timeopen = $this->apply_date_offset($data->timeopen); $data->timeclose = $this->apply_date_offset($data->timeclose); - $data->timemodified = $this->apply_date_offset($data->timemodified); if (!isset($data->displayactivityname)) { $data->displayactivityname = true; @@ -182,7 +183,6 @@ class restore_scorm_activity_structure_step extends restore_activity_structure_s $data->scormid = $this->get_new_parentid('scorm'); $data->scoid = $this->get_new_parentid('scorm_sco'); $data->userid = $this->get_mappingid('user', $data->userid); - $data->timemodified = $this->apply_date_offset($data->timemodified); $newitemid = $DB->insert_record('scorm_scoes_track', $data); // No need to save this mapping as far as nothing depend on it diff --git a/mod/scorm/tests/restore_date_test.php b/mod/scorm/tests/restore_date_test.php new file mode 100644 index 00000000000..da2d39b583f --- /dev/null +++ b/mod/scorm/tests/restore_date_test.php @@ -0,0 +1,65 @@ +. + +/** + * Restore date tests. + * + * @package mod_scorm + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . "/phpunit/classes/restore_date_testcase.php"); + +/** + * Restore date tests. + * + * @package mod_scorm + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_scorm_restore_date_testcase extends restore_date_testcase { + + public function test_restore_dates() { + global $DB, $USER; + + $time = 10000; + + list($course, $scorm) = $this->create_course_and_module('scorm', ['timeopen' => $time, 'timeclose' => $time]); + $scoes = scorm_get_scoes($scorm->id); + $sco = array_shift($scoes); + scorm_insert_track($USER->id, $scorm->id, $sco->id, 4, 'cmi.core.score.raw', 10); + + // We do not want second differences to fail our test because of execution delays. + $DB->set_field('scorm_scoes_track', 'timemodified', $time); + + // Do backup and restore. + $newcourseid = $this->backup_and_restore($course); + $newscorm = $DB->get_record('scorm', ['course' => $newcourseid]); + + $this->assertFieldsNotRolledForward($scorm, $newscorm, ['timemodified']); + $props = ['timeopen', 'timeclose']; + $this->assertFieldsRolledForward($scorm, $newscorm, $props); + + $tracks = $DB->get_records('scorm_scoes_track', ['scormid' => $newscorm->id]); + foreach ($tracks as $track) { + $this->assertEquals($time, $track->timemodified); + } + } +} diff --git a/mod/survey/backup/moodle2/restore_survey_stepslib.php b/mod/survey/backup/moodle2/restore_survey_stepslib.php index 823b765f384..a4de735990c 100644 --- a/mod/survey/backup/moodle2/restore_survey_stepslib.php +++ b/mod/survey/backup/moodle2/restore_survey_stepslib.php @@ -52,8 +52,9 @@ class restore_survey_activity_structure_step extends restore_activity_structure_ $data = (object)$data; $oldid = $data->id; $data->course = $this->get_courseid(); - $data->timemodified = $this->apply_date_offset($data->timemodified); - $data->timecreated = $this->apply_date_offset($data->timecreated); + + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. // insert the survey record $newitemid = $DB->insert_record('survey', $data); @@ -81,7 +82,6 @@ class restore_survey_activity_structure_step extends restore_activity_structure_ $oldid = $data->id; $data->survey = $this->get_new_parentid('survey'); $data->userid = $this->get_mappingid('user', $data->userid); - $data->time = $this->apply_date_offset($data->time); $newitemid = $DB->insert_record('survey_answers', $data); // No need to save this mapping as far as nothing depend on it diff --git a/mod/survey/lib.php b/mod/survey/lib.php index f7dcc336051..db773b581cf 100644 --- a/mod/survey/lib.php +++ b/mod/survey/lib.php @@ -762,7 +762,10 @@ function survey_reset_userdata($data) { $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallanswers', 'survey'), 'error'=>false); } - // no date shifting + // No date shifting. + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. + return $status; } diff --git a/mod/survey/tests/restore_date_test.php b/mod/survey/tests/restore_date_test.php new file mode 100644 index 00000000000..9356a476685 --- /dev/null +++ b/mod/survey/tests/restore_date_test.php @@ -0,0 +1,79 @@ +. + +/** + * Restore date tests. + * + * @package mod_survey + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . "/phpunit/classes/restore_date_testcase.php"); + +/** + * Restore date tests. + * + * @package mod_survey + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_survey_restore_date_testcase extends restore_date_testcase { + + public function test_restore_dates() { + global $DB; + + list($course, $survey) = $this->create_course_and_module('survey'); + $context = context_module::instance($survey->cmid); + + // Build our questions and responses array. + $realquestions = array(); + $questions = survey_get_questions($survey); + $i = 5; + foreach ($questions as $q) { + if ($q->type > 0) { + if ($q->multi) { + $subquestions = survey_get_subquestions($q); + foreach ($subquestions as $sq) { + $key = 'q' . $sq->id; + $realquestions[$key] = $i % 5 + 1; + $i++; + } + } else { + $key = 'q' . $q->id; + $realquestions[$key] = $i % 5 + 1; + $i++; + } + } + } + survey_save_answers($survey, $realquestions, $course, $context); + // We do not want second differences to fail our test because of execution delays. + $DB->set_field('survey_answers', 'time', $this->startdate); + + // Do backup and restore. + $newcourseid = $this->backup_and_restore($course); + $newsurvey = $DB->get_record('survey', ['course' => $newcourseid]); + $this->assertFieldsNotRolledForward($survey, $newsurvey, ['timecreated', 'timemodified']); + + $answers = $DB->get_records('survey_answers', ['survey' => $newsurvey->id]); + foreach ($answers as $answer) { + $this->assertEquals($this->startdate, $answer->time); + } + } +} diff --git a/mod/url/backup/moodle2/restore_url_stepslib.php b/mod/url/backup/moodle2/restore_url_stepslib.php index 8eaf8eefb75..82ebbfd9c1a 100644 --- a/mod/url/backup/moodle2/restore_url_stepslib.php +++ b/mod/url/backup/moodle2/restore_url_stepslib.php @@ -47,6 +47,9 @@ class restore_url_activity_structure_step extends restore_activity_structure_ste $oldid = $data->id; $data->course = $this->get_courseid(); + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. + // insert the url record $newitemid = $DB->insert_record('url', $data); // immediately after inserting "activity" record, call this diff --git a/mod/url/lib.php b/mod/url/lib.php index f9bf4df98d3..5aed89da0fb 100644 --- a/mod/url/lib.php +++ b/mod/url/lib.php @@ -60,6 +60,10 @@ function url_get_extra_capabilities() { * @return array status array */ function url_reset_userdata($data) { + + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. + return array(); } diff --git a/mod/wiki/backup/moodle2/restore_wiki_stepslib.php b/mod/wiki/backup/moodle2/restore_wiki_stepslib.php index 9f43f23cbf8..4a93bcffc6d 100644 --- a/mod/wiki/backup/moodle2/restore_wiki_stepslib.php +++ b/mod/wiki/backup/moodle2/restore_wiki_stepslib.php @@ -57,9 +57,10 @@ class restore_wiki_activity_structure_step extends restore_activity_structure_st $oldid = $data->id; $data->course = $this->get_courseid(); + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. $data->editbegin = $this->apply_date_offset($data->editbegin); $data->editend = $this->apply_date_offset($data->editend); - $data->timemodified = $this->apply_date_offset($data->timemodified); // insert the wiki record $newitemid = $DB->insert_record('wiki', $data); @@ -101,9 +102,6 @@ class restore_wiki_activity_structure_step extends restore_activity_structure_st $oldid = $data->id; $data->subwikiid = $this->get_new_parentid('wiki_subwiki'); $data->userid = $this->get_mappingid('user', $data->userid); - $data->timemodified = $this->apply_date_offset($data->timemodified); - $data->timecreated = $this->apply_date_offset($data->timecreated); - $data->timerendered = $this->apply_date_offset($data->timerendered); // Check that we were able to get a parentid for this page. if ($data->subwikiid !== false) { @@ -122,7 +120,6 @@ class restore_wiki_activity_structure_step extends restore_activity_structure_st $oldid = $data->id; $data->pageid = $this->get_new_parentid('wiki_page'); $data->userid = $this->get_mappingid('user', $data->userid); - $data->timecreated = $this->apply_date_offset($data->timecreated); $newitemid = $DB->insert_record('wiki_versions', $data); $this->set_mapping('wiki_version', $oldid, $newitemid); diff --git a/mod/wiki/tests/restore_date_test.php b/mod/wiki/tests/restore_date_test.php new file mode 100644 index 00000000000..215c4137f31 --- /dev/null +++ b/mod/wiki/tests/restore_date_test.php @@ -0,0 +1,73 @@ +. + +/** + * Restore date tests. + * + * @package mod_wiki + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . "/phpunit/classes/restore_date_testcase.php"); + +/** + * Restore date tests. + * + * @package mod_wiki + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_wiki_restore_date_testcase extends restore_date_testcase { + + /** + * Test restore dates. + */ + public function test_restore_dates() { + global $DB; + + $record = ['editbegin' => 100, 'editend' => 100, 'timemodified' => 100]; + list($course, $wiki) = $this->create_course_and_module('wiki', $record); + + $wikigenerator = $this->getDataGenerator()->get_plugin_generator('mod_wiki'); + $page = $wikigenerator->create_first_page($wiki); + $version = $DB->get_record('wiki_versions', ['pageid' => $page->id, 'version' => 1]); + + // Do backup and restore. + $newcourseid = $this->backup_and_restore($course); + $newwiki = $DB->get_record('wiki', ['course' => $newcourseid]); + + $this->assertFieldsNotRolledForward($wiki, $newwiki, ['timecreated', 'timemodified']); + $props = ['editend', 'editbegin']; + $this->assertFieldsRolledForward($wiki, $newwiki, $props); + + $newsubwiki = $DB->get_record('wiki_subwikis', ['wikiid' => $newwiki->id]); + $newpage = $DB->get_record('wiki_pages', ['subwikiid' => $newsubwiki->id]); + $newversion = $DB->get_record('wiki_versions', ['pageid' => $newpage->id, 'version' => 1]); + + // Wiki page time checks. + $this->assertEquals($page->timecreated, $newpage->timecreated); + $this->assertEquals($page->timemodified, $newpage->timemodified); + $this->assertEquals($page->timerendered, $newpage->timerendered); + + // Wiki version time checks. + $this->assertEquals($version->timecreated, $newversion->timecreated); + + } +} diff --git a/mod/workshop/backup/moodle2/restore_workshop_stepslib.php b/mod/workshop/backup/moodle2/restore_workshop_stepslib.php index a3680da5ccd..1812f039275 100644 --- a/mod/workshop/backup/moodle2/restore_workshop_stepslib.php +++ b/mod/workshop/backup/moodle2/restore_workshop_stepslib.php @@ -104,7 +104,8 @@ class restore_workshop_activity_structure_step extends restore_activity_structur $oldid = $data->id; $data->course = $this->get_courseid(); - $data->timemodified = $this->apply_date_offset($data->timemodified); + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. + // See MDL-9367. $data->submissionstart = $this->apply_date_offset($data->submissionstart); $data->submissionend = $this->apply_date_offset($data->submissionend); $data->assessmentstart = $this->apply_date_offset($data->assessmentstart); @@ -125,8 +126,6 @@ class restore_workshop_activity_structure_step extends restore_activity_structur $data->workshopid = $this->get_new_parentid('workshop'); $data->example = 1; $data->authorid = $this->task->get_userid(); - $data->timecreated = $this->apply_date_offset($data->timecreated); - $data->timemodified = $this->apply_date_offset($data->timemodified); $newitemid = $DB->insert_record('workshop_submissions', $data); $this->set_mapping('workshop_examplesubmission', $oldid, $newitemid, true); // Mapping with files @@ -140,8 +139,6 @@ class restore_workshop_activity_structure_step extends restore_activity_structur $data->submissionid = $this->get_new_parentid('workshop_examplesubmission'); $data->reviewerid = $this->task->get_userid(); - $data->timecreated = $this->apply_date_offset($data->timecreated); - $data->timemodified = $this->apply_date_offset($data->timemodified); $newitemid = $DB->insert_record('workshop_assessments', $data); $this->set_mapping('workshop_referenceassessment', $oldid, $newitemid, true); // Mapping with files @@ -155,8 +152,6 @@ class restore_workshop_activity_structure_step extends restore_activity_structur $data->submissionid = $this->get_new_parentid('workshop_examplesubmission'); $data->reviewerid = $this->get_mappingid('user', $data->reviewerid); - $data->timecreated = $this->apply_date_offset($data->timecreated); - $data->timemodified = $this->apply_date_offset($data->timemodified); $newitemid = $DB->insert_record('workshop_assessments', $data); $this->set_mapping('workshop_exampleassessment', $oldid, $newitemid, true); // Mapping with files @@ -171,8 +166,6 @@ class restore_workshop_activity_structure_step extends restore_activity_structur $data->workshopid = $this->get_new_parentid('workshop'); $data->example = 0; $data->authorid = $this->get_mappingid('user', $data->authorid); - $data->timecreated = $this->apply_date_offset($data->timecreated); - $data->timemodified = $this->apply_date_offset($data->timemodified); $newitemid = $DB->insert_record('workshop_submissions', $data); $this->set_mapping('workshop_submission', $oldid, $newitemid, true); // Mapping with files @@ -186,8 +179,6 @@ class restore_workshop_activity_structure_step extends restore_activity_structur $data->submissionid = $this->get_new_parentid('workshop_submission'); $data->reviewerid = $this->get_mappingid('user', $data->reviewerid); - $data->timecreated = $this->apply_date_offset($data->timecreated); - $data->timemodified = $this->apply_date_offset($data->timemodified); $newitemid = $DB->insert_record('workshop_assessments', $data); $this->set_mapping('workshop_assessment', $oldid, $newitemid, true); // Mapping with files @@ -201,7 +192,6 @@ class restore_workshop_activity_structure_step extends restore_activity_structur $data->workshopid = $this->get_new_parentid('workshop'); $data->userid = $this->get_mappingid('user', $data->userid); - $data->timegraded = $this->apply_date_offset($data->timegraded); $newitemid = $DB->insert_record('workshop_aggregations', $data); $this->set_mapping('workshop_aggregation', $oldid, $newitemid, true); diff --git a/mod/workshop/tests/restore_date_test.php b/mod/workshop/tests/restore_date_test.php new file mode 100644 index 00000000000..008cbb2615b --- /dev/null +++ b/mod/workshop/tests/restore_date_test.php @@ -0,0 +1,86 @@ +. + +/** + * Restore date tests. + * + * @package mod_workshop + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->dirroot . '/mod/workshop/locallib.php'); +require_once($CFG->dirroot . '/mod/workshop/lib.php'); +require_once($CFG->libdir . "/phpunit/classes/restore_date_testcase.php"); +require_once($CFG->dirroot . "/mod/workshop/tests/fixtures/testable.php"); + +/** + * Restore date tests. + * + * @package mod_workshop + * @copyright 2017 onwards Ankit Agarwal + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_workshop_restore_date_testcase extends restore_date_testcase { + + /** + * Test restore dates. + */ + public function test_restore_dates() { + global $DB, $USER; + + // Create workshop data. + $record = ['submissionstart' => 100, 'submissionend' => 100, 'assessmentend' => 100, 'assessmentstart' => 100]; + list($course, $workshop) = $this->create_course_and_module('workshop', $record); + $workshopgenerator = $this->getDataGenerator()->get_plugin_generator('mod_workshop'); + $subid = $workshopgenerator->create_submission($workshop->id, $USER->id); + $exsubid = $workshopgenerator->create_submission($workshop->id, $USER->id, ['example' => 1]); + $workshopgenerator->create_assessment($subid, $USER->id); + $workshopgenerator->create_assessment($exsubid, $USER->id, ['weight' => 0]); + $workshopgenerator->create_assessment($exsubid, $USER->id); + + // Set time fields to a constant for easy validation. + $timestamp = 100; + $DB->set_field('workshop_submissions', 'timecreated', $timestamp); + $DB->set_field('workshop_submissions', 'timemodified', $timestamp); + $DB->set_field('workshop_assessments', 'timecreated', $timestamp); + $DB->set_field('workshop_assessments', 'timemodified', $timestamp); + + // Do backup and restore. + $newcourseid = $this->backup_and_restore($course); + $newworkshop = $DB->get_record('workshop', ['course' => $newcourseid]); + + $this->assertFieldsNotRolledForward($workshop, $newworkshop, ['timemodified']); + $props = ['submissionstart', 'submissionend', 'assessmentend', 'assessmentstart']; + $this->assertFieldsRolledForward($workshop, $newworkshop, $props); + + $submissions = $DB->get_records('workshop_submissions', ['workshopid' => $newworkshop->id]); + // Workshop submission time checks. + foreach ($submissions as $submission) { + $this->assertEquals($timestamp, $submission->timecreated); + $this->assertEquals($timestamp, $submission->timemodified); + $assessments = $DB->get_records('workshop_assessments', ['submissionid' => $submission->id]); + // Workshop assessment time checks. + foreach ($assessments as $assessment) { + $this->assertEquals($timestamp, $assessment->timecreated); + $this->assertEquals($timestamp, $assessment->timemodified); + } + } + } +}