<?php

/**
 * @package framework
 * @subpackage tests
 */
class VersionedTest extends SapphireTest {
	
	protected static $fixture_file = 'VersionedTest.yml';

	protected $extraDataObjects = array(
		'VersionedTest_DataObject',
		'VersionedTest_Subclass',
		'VersionedTest_RelatedWithoutVersion'
	);
	
	protected $requiredExtensions = array(
		"VersionedTest_DataObject" => array('Versioned')
	);

	public function testDeletingOrphanedVersions() {
		$obj = new VersionedTest_Subclass();
		$obj->ExtraField = 'Foo'; // ensure that child version table gets written
		$obj->write();
		$obj->publish('Stage', 'Live');
		
		$obj->ExtraField = 'Bar'; // ensure that child version table gets written
		$obj->write();
		$obj->publish('Stage', 'Live');
	
		$versions = DB::query("SELECT COUNT(*) FROM \"VersionedTest_Subclass_versions\""
			. " WHERE \"RecordID\" = '$obj->ID'")->value();
	
		$this->assertGreaterThan(0, $versions, 'At least 1 version exists in the history of the page');
	
		// Force orphaning of all versions created earlier, only on parent record.
		// The child versiones table should still have the correct relationship
		DB::query("DELETE FROM \"VersionedTest_DataObject_versions\" WHERE \"RecordID\" = $obj->ID");
		
		// insert a record with no primary key (ID)
		DB::query("INSERT INTO \"VersionedTest_DataObject_versions\" (\"RecordID\") VALUES ($obj->ID)");
	
		// run the script which should clean that up
		$obj->augmentDatabase();
	
		$versions = DB::query("SELECT COUNT(*) FROM \"VersionedTest_Subclass_versions\""
			. " WHERE \"RecordID\" = '$obj->ID'")->value();
		$this->assertEquals(0, $versions, 'Orphaned versions on child tables are removed');
		
		// test that it doesn't delete records that we need
		$obj->write();
		$obj->publish('Stage', 'Live');
	
		$count = DB::query("SELECT COUNT(*) FROM \"VersionedTest_Subclass_versions\""
			. " WHERE \"RecordID\" = '$obj->ID'")->value();
		$obj->augmentDatabase();
		
		$count2 = DB::query("SELECT COUNT(*) FROM \"VersionedTest_Subclass_versions\""
			. " WHERE \"RecordID\" = '$obj->ID'")->value();
		
		$this->assertEquals($count, $count2);
	}
	
	public function testForceChangeUpdatesVersion() {
		$obj = new VersionedTest_DataObject();
		$obj->Name = "test";
		$obj->write();
		
		$oldVersion = $obj->Version;
		$obj->forceChange();
		$obj->write();
	
		$this->assertTrue(
			($obj->Version > $oldVersion),
			"A object Version is increased when just calling forceChange() without any other changes"
		);
	}

	/**
	 * Test Versioned::get_including_deleted()
	 */
	public function testGetIncludingDeleted() {
		// Get all ids of pages
		$allPageIDs = DataObject::get(
			'VersionedTest_DataObject', 
			"\"ParentID\" = 0", "\"VersionedTest_DataObject\".\"ID\" ASC"
		)->column('ID');
		
		// Modify a page, ensuring that the Version ID and Record ID will differ,
		// and then subsequently delete it
		$targetPage = $this->objFromFixture('VersionedTest_DataObject', 'page3');
		$targetPage->Content = 'To be deleted';
		$targetPage->write();
		$targetPage->delete();
	
		// Get all items, ignoring deleted
		$remainingPages = DataObject::get("VersionedTest_DataObject", "\"ParentID\" = 0",
			"\"VersionedTest_DataObject\".\"ID\" ASC");
		// Check that page 3 has gone
		$this->assertNotNull($remainingPages);
		$this->assertEquals(array("Page 1", "Page 2"), $remainingPages->column('Title'));
		
		// Get all including deleted
		$allPages = Versioned::get_including_deleted("VersionedTest_DataObject", "\"ParentID\" = 0",
			"\"VersionedTest_DataObject\".\"ID\" ASC");
		// Check that page 3 is still there
		$this->assertEquals(array("Page 1", "Page 2", "Page 3"), $allPages->column('Title'));
		
		// Check that the returned pages have the correct IDs
		$this->assertEquals($allPageIDs, $allPages->column('ID'));
		
		// Check that this still works if we switch to reading the other stage
		Versioned::reading_stage("Live");
		$allPages = Versioned::get_including_deleted("VersionedTest_DataObject", "\"ParentID\" = 0",
			"\"VersionedTest_DataObject\".\"ID\" ASC");
		$this->assertEquals(array("Page 1", "Page 2", "Page 3"), $allPages->column('Title'));
		
		// Check that the returned pages still have the correct IDs
		$this->assertEquals($allPageIDs, $allPages->column('ID'));
	}
	
