diff --git a/core/ClassInfo.php b/core/ClassInfo.php index 7aa17fa56..9c0f845fc 100644 --- a/core/ClassInfo.php +++ b/core/ClassInfo.php @@ -58,8 +58,10 @@ class ClassInfo { * Returns the manifest of all classes which are present in the database. * @param string $class Class name to check enum values for ClassName field */ - static function getValidSubClasses($class = 'SiteTree') { - return DB::getConn()->enumValuesForField($class, 'ClassName'); + static function getValidSubClasses($class = 'SiteTree', $includeUnbacked = false) { + $classes = DB::getConn()->enumValuesForField($class, 'ClassName'); + if (!$includeUnbacked) $classes = array_filter($classes, array('ClassInfo', 'exists')); + return $classes; } /** diff --git a/model/DataObject.php b/model/DataObject.php index e624a794d..cbfa2876f 100644 --- a/model/DataObject.php +++ b/model/DataObject.php @@ -181,9 +181,12 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity */ public static function database_fields($class) { if(get_parent_class($class) == 'DataObject') { + $db = DB::getConn(); + $existing = $db->hasField($class, 'ClassName') ? $db->query("SELECT DISTINCT \"ClassName\" FROM \"$class\"")->column() : array(); + return array_merge ( array ( - 'ClassName' => "Enum('" . implode(', ', ClassInfo::subclassesFor($class)) . "')", + 'ClassName' => "Enum('" . implode(', ', array_unique(array_merge($existing, ClassInfo::subclassesFor($class)))) . "')", 'Created' => 'SS_Datetime', 'LastEdited' => 'SS_Datetime' ), @@ -443,6 +446,17 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity } } } + + function getObsoleteClassName() { + $className = $this->getField("ClassName"); + if (!ClassInfo::exists($className)) return $className; + } + + function getClassName() { + $className = $this->getField("ClassName"); + if (!ClassInfo::exists($className)) return get_class($this); + return $className; + } /** * Set the ClassName attribute. {@link $class} is also updated. @@ -985,17 +999,34 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity $firstWrite = false; $this->brokenOnWrite = true; $isNewRecord = false; - - if(self::get_validation_enabled()) { + + $writeException = null; + + if ($this->ObsoleteClassName) { + $writeException = new ValidationException( + "Object is of class '{$this->ObsoleteClassName}' which doesn't exist - ". + "you need to change the ClassName before you can write it", + E_USER_WARNING + ); + } + else if(self::get_validation_enabled()) { $valid = $this->validate(); - if(!$valid->valid()) { - // Used by DODs to clean up after themselves, eg, Versioned - $this->extend('onAfterSkippedWrite'); - throw new ValidationException($valid, "Validation error writing a $this->class object: " . $valid->message() . ". Object not written.", E_USER_WARNING); - return false; + if (!$valid->valid()) { + $writeException = new ValidationException( + $valid, + "Validation error writing a $this->class object: " . $valid->message() . ". Object not written.", + E_USER_WARNING + ); } } + if($writeException) { + // Used by DODs to clean up after themselves, eg, Versioned + $this->extend('onAfterSkippedWrite'); + throw $writeException; + return false; + } + $this->onBeforeWrite(); if($this->brokenOnWrite) { user_error("$this->class has a broken onBeforeWrite() function. Make sure that you call parent::onBeforeWrite().", E_USER_ERROR); diff --git a/model/Database.php b/model/Database.php index d76f73853..7e32cd114 100644 --- a/model/Database.php +++ b/model/Database.php @@ -143,7 +143,7 @@ abstract class SS_Database { * Returns true if the given table exists in the database */ abstract function hasTable($tableName); - + /** * Returns the enum values available on the given field */ @@ -439,6 +439,18 @@ abstract class SS_Database { } } + /** + * Return true if the table exists and already has a the field specified + * @param string $tableName - The table to check + * @param string $fieldName - The field to check + * @return bool - True if the table exists and the field exists on the table + */ + function hasField($tableName, $fieldName) { + if (!$this->hasTable($tableName)) return false; + $fields = $this->fieldList($tableName); + return array_key_exists($fieldName, $fields); + } + /** * Generate the given field on the table, modifying whatever already exists as necessary. * @param string $table The table name. diff --git a/model/Versioned.php b/model/Versioned.php index 25da1a7c4..b8efd4d87 100644 --- a/model/Versioned.php +++ b/model/Versioned.php @@ -1103,7 +1103,7 @@ class Versioned_Version extends ViewableData { $record['ID'] = $record['RecordID']; $className = $record['ClassName']; - $this->object = new $className($record); + $this->object = ClassInfo::exists($className) ? new $className($record) : new DataObject($record); $this->failover = $this->object; parent::__construct();