Skip to content

Commit

Permalink
MDL-46991 Availability: Conditional dates not updated on restore
Browse files Browse the repository at this point in the history
  • Loading branch information
sammarshallou committed Sep 18, 2014
1 parent 272fec3 commit 5176504
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 55 deletions.
26 changes: 25 additions & 1 deletion availability/classes/info.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ abstract class info {
/** @var tree Availability configuration, decoded from JSON; null if unset */
protected $availabilitytree;

/** @var array|null Array of information about current restore if any */
protected static $restoreinfo = null;

/**
* Constructs with item details.
*
Expand Down Expand Up @@ -307,9 +310,12 @@ protected function warn_about_invalid_availability(\coding_exception $e) {
* @param string $restoreid Restore identifier
* @param int $courseid Target course id
* @param \base_logger $logger Logger for any warnings
* @param int $dateoffset Date offset to be added to any dates (0 = none)
*/
public function update_after_restore($restoreid, $courseid, \base_logger $logger) {
public function update_after_restore($restoreid, $courseid, \base_logger $logger, $dateoffset) {
$tree = $this->get_availability_tree();
// Set static data for use by get_restore_date_offset function.
self::$restoreinfo = array('restoreid' => $restoreid, 'dateoffset' => $dateoffset);
$changed = $tree->update_after_restore($restoreid, $courseid, $logger,
$this->get_thing_name());
if ($changed) {
Expand All @@ -319,6 +325,24 @@ public function update_after_restore($restoreid, $courseid, \base_logger $logger
}
}

/**
* Gets the date offset (amount by which any date values should be
* adjusted) for the current restore.
*
* @param string $restoreid Restore identifier
* @return int Date offset (0 if none)
* @throws coding_exception If not in a restore (or not in that restore)
*/
public static function get_restore_date_offset($restoreid) {
if (!self::$restoreinfo) {
throw new coding_exception('Only valid during restore');
}
if (self::$restoreinfo['restoreid'] !== $restoreid) {
throw new coding_exception('Data not available for that restore id');
}
return self::$restoreinfo['dateoffset'];
}

/**
* Obtains the name of the item (cm_info or section_info, at present) that
* this is controlling availability of. Name should be formatted ready
Expand Down
11 changes: 7 additions & 4 deletions availability/classes/tree.php
Original file line number Diff line number Diff line change
Expand Up @@ -606,10 +606,13 @@ public function get_logic_flags($not) {
public function save() {
$result = new \stdClass();
$result->op = $this->op;
if ($this->op === self::OP_AND || $this->op === self::OP_NOT_OR) {
$result->showc = $this->showchildren;
} else {
$result->show = $this->show;
// Only root tree has the 'show' options.
if ($this->root) {
if ($this->op === self::OP_AND || $this->op === self::OP_NOT_OR) {
$result->showc = $this->showchildren;
} else {
$result->show = $this->show;
}
}
$result->c = array();
foreach ($this->children as $child) {
Expand Down
3 changes: 3 additions & 0 deletions availability/classes/tree_node.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ public abstract function save();
* The default behaviour is simply to return false. If there is a problem
* with the update, $logger can be used to output a warning.
*
* Note: If you need information about the date offset, call
* \core_availability\info::get_restore_date_offset($restoreid).
*
* @param string $restoreid Restore ID
* @param int $courseid ID of target course
* @param \base_logger $logger Logger for any warnings
Expand Down
11 changes: 11 additions & 0 deletions availability/condition/date/classes/condition.php
Original file line number Diff line number Diff line change
Expand Up @@ -209,4 +209,15 @@ protected function show_time($time, $dateonly, $until = false) {
protected static function is_midnight($time) {
return usergetmidnight($time) == $time;
}

public function update_after_restore(
$restoreid, $courseid, \base_logger $logger, $name) {
// Update the date, if restoring with changed date.
$dateoffset = \core_availability\info::get_restore_date_offset($restoreid);
if ($dateoffset) {
$this->time += $dateoffset;
return true;
}
return false;
}
}
7 changes: 5 additions & 2 deletions backup/moodle2/restore_stepslib.php
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,9 @@ protected function define_execution() {
rebuild_course_cache($this->get_courseid(), true);
$modinfo = get_fast_modinfo($this->get_courseid());

// Get the date offset for this restore.
$dateoffset = $this->apply_date_offset(1) - 1;

// Update all sections that were restored.
$params = array('backupid' => $this->get_restoreid(), 'itemname' => 'course_section');
$rs = $DB->get_recordset('backup_ids_temp', $params, '', 'newitemid');
Expand All @@ -667,7 +670,7 @@ protected function define_execution() {
if (!is_null($section->availability)) {
$info = new \core_availability\info_section($section);
$info->update_after_restore($this->get_restoreid(),
$this->get_courseid(), $this->get_logger());
$this->get_courseid(), $this->get_logger(), $dateoffset);
}
}
$rs->close();
Expand All @@ -687,7 +690,7 @@ protected function define_execution() {
if (!is_null($cm->availability)) {
$info = new \core_availability\info_module($cm);
$info->update_after_restore($this->get_restoreid(),
$this->get_courseid(), $this->get_logger());
$this->get_courseid(), $this->get_logger(), $dateoffset);
}
}
$rs->close();
Expand Down
59 changes: 58 additions & 1 deletion backup/moodle2/tests/moodle2_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -331,13 +331,67 @@ public function test_duplicate_availability() {
$this->assertEquals($expected, $actual);
}

/**
* When restoring a course, you can change the start date, which shifts other
* dates. This test checks that certain dates are correctly modified.
*/
public function test_restore_dates() {
global $DB, $CFG;

$this->resetAfterTest(true);
$this->setAdminUser();
$CFG->enableavailability = true;

// Create a course with specific start date.
$generator = $this->getDataGenerator();
$course = $generator->create_course(array(
'startdate' => strtotime('1 Jan 2014 00:00 GMT')));

// Add a forum with conditional availability date restriction, including
// one of them nested inside a tree.
$availability = '{"op":"&","showc":[true,true],"c":[' .
'{"op":"&","c":[{"type":"date","d":">=","t":DATE1}]},' .
'{"type":"date","d":"<","t":DATE2}]}';
$before = str_replace(
array('DATE1', 'DATE2'),
array(strtotime('1 Feb 2014 00:00 GMT'), strtotime('10 Feb 2014 00:00 GMT')),
$availability);
$forum = $generator->create_module('forum', array('course' => $course->id,
'availability' => $before));

// Add an assign with defined start date.
$assign = $generator->create_module('assign', array('course' => $course->id,
'allowsubmissionsfromdate' => strtotime('7 Jan 2014 16:00 GMT')));

// Do backup and restore.
$newcourseid = $this->backup_and_restore($course, strtotime('3 Jan 2015 00:00 GMT'));

$modinfo = get_fast_modinfo($newcourseid);

// Check forum dates are modified by the same amount as the course start.
$newforums = $modinfo->get_instances_of('forum');
$newforum = reset($newforums);
$after = str_replace(
array('DATE1', 'DATE2'),
array(strtotime('3 Feb 2015 00:00 GMT'), strtotime('12 Feb 2015 00:00 GMT')),
$availability);
$this->assertEquals($after, $newforum->availability);

// Check assign date.
$newassigns = $modinfo->get_instances_of('assign');
$newassign = reset($newassigns);
$this->assertEquals(strtotime('9 Jan 2015 16:00 GMT'), $DB->get_field(
'assign', 'allowsubmissionsfromdate', array('id' => $newassign->instance)));
}

/**
* 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) {
protected function backup_and_restore($course, $newdate = 0) {
global $USER, $CFG;

// Turn off file logging, otherwise it can't delete the file (Windows).
Expand All @@ -358,6 +412,9 @@ protected function backup_and_restore($course) {
$rc = new restore_controller($backupid, $newcourseid,
backup::INTERACTIVE_NO, backup::MODE_GENERAL, $USER->id,
backup::TARGET_NEW_COURSE);
if ($newdate) {
$rc->get_plan()->get_setting('course_startdate')->set_value($newdate);
}
$this->assertTrue($rc->execute_precheck());
$rc->execute_plan();
$rc->destroy();
Expand Down
49 changes: 49 additions & 0 deletions backup/util/plan/restore_step.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,55 @@ protected function get_restoreid() {
}
return $this->task->get_restoreid();
}

/**
* Apply course startdate offset based in original course startdate and course_offset_startdate setting
* Note we are using one static cache here, but *by restoreid*, so it's ok for concurrence/multiple
* executions in the same request
*
* @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
*/
public function apply_date_offset($value) {

// Empties don't offset - zeros (int and string), false and nulls return original value.
if (empty($value)) {
return $value;
}

static $cache = array();
// Lookup cache.
if (isset($cache[$this->get_restoreid()])) {
return $value + $cache[$this->get_restoreid()];
}
// No cache, let's calculate the offset.
$original = $this->task->get_info()->original_course_startdate;
$setting = 0;
if ($this->setting_exists('course_startdate')) { // Seting may not exist (MDL-25019).
$setting = $this->get_setting_value('course_startdate');
}

if (empty($original) || empty($setting)) {
// Original course has not startdate or setting doesn't exist, offset = 0.
$cache[$this->get_restoreid()] = 0;

} else if (abs($setting - $original) < 24 * 60 * 60) {
// Less than 24h of difference, offset = 0 (this avoids some problems with timezones).
$cache[$this->get_restoreid()] = 0;

} else if (!has_capability('moodle/restore:rolldates',
context_course::instance($this->get_courseid()), $this->task->get_userid())) {
// Re-enforce 'moodle/restore:rolldates' capability for the user in the course, just in case.
$cache[$this->get_restoreid()] = 0;

} else {
// Arrived here, let's calculate the real offset.
$cache[$this->get_restoreid()] = $setting - $original;
}

// Return the passed value with cached offset applied.
return $value + $cache[$this->get_restoreid()];
}
}

/*
Expand Down
47 changes: 0 additions & 47 deletions backup/util/plan/restore_structure_step.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -245,53 +245,6 @@ public function add_related_files($component, $filearea, $mappingitemname, $file
$this->task->add_result($resultstoadd);
}

/**
* Apply course startdate offset based in original course startdate and course_offset_startdate setting
* Note we are using one static cache here, but *by restoreid*, so it's ok for concurrence/multiple
* executions in the same request
*/
public function apply_date_offset($value) {

// empties don't offset - zeros (int and string), false and nulls return original value
if (empty($value)) {
return $value;
}

static $cache = array();
// Lookup cache
if (isset($cache[$this->get_restoreid()])) {
return $value + $cache[$this->get_restoreid()];
}
// No cache, let's calculate the offset
$original = $this->task->get_info()->original_course_startdate;
$setting = 0;
if ($this->setting_exists('course_startdate')) { // Seting may not exist (MDL-25019)
$setting = $this->get_setting_value('course_startdate');
}

// Original course has not startdate or setting doesn't exist, offset = 0
if (empty($original) || empty($setting)) {
$cache[$this->get_restoreid()] = 0;

// Less than 24h of difference, offset = 0 (this avoids some problems with timezones)
} else if (abs($setting - $original) < 24 * 60 * 60) {
$cache[$this->get_restoreid()] = 0;

// Re-enforce 'moodle/restore:rolldates' capability for the user in the course, just in case
} else if (!has_capability('moodle/restore:rolldates',
context_course::instance($this->get_courseid()),
$this->task->get_userid())) {
$cache[$this->get_restoreid()] = 0;

// Arrived here, let's calculate the real offset
} else {
$cache[$this->get_restoreid()] = $setting - $original;
}

// Return the passed value with cached offset applied
return $value + $cache[$this->get_restoreid()];
}

/**
* As far as restore structure steps are implementing restore_plugin stuff, they need to
* have the parent task available for wrapping purposes (get course/context....)
Expand Down

0 comments on commit 5176504

Please sign in to comment.