	public function testVersionedFieldsAdded() {
		$obj = new VersionedTest_DataObject();
		// Check that the Version column is added as a full-fledged column
		$this->assertInstanceOf('Int', $obj->dbObject('Version'));
	
		$obj2 = new VersionedTest_Subclass();
		// Check that the Version column is added as a full-fledged column
		$this->assertInstanceOf('Int', $obj2->dbObject('Version'));
	}

	public function testVersionedFieldsNotInCMS() {
		$obj = new VersionedTest_DataObject();

		// the version field in cms causes issues with Versioned::augmentWrite()
		$this->assertNull($obj->getCMSFields()->dataFieldByName('Version'));
	}

	public function testPublishCreateNewVersion() {
		$page1 = $this->objFromFixture('VersionedTest_DataObject', 'page1');
		$page1->Content = 'orig';
		$page1->write();
		$oldVersion = $page1->Version;
		$page1->publish('Stage', 'Live', false);
		$this->assertEquals($oldVersion, $page1->Version, 'publish() with $createNewVersion=FALSE');
		
		$page1->Content = 'changed';
		$page1->write();
		$oldVersion = $page1->Version;
		$page1->publish('Stage', 'Live', true);
		$this->assertTrue($oldVersion < $page1->Version, 'publish() with $createNewVersion=TRUE');
	}
	
	public function testRollbackTo() {
		$page1 = $this->objFromFixture('VersionedTest_DataObject', 'page1');
		$page1->Content = 'orig';
		$page1->write();
		$page1->publish('Stage', 'Live');
		$origVersion = $page1->Version;
		
		$page1->Content = 'changed';
		$page1->write();
		$page1->publish('Stage', 'Live');
		$changedVersion = $page1->Version;

		$page1->doRollbackTo($origVersion);
		$page1 = Versioned::get_one_by_stage('VersionedTest_DataObject', 'Stage',
			sprintf('"VersionedTest_DataObject"."ID" = %d', $page1->ID));
		
		$this->assertTrue($page1->Version > $changedVersion, 'Create a new higher version number');
		$this->assertEquals('orig', $page1->Content, 'Copies the content from the old version');
	}
	
