diff --git a/model/Versioned.php b/model/Versioned.php index 67dedb5bd..705e6a3a6 100644 --- a/model/Versioned.php +++ b/model/Versioned.php @@ -145,7 +145,8 @@ class Versioned extends DataExtension { $date = $dataQuery->getQueryParam('Versioned.date'); foreach($query->getFrom() as $table => $dummy) { $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 _versions columns 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"); 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 - $archiveTable = $this->requireArchiveTempTable($baseTable, $date); - $query->addFrom(array($archiveTable => "INNER JOIN \"$archiveTable\" - ON \"$archiveTable\".\"ID\" = \"{$baseTable}_versions\".\"RecordID\" - AND \"$archiveTable\".\"Version\" = \"{$baseTable}_versions\".\"Version\"")); + $safeDate = Convert::raw2sql($date); + $query->addWhere( + "`{$baseTable}_versions`.`Version` IN + (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; // 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 // This provides "show all, including deleted" functonality if($dataQuery->getQueryParam('Versioned.mode') == 'latest_versions') { - $archiveTable = self::requireArchiveTempTable($baseTable); - $query->addInnerJoin($archiveTable, "\"$archiveTable\".\"ID\" = \"{$baseTable}_versions\".\"RecordID\" AND \"$archiveTable\".\"Version\" = \"{$baseTable}_versions\".\"Version\""); + $query->addWhere( + "`{$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; default: throw new InvalidArgumentException("Bad value for query parameter Versioned.mode: " . $dataQuery->getQueryParam('Versioned.mode')); @@ -233,34 +251,6 @@ class Versioned extends DataExtension { // Remove references to them 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 diff --git a/tests/model/VersionedTest.php b/tests/model/VersionedTest.php index ef871f64e..eb9ed65e7 100644 --- a/tests/model/VersionedTest.php +++ b/tests/model/VersionedTest.php @@ -266,6 +266,109 @@ class VersionedTest extends SapphireTest { $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 {