/**
	 * @see self::testVersionedSiteTreeDeleteWithDeletableVersionedHasOneRelation()
	 */
	public function testVersionedSiteTreeDeleteWithDeletableVersionedHasOneRelationFromNewClassInstance() {
		// Make sure that has_many <= has_one relations are cleaned
		// for related DataObject classes even if the class type
		// being deleted does not have the relations.
		DataObjectOnDeleteDecorator::set_clean_versions_table(true);
		
		$masterPage = DataObject::get_one('DataObjectOnDeleteDecoratorTest_MasterPage', "\"Title\" = 'MasterPage'");
		$this->assertInstanceOf('DataObjectOnDeleteDecoratorTest_MasterPage', $masterPage);
		$masterPageID = $masterPage->ID;
		
		$subPage = DataObject::get_one('DataObjectOnDeleteDecoratorTest_SubPage', "\"Title\" = 'SubPage'");
		$this->assertInstanceOf('DataObjectOnDeleteDecoratorTest_SubPage', $subPage);
		$subPageID = $subPage->ID;
		$this->assertEquals($masterPage->ID, $subPage->MasterPageID, "Something is wrong with the fixture");
		$subPage->doPublish();
		
		$masterPage = $masterPage->newClassInstance('VirtualPage');
		$masterPage->write();
		$masterPage->delete();
		
		$this->assertVersionedTables('SiteTree', $masterPageID, false);
		$this->assertVersionedTables('DataObjectOnDeleteDecoratorTest_SubPage', $subPageID, false);
		
		DataObjectOnDeleteDecorator::set_clean_versions_table(false);
	}
Esempio n. 2
0
	/**
	 * Use DB plumber to backup the database.
	 * 
	 * @return string The path to the database dump.
	 */
	public static function backup_database() {
		if (!self::available()) {
			trigger_error("This feature requires the Database Plumber module."
				. " See README.md for more information.", E_USER_ERROR);
		}
		if (!self::get_sql_file_path()) {
			trigger_error("You must set self::\$sql_file_path to use this feature.",
				E_USER_ERROR);
		}
		
		$SQLDialects = array(
			'MySQLDatabase' => 'MySQL',
			'SQLite3Database' => 'SQLite',
			'MSSQLDatabase' => 'MSSQL',
			'PostgreSQLDatabase' => 'Postgres'
		);
		$sql = singleton('DBP_Database_Controller')->backup(
		    DataObjectOnDeleteDecorator::get_db_tables(false),
		    @$SQLDialects[get_class(DB::getConn())]);
		
		file_put_contents(self::get_sql_file_path(), $sql);
		return self::get_sql_file_path();
	}
	/**
	 * Handle orphaned rows in intermediate many_many tables.
	 */
	public function handleBrokenManyManyRelations() {
		JanitorDebug::message("handleBrokenManyManyRelations()");
		$dbTables = DataObjectOnDeleteDecorator::get_db_tables();
		foreach ((array)$this->dataObject->many_many() as $relationName => $relationClass) {
			list($parentClass, $componentClass, $parentField, $componentField, $table) = $this->dataObject->many_many($relationName);
			if (!in_array($table, DataObjectOnDeleteDecorator_ManyManyCleaner::get_ignored_tables()) &&
				in_array(strtolower($table), $dbTables)) {
				$oneWayTables = DataObjectOnDeleteDecorator_ManyManyCleaner::get_one_way_tables();
				$oneWayTable = array_key_exists($table, $oneWayTables);
				if ($oneWayTable && $oneWayTables[$table] != $relationName) {
					JanitorDebug::message("Handling one-way table \"{$table}\" from \"{$relationName}\", aborting");
					continue;
				}
				$query = "SELECT \"{$parentField}\" FROM \"{$table}\" WHERE 1=1";
				$relations = DB::query($query)->column($parentField);
				foreach ((array)$relations as $ID) {
					$baseDataObject = DataObject::get_by_id($this->baseClassName, $ID);
					if ($baseDataObject)
						continue;
					$query = "DELETE FROM \"{$table}\" WHERE \"{$parentField}\" = {$ID}";
					JanitorDebug::message("Running query: {$query}", 'p', 'color:#ff0000');
					DB::query($query);
				}
			}
		}
	}
	/**
	 * Clean the intermediate table from references to $this->ID.
	 */
	protected function handleRelation($class, $classRelationName) {
		$dbTables = DataObjectOnDeleteDecorator::get_db_tables();
		list($parentClass, $componentClass, $parentField, $componentField, $table) = $this->object->many_many($classRelationName);
		if (!in_array($table, self::get_ignored_tables()) && in_array(strtolower($table), $dbTables)) {
			$oneWayTables = self::get_one_way_tables();
			$oneWayTable = array_key_exists($table, $oneWayTables);
			if (!$oneWayTable || ($oneWayTable && $oneWayTables[$table] == $classRelationName)) {
				DB::query("DELETE FROM \"{$table}\" WHERE \"{$parentField}\" = {$this->ID}");
			}
		}
	}
	public function testWithOrphanedManyManyRelations() {
		DataObjectOnDeleteDecorator::set_disabled(true);
		//JanitorDebug::set_verbose(true);
		
		$page1 = new Page();
		$page1->write();
		$page1->doPublish();
		$page1ID = $page1->ID;
		
		$page2 = new Page();
		$page2->Content .= "<p><a href=\"[sitetree_link id={$page1->ID}]\">page1 link</a></p>";
		$page2->write();
		$page2->doPublish();
		$page2ID = $page2->ID;
		
		$query = "SELECT \"ID\" FROM \"SiteTree_LinkTracking\" WHERE \"SiteTreeID\" = $page2ID AND \"ChildID\" = $page1ID";
		
		$page2->deleteFromStage('Live');
		$page2 = DataObject::get_by_id('Page', $page2ID);
		$page2->delete();
		
		$this->assertTrue((bool)DB::query($query)->value(),
			"many_many table SiteTree_LinkTracking cleaned prematurely (possibly due to SilverStripe core changes)");
		
		DataObjectOnDeleteDecorator::set_disabled(false);
		$task = new DataObjectRetroactiveCleanerTask();
		$task->run(null);
		$task->deleteBackup();
		
		$this->assertFalse((bool)DB::query($query)->value(),
			"many_many table SiteTree_LinkTracking not cleaned properly (retroactively)");
		JanitorDebug::set_verbose(false);
	}