	public function testDeleteFromStage() {
		$page1 = $this->objFromFixture('VersionedTest_DataObject', 'page1');
		$pageID = $page1->ID;
		
		$page1->Content = 'orig';
		$page1->write();
		$page1->publish('Stage', 'Live');
		
		$this->assertEquals(1,
			DB::query('SELECT COUNT(*) FROM "VersionedTest_DataObject" WHERE "ID" = '.$pageID)->value());
		$this->assertEquals(1,
			DB::query('SELECT COUNT(*) FROM "VersionedTest_DataObject_Live" WHERE "ID" = '.$pageID)->value());
		
		$page1->deleteFromStage('Live');
		
		// Confirm that deleteFromStage() doesn't manipulate the original record
		$this->assertEquals($pageID, $page1->ID);

		$this->assertEquals(1,
			DB::query('SELECT COUNT(*) FROM "VersionedTest_DataObject" WHERE "ID" = '.$pageID)->value());
		$this->assertEquals(0,
			DB::query('SELECT COUNT(*) FROM "VersionedTest_DataObject_Live" WHERE "ID" = '.$pageID)->value());

		$page1->delete();

		$this->assertEquals(0, $page1->ID);
		$this->assertEquals(0,
			DB::query('SELECT COUNT(*) FROM "VersionedTest_DataObject" WHERE "ID" = '.$pageID)->value());
		$this->assertEquals(0,
			DB::query('SELECT COUNT(*) FROM "VersionedTest_DataObject_Live" WHERE "ID" = '.$pageID)->value());
	}

	public function testWritingNewToStage() {
		$origStage = Versioned::current_stage();
		
		Versioned::reading_stage("Stage");
		$page = new VersionedTest_DataObject();
		$page->Title = "testWritingNewToStage";
		$page->URLSegment = "testWritingNewToStage";
		$page->write();
		
		$live = Versioned::get_by_stage('VersionedTest_DataObject', 'Live',
			"\"VersionedTest_DataObject_Live\".\"ID\"='$page->ID'");
		$this->assertEquals(0, $live->count());
		
		$stage = Versioned::get_by_stage('VersionedTest_DataObject', 'Stage',
			"\"VersionedTest_DataObject\".\"ID\"='$page->ID'");
		$this->assertEquals(1, $stage->count());
		$this->assertEquals($stage->First()->Title, 'testWritingNewToStage');
		
		Versioned::reading_stage($origStage);
	}

	/**
	 * This tests for the situation described in the ticket #5596. 
	 * Writing new Page to live first creates a row in VersionedTest_DataObject table (to get the new ID),
	 * then "changes it's mind" in Versioned and writes VersionedTest_DataObject_Live. It does not remove
	 * the VersionedTest_DataObject record though.
	 */ 
	public function testWritingNewToLive() {
		$origStage = Versioned::current_stage();
		
		Versioned::reading_stage("Live");
		$page = new VersionedTest_DataObject();
		$page->Title = "testWritingNewToLive";
		$page->URLSegment = "testWritingNewToLive";
		$page->write();
		
		$live = Versioned::get_by_stage('VersionedTest_DataObject', 'Live',
			"\"VersionedTest_DataObject_Live\".\"ID\"='$page->ID'");
		$this->assertEquals(1, $live->count());
		$this->assertEquals($live->First()->Title, 'testWritingNewToLive');
		
		$stage = Versioned::get_by_stage('VersionedTest_DataObject', 'Stage',
			"\"VersionedTest_DataObject\".\"ID\"='$page->ID'");
		$this->assertEquals(0, $stage->count());
		
		Versioned::reading_stage($origStage);
	}
	
	/**
	 * Tests DataObject::hasOwnTableDatabaseField
	 */
	public function testHasOwnTableDatabaseFieldWithVersioned() {
		$noversion    = new DataObject();
		$versioned    = new VersionedTest_DataObject();
		$versionedSub = new VersionedTest_Subclass();
		$versionField = new VersionedTest_UnversionedWithField();

		$this->assertFalse(
			(bool) $noversion->hasOwnTableDatabaseField('Version'),
			'Plain models have no version field.'
		);
		$this->assertEquals(
			'Int', $versioned->hasOwnTableDatabaseField('Version'),
			'The versioned ext adds an Int version field.'
		);
		$this->assertEquals(
			'Int', $versionedSub->hasOwnTableDatabaseField('Version'),
			'Sub-classes of a versioned model have a Version field.'
		);
		$this->assertEquals(
			'Varchar', $versionField->hasOwnTableDatabaseField('Version'),
			'Models w/o Versioned can have their own Version field.'
		);
	}
	
