Merge pull request #716 from tractorcow/3.0-versioned-fixes

FIXED: Crashed caused by viewing versioned page
This commit is contained in:
Sam Minnée 2012-08-19 19:02:24 -07:00
commit 47b56d4ef8
2 changed files with 130 additions and 37 deletions

View File

@ -145,7 +145,8 @@ class Versioned extends DataExtension {
$date = $dataQuery->getQueryParam('Versioned.date'); $date = $dataQuery->getQueryParam('Versioned.date');
foreach($query->getFrom() as $table => $dummy) { foreach($query->getFrom() as $table => $dummy) {
$query->renameTable($table, $table . '_versions'); $query->renameTable($table, $table . '_versions');
$query->replaceText("\"$table\".\"ID\"", "\"$table\".\"RecordID\""); $query->replaceText("\"{$table}_versions\".\"ID\"", "\"{$table}_versions\".\"RecordID\"");
$query->replaceText("`{$table}_versions`.`ID`", "`{$table}_versions`.`RecordID`");
// Add all <basetable>_versions columns // Add all <basetable>_versions columns
foreach(self::$db_for_versions_table as $name => $type) { foreach(self::$db_for_versions_table as $name => $type) {
@ -154,15 +155,24 @@ class Versioned extends DataExtension {
$query->selectField(sprintf('"%s_versions"."%s"', $baseTable, 'RecordID'), "ID"); $query->selectField(sprintf('"%s_versions"."%s"', $baseTable, 'RecordID'), "ID");
if($table != $baseTable) { if($table != $baseTable) {
$query->addFrom(array($table => " AND \"{$table}_versions\".\"Version\" = \"{$baseTable}_versions\".\"Version\"")); $query->addWhere("\"{$table}_versions\".\"Version\" = \"{$baseTable}_versions\".\"Version\"");
} }
} }
// Link to the version archived on that date // Link to the version archived on that date
$archiveTable = $this->requireArchiveTempTable($baseTable, $date); $safeDate = Convert::raw2sql($date);
$query->addFrom(array($archiveTable => "INNER JOIN \"$archiveTable\" $query->addWhere(
ON \"$archiveTable\".\"ID\" = \"{$baseTable}_versions\".\"RecordID\" "`{$baseTable}_versions`.`Version` IN
AND \"$archiveTable\".\"Version\" = \"{$baseTable}_versions\".\"Version\"")); (SELECT LatestVersion FROM
(SELECT
`{$baseTable}_versions`.`RecordID`,
MAX(`{$baseTable}_versions`.`Version`) AS LatestVersion
FROM `{$baseTable}_versions`
WHERE `{$baseTable}_versions`.`LastEdited` <= '$safeDate'
GROUP BY `{$baseTable}_versions`.`RecordID`
) AS `{$baseTable}_versions_latest`
WHERE `{$baseTable}_versions_latest`.`RecordID` = `{$baseTable}_versions`.`RecordID`
)");
break; break;
// Reading a specific stage (Stage or Live) // Reading a specific stage (Stage or Live)
@ -203,10 +213,18 @@ class Versioned extends DataExtension {
// Return latest version instances, regardless of whether they are on a particular stage // Return latest version instances, regardless of whether they are on a particular stage
// This provides "show all, including deleted" functonality // This provides "show all, including deleted" functonality
if($dataQuery->getQueryParam('Versioned.mode') == 'latest_versions') { if($dataQuery->getQueryParam('Versioned.mode') == 'latest_versions') {
$archiveTable = self::requireArchiveTempTable($baseTable); $query->addWhere(
$query->addInnerJoin($archiveTable, "\"$archiveTable\".\"ID\" = \"{$baseTable}_versions\".\"RecordID\" AND \"$archiveTable\".\"Version\" = \"{$baseTable}_versions\".\"Version\""); "`{$alias}_versions`.`Version` IN
(SELECT LatestVersion FROM
(SELECT
`{$alias}_versions`.`RecordID`,
MAX(`{$alias}_versions`.`Version`) AS LatestVersion
FROM `{$alias}_versions`
GROUP BY `{$alias}_versions`.`RecordID`
) AS `{$alias}_versions_latest`
WHERE `{$alias}_versions_latest`.`RecordID` = `{$alias}_versions`.`RecordID`
)");
} }
break; break;
default: default:
throw new InvalidArgumentException("Bad value for query parameter Versioned.mode: " . $dataQuery->getQueryParam('Versioned.mode')); throw new InvalidArgumentException("Bad value for query parameter Versioned.mode: " . $dataQuery->getQueryParam('Versioned.mode'));
@ -234,34 +252,6 @@ class Versioned extends DataExtension {
self::$archive_tables = array(); self::$archive_tables = array();
} }
/**
* Create a temporary table mapping each database record to its version on the given date.
* This is used by the versioning system to return database content on that date.
* @param string $baseTable The base table.
* @param string $date The date. If omitted, then the latest version of each page will be returned.
* @todo Ensure that this is DB abstracted
*/
protected static function requireArchiveTempTable($baseTable, $date = null) {
if(!isset(self::$archive_tables[$baseTable])) {
self::$archive_tables[$baseTable] = DB::createTable("_Archive$baseTable", array(
"ID" => "INT NOT NULL",
"Version" => "INT NOT NULL",
), null, array('temporary' => true));
}
if(!DB::query("SELECT COUNT(*) FROM \"" . self::$archive_tables[$baseTable] . "\"")->value()) {
if($date) $dateClause = "WHERE \"LastEdited\" <= '$date'";
else $dateClause = "";
DB::query("INSERT INTO \"" . self::$archive_tables[$baseTable] . "\"
SELECT \"RecordID\", max(\"Version\") FROM \"{$baseTable}_versions\"
$dateClause
GROUP BY \"RecordID\"");
}
return self::$archive_tables[$baseTable];
}
/** /**
* An array of DataObject extensions that may require versioning for extra tables * An array of DataObject extensions that may require versioning for extra tables
* The array value is a set of suffixes to form these table names, assuming a preceding '_'. * The array value is a set of suffixes to form these table names, assuming a preceding '_'.

View File

@ -266,6 +266,109 @@ class VersionedTest extends SapphireTest {
$this->assertInstanceOf("VersionedTest_DataObject", $obj3); $this->assertInstanceOf("VersionedTest_DataObject", $obj3);
} }
public function testArchiveVersion() {
// In 2005 this file was created
SS_Datetime::set_mock_now('2005-01-01 00:00:00');
$testPage = new VersionedTest_Subclass();
$testPage->Title = 'Archived page';
$testPage->Content = 'This is the content from 2005';
$testPage->ExtraField = '2005';
$testPage->write();
// In 2007 we updated it
SS_Datetime::set_mock_now('2007-01-01 00:00:00');
$testPage->Content = "It's 2007 already!";
$testPage->ExtraField = '2007';
$testPage->write();
// In 2009 we updated it again
SS_Datetime::set_mock_now('2009-01-01 00:00:00');
$testPage->Content = "I'm enjoying 2009";
$testPage->ExtraField = '2009';
$testPage->write();
// End mock, back to the present day:)
SS_Datetime::clear_mock_now();
// Test 1 - 2006 Content
singleton('VersionedTest_Subclass')->flushCache(true);
Versioned::set_reading_mode('Archive.2006-01-01 00:00:00');
$testPage2006 = DataObject::get('VersionedTest_Subclass')->filter(array('Title' => 'Archived page'))->first();
$this->assertInstanceOf("VersionedTest_Subclass", $testPage2006);
$this->assertEquals("2005", $testPage2006->ExtraField);
$this->assertEquals("This is the content from 2005", $testPage2006->Content);
// Test 2 - 2008 Content
singleton('VersionedTest_Subclass')->flushCache(true);
Versioned::set_reading_mode('Archive.2008-01-01 00:00:00');
$testPage2008 = DataObject::get('VersionedTest_Subclass')->filter(array('Title' => 'Archived page'))->first();
$this->assertInstanceOf("VersionedTest_Subclass", $testPage2008);
$this->assertEquals("2007", $testPage2008->ExtraField);
$this->assertEquals("It's 2007 already!", $testPage2008->Content);
// Test 3 - Today
singleton('VersionedTest_Subclass')->flushCache(true);
Versioned::set_reading_mode('Stage.Stage');
$testPageCurrent = DataObject::get('VersionedTest_Subclass')->filter(array('Title' => 'Archived page'))->first();
$this->assertInstanceOf("VersionedTest_Subclass", $testPageCurrent);
$this->assertEquals("2009", $testPageCurrent->ExtraField);
$this->assertEquals("I'm enjoying 2009", $testPageCurrent->Content);
}
public function testAllVersions()
{
// In 2005 this file was created
SS_Datetime::set_mock_now('2005-01-01 00:00:00');
$testPage = new VersionedTest_Subclass();
$testPage->Title = 'Archived page';
$testPage->Content = 'This is the content from 2005';
$testPage->ExtraField = '2005';
$testPage->write();
// In 2007 we updated it
SS_Datetime::set_mock_now('2007-01-01 00:00:00');
$testPage->Content = "It's 2007 already!";
$testPage->ExtraField = '2007';
$testPage->write();
// Check both versions are returned
$versions = Versioned::get_all_versions('VersionedTest_Subclass', $testPage->ID);
$content = array();
$extraFields = array();
foreach($versions as $version)
{
$content[] = $version->Content;
$extraFields[] = $version->ExtraField;
}
$this->assertEquals($versions->Count(), 2, 'All versions returned');
$this->assertEquals($content, array('This is the content from 2005', "It's 2007 already!"), 'Version fields returned');
$this->assertEquals($extraFields, array('2005', '2007'), 'Version fields returned');
// In 2009 we updated it again
SS_Datetime::set_mock_now('2009-01-01 00:00:00');
$testPage->Content = "I'm enjoying 2009";
$testPage->ExtraField = '2009';
$testPage->write();
// End mock, back to the present day:)
SS_Datetime::clear_mock_now();
$versions = Versioned::get_all_versions('VersionedTest_Subclass', $testPage->ID);
$content = array();
$extraFields = array();
foreach($versions as $version)
{
$content[] = $version->Content;
$extraFields[] = $version->ExtraField;
}
$this->assertEquals($versions->Count(), 3, 'Additional all versions returned');
$this->assertEquals($content, array('This is the content from 2005', "It's 2007 already!", "I'm enjoying 2009"), 'Additional version fields returned');
$this->assertEquals($extraFields, array('2005', '2007', '2009'), 'Additional version fields returned');
}
} }
class VersionedTest_DataObject extends DataObject implements TestOnly { class VersionedTest_DataObject extends DataObject implements TestOnly {