	/**
	 * Test that SQLQuery::queriedTables() applies the version-suffixes properly.
	 */
	public function testQueriedTables() {
		Versioned::reading_stage('Live');

		$this->assertEquals(array(
			'VersionedTest_DataObject_Live',
			'VersionedTest_Subclass_Live',
		), DataObject::get('VersionedTest_Subclass')->dataQuery()->query()->queriedTables());
	}
	
	public function testGetVersionWhenClassnameChanged() {
		$obj = new VersionedTest_DataObject;
		$obj->Name = "test";
		$obj->write();
		$obj->Name = "test2";
		$obj->ClassName = "VersionedTest_Subclass";
		$obj->write();
		$subclassVersion = $obj->Version;
		
		$obj->Name = "test3";
		$obj->ClassName = "VersionedTest_DataObject";
		$obj->write();
		
		// We should be able to pass the subclass and still get the correct class back
		$obj2 = Versioned::get_version("VersionedTest_Subclass", $obj->ID, $subclassVersion);
		$this->assertInstanceOf("VersionedTest_Subclass", $obj2);
		$this->assertEquals("test2", $obj2->Name);

		$obj3 = Versioned::get_latest_version("VersionedTest_Subclass", $obj->ID);
		$this->assertEquals("test3", $obj3->Name);
		$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');
	}

	public function testArchiveRelatedDataWithoutVersioned() {
		SS_Datetime::set_mock_now('2009-01-01 00:00:00');

		$relatedData = new VersionedTest_RelatedWithoutVersion();
		$relatedData->Name = 'Related Data';
		$relatedDataId = $relatedData->write();

		$testData = new VersionedTest_DataObject();
		$testData->Title = 'Test';
		$testData->Content = 'Before Content';
		$testData->Related()->add($relatedData);
		$id = $testData->write();

		SS_Datetime::set_mock_now('2010-01-01 00:00:00');
		$testData->Content = 'After Content';
		$testData->write();

		$_GET['archiveDate'] = '2009-01-01 19:00:00';
		Versioned::reading_archived_date('2009-01-01 19:00:00');

		$fetchedData = VersionedTest_DataObject::get()->byId($id);
		$this->assertEquals('Before Content', $fetchedData->Content, 'We see the correct content of the older version');

		$relatedData = VersionedTest_RelatedWithoutVersion::get()->byId($relatedDataId);
		$this->assertEquals(
			1,
			$relatedData->Related()->count(),
			'We have a relation, with no version table, querying it still works'
		);
	}

}


/**
 * @package framework
 * @subpackage tests
 */
class VersionedTest_DataObject extends DataObject implements TestOnly {
	private static $db = array(
		"Name" => "Varchar",
		'Title' => 'Varchar',
		'Content' => 'HTMLText'
	);

	private static $extensions = array(
		"Versioned('Stage', 'Live')"
	);
	
	private static $has_one = array(
		'Parent' => 'VersionedTest_DataObject'
	);

	private static $many_many = array(
		'Related' => 'VersionedTest_RelatedWithoutVersion'
	);

}

/**
 * @package framework
 * @subpackage tests
 */
class VersionedTest_RelatedWithoutVersion extends DataObject implements TestOnly {

	private static $db = array(
		'Name' => 'Varchar'
	);

	private static $belongs_many_many = array(
		'Related' => 'VersionedTest_DataObject'
	);

}

/**
 * @package framework
 * @subpackage tests
 */
class VersionedTest_Subclass extends VersionedTest_DataObject implements TestOnly {
	private static $db = array(
		"ExtraField" => "Varchar",
	);
	
	private static $extensions = array(
		"Versioned('Stage', 'Live')"
	);
}

/**
 * @package framework
 * @subpackage tests
 */
class VersionedTest_UnversionedWithField extends DataObject implements TestOnly {
	private static $db = array('Version' => 'Varchar(255)');
}