mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge pull request #3949 from dhensby/pulls/case-sensitive-class-info
Ensuring classinfo is case insensitive
This commit is contained in:
commit
912b133ba1
@ -1,36 +1,36 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Use the SilverStripe configuration system to lookup config for a
|
||||
* Use the SilverStripe configuration system to lookup config for a
|
||||
* particular service.
|
||||
*
|
||||
* @package framework
|
||||
* @subpackage injector
|
||||
*/
|
||||
class SilverStripeServiceConfigurationLocator extends ServiceConfigurationLocator {
|
||||
|
||||
|
||||
/**
|
||||
* List of Injector configurations cached from Config in class => config format.
|
||||
* If any config is false, this denotes that this class and all its parents
|
||||
* If any config is false, this denotes that this class and all its parents
|
||||
* have no configuration specified.
|
||||
*
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $configs = array();
|
||||
|
||||
|
||||
public function locateConfigFor($name) {
|
||||
|
||||
|
||||
// Check direct or cached result
|
||||
$config = $this->configFor($name);
|
||||
if($config !== null) return $config;
|
||||
|
||||
|
||||
// do parent lookup if it's a class
|
||||
if (class_exists($name)) {
|
||||
$parents = array_reverse(array_keys(ClassInfo::ancestry($name)));
|
||||
$parents = array_reverse(array_values(ClassInfo::ancestry($name)));
|
||||
array_shift($parents);
|
||||
|
||||
foreach ($parents as $parent) {
|
||||
// have we already got for this?
|
||||
// have we already got for this?
|
||||
$config = $this->configFor($parent);
|
||||
if($config !== null) {
|
||||
// Cache this result
|
||||
@ -39,27 +39,27 @@ class SilverStripeServiceConfigurationLocator extends ServiceConfigurationLocato
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// there is no parent config, so we'll record that as false so we don't do the expensive
|
||||
// lookup through parents again
|
||||
$this->configs[$name] = false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the config for a named service without performing a hierarchy walk
|
||||
*
|
||||
*
|
||||
* @param string $name Name of service
|
||||
* @return mixed Returns either the configuration data, if there is any. A missing config is denoted
|
||||
* @return mixed Returns either the configuration data, if there is any. A missing config is denoted
|
||||
* by a value of either null (there is no direct config assigned and a hierarchy walk is necessary)
|
||||
* or false (there is no config for this class, nor within the hierarchy for this class).
|
||||
* or false (there is no config for this class, nor within the hierarchy for this class).
|
||||
*/
|
||||
protected function configFor($name) {
|
||||
|
||||
|
||||
// Return cached result
|
||||
if (isset($this->configs[$name])) {
|
||||
return $this->configs[$name]; // Potentially false
|
||||
}
|
||||
|
||||
|
||||
$config = Config::inst()->get('Injector', $name);
|
||||
if ($config) {
|
||||
$this->configs[$name] = $config;
|
||||
|
@ -3,8 +3,8 @@
|
||||
/**
|
||||
* Provides introspection information about the class tree.
|
||||
*
|
||||
* It's a cached wrapper around the built-in class functions. SilverStripe uses
|
||||
* class introspection heavily and without the caching it creates an unfortunate
|
||||
* It's a cached wrapper around the built-in class functions. SilverStripe uses
|
||||
* class introspection heavily and without the caching it creates an unfortunate
|
||||
* performance hit.
|
||||
*
|
||||
* @package framework
|
||||
@ -35,7 +35,7 @@ class ClassInfo {
|
||||
* @var Array Cache for {@link ancestry()}.
|
||||
*/
|
||||
private static $_cache_ancestry = array();
|
||||
|
||||
|
||||
/**
|
||||
* @todo Move this to SS_Database or DB
|
||||
*/
|
||||
@ -52,17 +52,18 @@ class ClassInfo {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function reset_db_cache() {
|
||||
self::$_cache_all_tables = null;
|
||||
self::$_cache_ancestry = array();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the manifest of all classes which are present in the database.
|
||||
* @param string $class Class name to check enum values for ClassName field
|
||||
*/
|
||||
public static function getValidSubClasses($class = 'SiteTree', $includeUnbacked = false) {
|
||||
$class = self::class_name($class);
|
||||
$classes = DB::getConn()->enumValuesForField($class, 'ClassName');
|
||||
if (!$includeUnbacked) $classes = array_filter($classes, array('ClassInfo', 'exists'));
|
||||
return $classes;
|
||||
@ -71,7 +72,7 @@ class ClassInfo {
|
||||
/**
|
||||
* Returns an array of the current class and all its ancestors and children
|
||||
* which have a DB table.
|
||||
*
|
||||
*
|
||||
* @param string|object $class
|
||||
* @todo Move this into data object
|
||||
* @return array
|
||||
@ -79,9 +80,7 @@ class ClassInfo {
|
||||
public static function dataClassesFor($class) {
|
||||
$result = array();
|
||||
|
||||
if (is_object($class)) {
|
||||
$class = get_class($class);
|
||||
}
|
||||
$class = self::class_name($class);
|
||||
|
||||
$classes = array_merge(
|
||||
self::ancestry($class),
|
||||
@ -102,7 +101,7 @@ class ClassInfo {
|
||||
* @return string
|
||||
*/
|
||||
public static function baseDataClass($class) {
|
||||
if (is_object($class)) $class = get_class($class);
|
||||
$class = self::class_name($class);
|
||||
|
||||
if (!is_subclass_of($class, 'DataObject')) {
|
||||
throw new InvalidArgumentException("$class is not a subclass of DataObject");
|
||||
@ -121,23 +120,25 @@ class ClassInfo {
|
||||
* Returns a list of classes that inherit from the given class.
|
||||
* The resulting array includes the base class passed
|
||||
* through the $class parameter as the first array value.
|
||||
*
|
||||
*
|
||||
* Example usage:
|
||||
* <code>
|
||||
* ClassInfo::subclassesFor('BaseClass');
|
||||
* array(
|
||||
* 0 => 'BaseClass',
|
||||
* 'BaseClass' => 'BaseClass',
|
||||
* 'ChildClass' => 'ChildClass',
|
||||
* 'GrandChildClass' => 'GrandChildClass'
|
||||
* )
|
||||
* </code>
|
||||
*
|
||||
*
|
||||
* @param mixed $class string of the classname or instance of the class
|
||||
* @return array Names of all subclasses as an associative array.
|
||||
*/
|
||||
public static function subclassesFor($class) {
|
||||
//normalise class case
|
||||
$className = self::class_name($class);
|
||||
$descendants = SS_ClassLoader::instance()->getManifest()->getDescendantsOf($class);
|
||||
$result = array($class => $class);
|
||||
$result = array($className => $className);
|
||||
|
||||
if ($descendants) {
|
||||
return $result + ArrayLib::valuekey($descendants);
|
||||
@ -146,6 +147,23 @@ class ClassInfo {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a class name in any case and return it as it was defined in PHP
|
||||
*
|
||||
* eg: self::class_name('dataobJEct'); //returns 'DataObject'
|
||||
*
|
||||
* @param string|object $nameOrObject The classname or object you want to normalise
|
||||
*
|
||||
* @return string The normalised class name
|
||||
*/
|
||||
public static function class_name($nameOrObject) {
|
||||
if (is_object($nameOrObject)) {
|
||||
return get_class($nameOrObject);
|
||||
}
|
||||
$reflection = new ReflectionClass($nameOrObject);
|
||||
return $reflection->getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the passed class name along with all its parent class names in an
|
||||
* array, sorted with the root class first.
|
||||
@ -155,9 +173,11 @@ class ClassInfo {
|
||||
* @return array
|
||||
*/
|
||||
public static function ancestry($class, $tablesOnly = false) {
|
||||
if (!is_string($class)) $class = get_class($class);
|
||||
$class = self::class_name($class);
|
||||
|
||||
$cacheKey = $class . '_' . (string)$tablesOnly;
|
||||
$lClass = strtolower($class);
|
||||
|
||||
$cacheKey = $lClass . '_' . (string)$tablesOnly;
|
||||
$parent = $class;
|
||||
if(!isset(self::$_cache_ancestry[$cacheKey])) {
|
||||
$ancestry = array();
|
||||
@ -166,7 +186,7 @@ class ClassInfo {
|
||||
$ancestry[$parent] = $parent;
|
||||
}
|
||||
} while ($parent = get_parent_class($parent));
|
||||
self::$_cache_ancestry[$cacheKey] = array_reverse($ancestry);
|
||||
self::$_cache_ancestry[$cacheKey] = array_reverse($ancestry);
|
||||
}
|
||||
|
||||
return self::$_cache_ancestry[$cacheKey];
|
||||
@ -184,16 +204,16 @@ class ClassInfo {
|
||||
* Returns true if the given class implements the given interface
|
||||
*/
|
||||
public static function classImplements($className, $interfaceName) {
|
||||
return in_array($className, SS_ClassLoader::instance()->getManifest()->getImplementorsOf($interfaceName));
|
||||
return in_array($className, self::implementorsOf($interfaceName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all classes contained in a file.
|
||||
* @uses ManifestBuilder
|
||||
*
|
||||
*
|
||||
* @todo Doesn't return additional classes that only begin
|
||||
* with the filename, and have additional naming separated through underscores.
|
||||
*
|
||||
*
|
||||
* @param string $filePath Path to a PHP file (absolute or relative to webroot)
|
||||
* @return array
|
||||
*/
|
||||
@ -205,16 +225,16 @@ class ClassInfo {
|
||||
foreach($manifest as $class => $compareFilePath) {
|
||||
if($absFilePath == $compareFilePath) $matchedClasses[] = $class;
|
||||
}
|
||||
|
||||
|
||||
return $matchedClasses;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns all classes contained in a certain folder.
|
||||
*
|
||||
* @todo Doesn't return additional classes that only begin
|
||||
* with the filename, and have additional naming separated through underscores.
|
||||
*
|
||||
*
|
||||
* @param string $folderPath Relative or absolute folder path
|
||||
* @return array Array of class names
|
||||
*/
|
||||
@ -233,25 +253,28 @@ class ClassInfo {
|
||||
private static $method_from_cache = array();
|
||||
|
||||
public static function has_method_from($class, $method, $compclass) {
|
||||
if (!isset(self::$method_from_cache[$class])) self::$method_from_cache[$class] = array();
|
||||
$lClass = strtolower($class);
|
||||
$lMethod = strtolower($method);
|
||||
$lCompclass = strtolower($compclass);
|
||||
if (!isset(self::$method_from_cache[$lClass])) self::$method_from_cache[$lClass] = array();
|
||||
|
||||
if (!array_key_exists($method, self::$method_from_cache[$class])) {
|
||||
self::$method_from_cache[$class][$method] = false;
|
||||
if (!array_key_exists($lMethod, self::$method_from_cache[$lClass])) {
|
||||
self::$method_from_cache[$lClass][$lMethod] = false;
|
||||
|
||||
$classRef = new ReflectionClass($class);
|
||||
|
||||
if ($classRef->hasMethod($method)) {
|
||||
$methodRef = $classRef->getMethod($method);
|
||||
self::$method_from_cache[$class][$method] = $methodRef->getDeclaringClass()->getName();
|
||||
self::$method_from_cache[$lClass][$lMethod] = $methodRef->getDeclaringClass()->getName();
|
||||
}
|
||||
}
|
||||
|
||||
return self::$method_from_cache[$class][$method] == $compclass;
|
||||
return strtolower(self::$method_from_cache[$lClass][$lMethod]) == $lCompclass;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns the table name in the class hierarchy which contains a given
|
||||
* Returns the table name in the class hierarchy which contains a given
|
||||
* field column for a {@link DataObject}. If the field does not exist, this
|
||||
* will return null.
|
||||
*
|
||||
@ -261,23 +284,26 @@ class ClassInfo {
|
||||
* @return string
|
||||
*/
|
||||
public static function table_for_object_field($candidateClass, $fieldName) {
|
||||
if(!$candidateClass || !$fieldName) {
|
||||
if(!$candidateClass || !$fieldName || !is_subclass_of($candidateClass, 'DataObject')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$exists = class_exists($candidateClass);
|
||||
//normalise class name
|
||||
$candidateClass = self::class_name($candidateClass);
|
||||
|
||||
$exists = self::exists($candidateClass);
|
||||
|
||||
while($candidateClass && $candidateClass != 'DataObject' && $exists) {
|
||||
if(DataObject::has_own_table($candidateClass)) {
|
||||
$inst = singleton($candidateClass);
|
||||
|
||||
|
||||
if($inst->hasOwnTableDatabaseField($fieldName)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$candidateClass = get_parent_class($candidateClass);
|
||||
$exists = class_exists($candidateClass);
|
||||
$exists = $candidateClass && self::exists($candidateClass);
|
||||
}
|
||||
|
||||
if(!$candidateClass || !$exists) {
|
||||
|
@ -5,16 +5,16 @@
|
||||
* <h2>Extensions</h2>
|
||||
*
|
||||
* See {@link Extension} and {@link DataExtension}.
|
||||
*
|
||||
*
|
||||
* <h2>Permission Control</h2>
|
||||
*
|
||||
*
|
||||
* Object-level access control by {@link Permission}. Permission codes are arbitrary
|
||||
* strings which can be selected on a group-by-group basis.
|
||||
*
|
||||
*
|
||||
* <code>
|
||||
* class Article extends DataObject implements PermissionProvider {
|
||||
* static $api_access = true;
|
||||
*
|
||||
*
|
||||
* function canView($member = false) {
|
||||
* return Permission::check('ARTICLE_VIEW');
|
||||
* }
|
||||
@ -36,13 +36,13 @@
|
||||
* );
|
||||
* }
|
||||
* }
|
||||
* </code>
|
||||
* </code>
|
||||
*
|
||||
* Object-level access control by {@link Group} membership:
|
||||
* Object-level access control by {@link Group} membership:
|
||||
* <code>
|
||||
* class Article extends DataObject {
|
||||
* static $api_access = true;
|
||||
*
|
||||
*
|
||||
* function canView($member = false) {
|
||||
* if(!$member) $member = Member::currentUser();
|
||||
* return $member->inGroup('Subscribers');
|
||||
@ -51,18 +51,18 @@
|
||||
* if(!$member) $member = Member::currentUser();
|
||||
* return $member->inGroup('Editors');
|
||||
* }
|
||||
*
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* If any public method on this class is prefixed with an underscore,
|
||||
*
|
||||
* If any public method on this class is prefixed with an underscore,
|
||||
* the results are cached in memory through {@link cachedCall()}.
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
* @todo Add instance specific removeExtension() which undos loadExtraStatics()
|
||||
* and defineMethods()
|
||||
*
|
||||
*
|
||||
* @package framework
|
||||
* @subpackage model
|
||||
*
|
||||
@ -72,21 +72,21 @@
|
||||
* @property string Created Date and time of DataObject creation.
|
||||
*/
|
||||
class DataObject extends ViewableData implements DataObjectInterface, i18nEntityProvider {
|
||||
|
||||
|
||||
/**
|
||||
* Human-readable singular name.
|
||||
* @var string
|
||||
* @config
|
||||
*/
|
||||
private static $singular_name = null;
|
||||
|
||||
|
||||
/**
|
||||
* Human-readable plural name
|
||||
* @var string
|
||||
* @config
|
||||
*/
|
||||
private static $plural_name = null;
|
||||
|
||||
|
||||
/**
|
||||
* Allow API access to this object?
|
||||
* @todo Define the options that can be set here
|
||||
@ -99,18 +99,18 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* @var boolean
|
||||
*/
|
||||
public $destroyed = false;
|
||||
|
||||
|
||||
/**
|
||||
* The DataModel from this this object comes
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
|
||||
/**
|
||||
* Data stored in this objects database record. An array indexed by fieldname.
|
||||
*
|
||||
* Data stored in this objects database record. An array indexed by fieldname.
|
||||
*
|
||||
* Use {@link toMap()} if you want an array representation
|
||||
* of this object, as the $record array might contain lazy loaded field aliases.
|
||||
*
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $record;
|
||||
@ -119,7 +119,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* An array indexed by fieldname, true if the field has been changed.
|
||||
* Use {@link getChangedFields()} and {@link isChanged()} to inspect
|
||||
* the changed state.
|
||||
*
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $changed;
|
||||
@ -136,13 +136,13 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* @var boolean
|
||||
*/
|
||||
protected $brokenOnDelete = false;
|
||||
|
||||
|
||||
/**
|
||||
* Used by onBeforeWrite() to ensure child classes call parent::onBeforeWrite()
|
||||
* @var boolean
|
||||
*/
|
||||
protected $brokenOnWrite = false;
|
||||
|
||||
|
||||
/**
|
||||
* @config
|
||||
* @var boolean Should dataobjects be validated before they are written?
|
||||
@ -185,7 +185,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
|
||||
/**
|
||||
* Returns when validation on DataObjects is enabled.
|
||||
*
|
||||
*
|
||||
* @deprecated 3.2 Use the "DataObject.validation_enabled" config setting instead
|
||||
* @return bool
|
||||
*/
|
||||
@ -193,14 +193,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
Deprecation::notice('3.2', 'Use the "DataObject.validation_enabled" config setting instead');
|
||||
return Config::inst()->get('DataObject', 'validation_enabled');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set whether DataObjects should be validated before they are written.
|
||||
*
|
||||
*
|
||||
* Caution: Validation can contain safeguards against invalid/malicious data,
|
||||
* and check permission levels (e.g. on {@link Group}). Therefore it is recommended
|
||||
* to only disable validation for very specific use cases.
|
||||
*
|
||||
*
|
||||
* @param $enable bool
|
||||
* @see DataObject::validate()
|
||||
* @deprecated 3.2 Use the "DataObject.validation_enabled" config setting instead
|
||||
@ -214,7 +214,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* @var [string] - class => ClassName field definition cache for self::database_fields
|
||||
*/
|
||||
private static $classname_spec_cache = array();
|
||||
|
||||
|
||||
/**
|
||||
* Clear all cached classname specs. It's necessary to clear all cached subclassed names
|
||||
* for any classes if a new class manifest is generated.
|
||||
@ -260,14 +260,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all database columns explicitly defined on a class in {@link DataObject::$db}
|
||||
* and {@link DataObject::$has_one}. Resolves instances of {@link CompositeDBField}
|
||||
* into the actual database fields, rather than the name of the field which
|
||||
* Get all database columns explicitly defined on a class in {@link DataObject::$db}
|
||||
* and {@link DataObject::$has_one}. Resolves instances of {@link CompositeDBField}
|
||||
* into the actual database fields, rather than the name of the field which
|
||||
* might not equate a database column.
|
||||
*
|
||||
*
|
||||
* Does not include "base fields" like "ID", "ClassName", "Created", "LastEdited",
|
||||
* see {@link database_fields()}.
|
||||
*
|
||||
*
|
||||
* @uses CompositeDBField->compositeDatabaseFields()
|
||||
*
|
||||
* @param string $class
|
||||
@ -283,14 +283,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
foreach(self::composite_fields($class, false) as $fieldName => $fieldClass) {
|
||||
// Remove the original fieldname, it's not an actual database column
|
||||
unset($fields[$fieldName]);
|
||||
|
||||
|
||||
// Add all composite columns
|
||||
$compositeFields = singleton($fieldClass)->compositeDatabaseFields();
|
||||
if($compositeFields) foreach($compositeFields as $compositeName => $spec) {
|
||||
$fields["{$fieldName}{$compositeName}"] = $spec;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Add has_one relationships
|
||||
$hasOne = Config::inst()->get($class, 'has_one', Config::UNINHERITED);
|
||||
if($hasOne) foreach(array_keys($hasOne) as $field) {
|
||||
@ -303,7 +303,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the field class if the given db field on the class is a composite field.
|
||||
* Will check all applicable ancestor classes and aggregate results.
|
||||
@ -317,7 +317,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
if(!isset(DataObject::$_cache_composite_fields[$class])) {
|
||||
self::cache_composite_fields($class);
|
||||
}
|
||||
|
||||
|
||||
if(isset(DataObject::$_cache_composite_fields[$class][$name])) {
|
||||
$isComposite = DataObject::$_cache_composite_fields[$class][$name];
|
||||
} elseif($aggregated && $class != 'DataObject' && ($parentClass=get_parent_class($class)) != 'DataObject') {
|
||||
@ -336,14 +336,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
*/
|
||||
public static function composite_fields($class, $aggregated = true) {
|
||||
if(!isset(DataObject::$_cache_composite_fields[$class])) self::cache_composite_fields($class);
|
||||
|
||||
|
||||
$compositeFields = DataObject::$_cache_composite_fields[$class];
|
||||
|
||||
|
||||
if($aggregated && $class != 'DataObject' && ($parentClass=get_parent_class($class)) != 'DataObject') {
|
||||
$compositeFields = array_merge($compositeFields,
|
||||
$compositeFields = array_merge($compositeFields,
|
||||
self::composite_fields($parentClass));
|
||||
}
|
||||
|
||||
|
||||
return $compositeFields;
|
||||
}
|
||||
|
||||
@ -352,7 +352,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
*/
|
||||
private static function cache_composite_fields($class) {
|
||||
$compositeFields = array();
|
||||
|
||||
|
||||
$fields = Config::inst()->get($class, 'db', Config::UNINHERITED);
|
||||
if($fields) foreach($fields as $fieldName => $fieldClass) {
|
||||
if(!is_string($fieldClass)) continue;
|
||||
@ -360,16 +360,16 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
// Strip off any parameters
|
||||
$bPos = strpos($fieldClass, '(');
|
||||
if($bPos !== FALSE) $fieldClass = substr($fieldClass, 0, $bPos);
|
||||
|
||||
|
||||
// Test to see if it implements CompositeDBField
|
||||
if(ClassInfo::classImplements($fieldClass, 'CompositeDBField')) {
|
||||
$compositeFields[$fieldName] = $fieldClass;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
DataObject::$_cache_composite_fields[$class] = $compositeFields;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Construct a new DataObject.
|
||||
*
|
||||
@ -448,7 +448,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
// prevent populateDefaults() and setField() from marking overwritten defaults as changed
|
||||
$this->changed = array();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the DataModel
|
||||
* @param DataModel $model
|
||||
@ -481,14 +481,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$className = $this->class;
|
||||
$clone = new $className( $this->toMap(), false, $this->model );
|
||||
$clone->ID = 0;
|
||||
|
||||
|
||||
$clone->invokeWithExtensions('onBeforeDuplicate', $this, $doWrite);
|
||||
if($doWrite) {
|
||||
$clone->write();
|
||||
$this->duplicateManyManyRelations($this, $clone);
|
||||
}
|
||||
$clone->invokeWithExtensions('onAfterDuplicate', $this, $doWrite);
|
||||
|
||||
|
||||
return $clone;
|
||||
}
|
||||
|
||||
@ -526,7 +526,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* Helper function to duplicate relations from one object to another
|
||||
* @param $sourceObject the source object to duplicate from
|
||||
* @param $destinationObject the destination object to populate with the duplicated relations
|
||||
* @param $name the name of the relation to duplicate (e.g. members)
|
||||
* @param $name the name of the relation to duplicate (e.g. members)
|
||||
*/
|
||||
private function duplicateRelations($sourceObject, $destinationObject, $name) {
|
||||
$relations = $sourceObject->$name();
|
||||
@ -553,7 +553,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
if (!ClassInfo::exists($className)) return get_class($this);
|
||||
return $className;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the ClassName attribute. {@link $class} is also updated.
|
||||
* Warning: This will produce an inconsistent record, as the object
|
||||
@ -579,7 +579,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* it ensures that the instance of the class is a match for the className of the
|
||||
* record. Don't set the {@link DataObject->class} or {@link DataObject->ClassName}
|
||||
* property manually before calling this method, as it will confuse change detection.
|
||||
*
|
||||
*
|
||||
* If the new class is different to the original class, defaults are populated again
|
||||
* because this will only occur automatically on instantiation of a DataObject if
|
||||
* there is no record, or the record has no ID. In this case, we do have an ID but
|
||||
@ -598,7 +598,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
'RecordClassName' => $originalClass,
|
||||
)
|
||||
), false, $this->model);
|
||||
|
||||
|
||||
if($newClassName != $originalClass) {
|
||||
$newInstance->setClassName($newClassName);
|
||||
$newInstance->populateDefaults();
|
||||
@ -667,9 +667,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* Returns TRUE if all values (other than "ID") are
|
||||
* considered empty (by weak boolean comparison).
|
||||
* Only checks for fields listed in {@link custom_database_fields()}
|
||||
*
|
||||
*
|
||||
* @todo Use DBField->hasValue()
|
||||
*
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isEmpty(){
|
||||
@ -679,7 +679,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
foreach($map as $k=>$v){
|
||||
// only look at custom fields
|
||||
if(!array_key_exists($k, $customFields)) continue;
|
||||
|
||||
|
||||
$dbObj = ($v instanceof DBField) ? $v : $this->dbObject($k);
|
||||
$isEmpty = ($isEmpty && !$dbObj->exists());
|
||||
}
|
||||
@ -698,7 +698,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
if(!$name = $this->stat('singular_name')) {
|
||||
$name = ucwords(trim(strtolower(preg_replace('/_?([A-Z])/', ' $1', $this->class))));
|
||||
}
|
||||
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
@ -749,14 +749,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$name = $this->plural_name();
|
||||
return _t($this->class.'.PLURALNAME', $name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Standard implementation of a title/label for a specific
|
||||
* record. Tries to find properties 'Title' or 'Name',
|
||||
* and falls back to the 'ID'. Useful to provide
|
||||
* user-friendly identification of a record, e.g. in errormessages
|
||||
* or UI-selections.
|
||||
*
|
||||
*
|
||||
* Overload this method to have a more specialized implementation,
|
||||
* e.g. for an Address record this could be:
|
||||
* <code>
|
||||
@ -770,7 +770,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
public function getTitle() {
|
||||
if($this->hasDatabaseField('Title')) return $this->getField('Title');
|
||||
if($this->hasDatabaseField('Name')) return $this->getField('Name');
|
||||
|
||||
|
||||
return "#{$this->ID}";
|
||||
}
|
||||
|
||||
@ -808,11 +808,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
|
||||
/**
|
||||
* Update a number of fields on this object, given a map of the desired changes.
|
||||
*
|
||||
*
|
||||
* The field names can be simple names, or you can use a dot syntax to access $has_one relations.
|
||||
* For example, array("Author.FirstName" => "Jim") will set $this->Author()->FirstName to "Jim".
|
||||
*
|
||||
* update() doesn't write the main object, but if you use the dot syntax, it will write()
|
||||
*
|
||||
* update() doesn't write the main object, but if you use the dot syntax, it will write()
|
||||
* the related objects that it alters.
|
||||
*
|
||||
* @param array $data A map of field name to data values to update.
|
||||
@ -840,8 +840,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
} else {
|
||||
user_error(
|
||||
"DataObject::update(): Can't traverse relationship '$relation'," .
|
||||
"it has to be a has_one relationship or return a single DataObject",
|
||||
"DataObject::update(): Can't traverse relationship '$relation'," .
|
||||
"it has to be a has_one relationship or return a single DataObject",
|
||||
E_USER_NOTICE
|
||||
);
|
||||
// unset relation object so we don't write properties to the wrong object
|
||||
@ -865,7 +865,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pass changes as a map, and try to
|
||||
* get automatic casting for these fields.
|
||||
@ -977,7 +977,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* Forces the record to think that all its data has changed.
|
||||
* Doesn't write to the database. Only sets fields as changed
|
||||
* if they are not already marked as changed.
|
||||
*
|
||||
*
|
||||
* @return DataObject $this
|
||||
*/
|
||||
public function forceChange() {
|
||||
@ -986,32 +986,32 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
|
||||
// $this->record might not contain the blank values so we loop on $this->inheritedDatabaseFields() as well
|
||||
$fieldNames = array_unique(array_merge(
|
||||
array_keys($this->record),
|
||||
array_keys($this->record),
|
||||
array_keys($this->inheritedDatabaseFields())));
|
||||
|
||||
|
||||
foreach($fieldNames as $fieldName) {
|
||||
if(!isset($this->changed[$fieldName])) $this->changed[$fieldName] = 1;
|
||||
// Populate the null values in record so that they actually get written
|
||||
if(!isset($this->record[$fieldName])) $this->record[$fieldName] = null;
|
||||
}
|
||||
|
||||
|
||||
// @todo Find better way to allow versioned to write a new version after forceChange
|
||||
if($this->isChanged('Version')) unset($this->changed['Version']);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validate the current object.
|
||||
*
|
||||
* By default, there is no validation - objects are always valid! However, you can overload this method in your
|
||||
* DataObject sub-classes to specify custom validation, or use the hook through DataExtension.
|
||||
*
|
||||
*
|
||||
* Invalid objects won't be able to be written - a warning will be thrown and no write will occur. onBeforeWrite()
|
||||
* and onAfterWrite() won't get called either.
|
||||
*
|
||||
*
|
||||
* It is expected that you call validate() in your own application to test that an object is valid before
|
||||
* attempting a write, and respond appropriately if it isn't.
|
||||
*
|
||||
*
|
||||
* @see {@link ValidationResult}
|
||||
* @return ValidationResult
|
||||
*/
|
||||
@ -1027,12 +1027,12 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* database. Don't forget to call parent::onBeforeWrite(), though!
|
||||
*
|
||||
* This called after {@link $this->validate()}, so you can be sure that your data is valid.
|
||||
*
|
||||
*
|
||||
* @uses DataExtension->onBeforeWrite()
|
||||
*/
|
||||
protected function onBeforeWrite() {
|
||||
$this->brokenOnWrite = false;
|
||||
|
||||
|
||||
$dummy = null;
|
||||
$this->extend('onBeforeWrite', $dummy);
|
||||
}
|
||||
@ -1059,11 +1059,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
*/
|
||||
protected function onBeforeDelete() {
|
||||
$this->brokenOnDelete = false;
|
||||
|
||||
|
||||
$dummy = null;
|
||||
$this->extend('onBeforeDelete', $dummy);
|
||||
}
|
||||
|
||||
|
||||
protected function onAfterDelete() {
|
||||
$this->extend('onAfterDelete');
|
||||
}
|
||||
@ -1072,22 +1072,22 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* Load the default values in from the self::$defaults array.
|
||||
* Will traverse the defaults of the current class and all its parent classes.
|
||||
* Called by the constructor when creating new records.
|
||||
*
|
||||
*
|
||||
* @uses DataExtension->populateDefaults()
|
||||
* @return DataObject $this
|
||||
*/
|
||||
public function populateDefaults() {
|
||||
$classes = array_reverse(ClassInfo::ancestry($this));
|
||||
|
||||
|
||||
foreach($classes as $class) {
|
||||
$defaults = Config::inst()->get($class, 'defaults', Config::UNINHERITED);
|
||||
|
||||
|
||||
if($defaults && !is_array($defaults)) {
|
||||
user_error("Bad '$this->class' defaults given: " . var_export($defaults, true),
|
||||
E_USER_WARNING);
|
||||
$defaults = null;
|
||||
}
|
||||
|
||||
|
||||
if($defaults) foreach($defaults as $fieldName => $fieldValue) {
|
||||
// SRM 2007-03-06: Stricter check
|
||||
if(!isset($this->$fieldName) || $this->$fieldName === null) {
|
||||
@ -1103,7 +1103,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$this->extend('populateDefaults');
|
||||
return $this;
|
||||
}
|
||||
@ -1114,7 +1114,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* - All relevant tables will be updated.
|
||||
* - $this->onBeforeWrite() gets called beforehand.
|
||||
* - Extensions such as Versioned will ammend the database-write to ensure that a version is saved.
|
||||
*
|
||||
*
|
||||
* @uses DataExtension->augmentWrite()
|
||||
*
|
||||
* @param boolean $showDebug Show debugging information
|
||||
@ -1183,7 +1183,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
foreach($this->record as $k => $v) {
|
||||
$this->changed[$k] = 2;
|
||||
}
|
||||
|
||||
|
||||
$firstWrite = true;
|
||||
}
|
||||
|
||||
@ -1207,12 +1207,12 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
}
|
||||
if($hasChanges || $forceWrite || !$this->record['ID']) {
|
||||
|
||||
|
||||
// New records have their insert into the base data table done first, so that they can pass the
|
||||
// generated primary key on to the rest of the manipulation
|
||||
$baseTable = $ancestry[0];
|
||||
|
||||
if((!isset($this->record['ID']) || !$this->record['ID']) && isset($ancestry[0])) {
|
||||
|
||||
if((!isset($this->record['ID']) || !$this->record['ID']) && isset($ancestry[0])) {
|
||||
|
||||
DB::query("INSERT INTO \"{$baseTable}\" (\"Created\") VALUES (" . DB::getConn()->now() . ")");
|
||||
$this->record['ID'] = DB::getGeneratedID($baseTable);
|
||||
@ -1226,9 +1226,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
if(isset($ancestry) && is_array($ancestry)) {
|
||||
foreach($ancestry as $idx => $class) {
|
||||
$classSingleton = singleton($class);
|
||||
|
||||
|
||||
foreach($this->record as $fieldName => $fieldValue) {
|
||||
if(isset($this->changed[$fieldName]) && $this->changed[$fieldName]
|
||||
if(isset($this->changed[$fieldName]) && $this->changed[$fieldName]
|
||||
&& $fieldType = $classSingleton->hasOwnTableDatabaseField($fieldName)) {
|
||||
|
||||
$fieldObj = $this->dbObject($fieldName);
|
||||
@ -1274,13 +1274,13 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
}
|
||||
$this->extend('augmentWrite', $manipulation);
|
||||
|
||||
|
||||
// New records have their insert into the base data table done first, so that they can pass the
|
||||
// generated ID on to the rest of the manipulation
|
||||
if(isset($isNewRecord) && $isNewRecord && isset($manipulation[$baseTable])) {
|
||||
$manipulation[$baseTable]['command'] = 'update';
|
||||
}
|
||||
|
||||
|
||||
DB::manipulate($manipulation);
|
||||
|
||||
// If there's any relations that couldn't be saved before, save them now (we have an ID here)
|
||||
@ -1328,13 +1328,13 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
*/
|
||||
public function writeComponents($recursive = false) {
|
||||
if(!$this->components) return $this;
|
||||
|
||||
|
||||
foreach($this->components as $component) {
|
||||
$component->write(false, false, false, $recursive);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Delete this data object.
|
||||
* $this->onBeforeDelete() gets called.
|
||||
@ -1348,7 +1348,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
user_error("$this->class has a broken onBeforeDelete() function."
|
||||
. " Make sure that you call parent::onBeforeDelete().", E_USER_ERROR);
|
||||
}
|
||||
|
||||
|
||||
// Deleting a record without an ID shouldn't do anything
|
||||
if(!$this->ID) throw new LogicException("DataObject::delete() called on a DataObject without an ID");
|
||||
|
||||
@ -1365,7 +1365,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
// Remove this item out of any caches
|
||||
$this->flushCache();
|
||||
|
||||
|
||||
$this->onAfterDelete();
|
||||
|
||||
$this->OldID = $this->ID;
|
||||
@ -1417,11 +1417,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
if(isset($this->components[$componentName])) {
|
||||
return $this->components[$componentName];
|
||||
}
|
||||
|
||||
|
||||
if($class = $this->has_one($componentName)) {
|
||||
$joinField = $componentName . 'ID';
|
||||
$joinID = $this->getField($joinField);
|
||||
|
||||
|
||||
if($joinID) {
|
||||
$component = $this->model->$class->byID($joinID);
|
||||
}
|
||||
@ -1432,11 +1432,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
} elseif($class = $this->belongs_to($componentName)) {
|
||||
$joinField = $this->getRemoteJoinField($componentName, 'belongs_to');
|
||||
$joinID = $this->ID;
|
||||
|
||||
|
||||
if($joinID) {
|
||||
$component = DataObject::get_one($class, "\"$joinField\" = $joinID");
|
||||
}
|
||||
|
||||
|
||||
if(!isset($component) || !$component) {
|
||||
$component = $this->model->$class->newObject();
|
||||
$component->$joinField = $this->ID;
|
||||
@ -1444,7 +1444,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
} else {
|
||||
throw new Exception("DataObject->getComponent(): Could not find component '$componentName'.");
|
||||
}
|
||||
|
||||
|
||||
$this->components[$componentName] = $component;
|
||||
return $component;
|
||||
}
|
||||
@ -1479,13 +1479,13 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
if(!$this->ID) {
|
||||
if(!isset($this->unsavedRelations[$componentName])) {
|
||||
$this->unsavedRelations[$componentName] =
|
||||
new UnsavedRelationList($this->class, $componentName, $componentClass);
|
||||
new UnsavedRelationList($this->class, $componentName, $componentClass);
|
||||
}
|
||||
return $this->unsavedRelations[$componentName];
|
||||
}
|
||||
|
||||
$joinField = $this->getRemoteJoinField($componentName, 'has_many');
|
||||
|
||||
|
||||
$result = HasManyList::create($componentClass, $joinField);
|
||||
if($this->model) $result->setDataModel($this->model);
|
||||
$result = $result->forForeignID($this->ID);
|
||||
@ -1544,7 +1544,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
*/
|
||||
public function getRemoteJoinField($component, $type = 'has_many') {
|
||||
$remoteClass = $this->$type($component, false);
|
||||
|
||||
|
||||
if(!$remoteClass) {
|
||||
throw new Exception("Unknown $type component '$component' on class '$this->class'");
|
||||
}
|
||||
@ -1553,7 +1553,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
"Class '$remoteClass' not found, but used in $type component '$component' on class '$this->class'"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if($fieldPos = strpos($remoteClass, '.')) {
|
||||
return substr($remoteClass, $fieldPos + 1) . 'ID';
|
||||
}
|
||||
@ -1563,7 +1563,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$remoteRelations = array();
|
||||
}
|
||||
$remoteRelations = array_flip($remoteRelations);
|
||||
|
||||
|
||||
// look for remote has_one joins on this class or any parent classes
|
||||
foreach(array_reverse(ClassInfo::ancestry($this)) as $class) {
|
||||
if(array_key_exists($class, $remoteRelations)) return $remoteRelations[$class] . 'ID';
|
||||
@ -1577,7 +1577,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
throw new Exception($message);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a many-to-many component, as a ManyManyList.
|
||||
* @param string $componentName Name of the many-many component
|
||||
@ -1592,11 +1592,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
if(!$this->ID) {
|
||||
if(!isset($this->unsavedRelations[$componentName])) {
|
||||
$this->unsavedRelations[$componentName] =
|
||||
new UnsavedRelationList($parentClass, $componentName, $componentClass);
|
||||
new UnsavedRelationList($parentClass, $componentName, $componentClass);
|
||||
}
|
||||
return $this->unsavedRelations[$componentName];
|
||||
}
|
||||
|
||||
|
||||
$result = ManyManyList::create($componentClass, $table, $componentField, $parentField,
|
||||
$this->many_many_extraFields($componentName));
|
||||
if($this->model) $result->setDataModel($this->model);
|
||||
@ -1604,10 +1604,10 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
// If this is called on a singleton, then we return an 'orphaned relation' that can have the
|
||||
// foreignID set elsewhere.
|
||||
$result = $result->forForeignID($this->ID);
|
||||
|
||||
|
||||
return $result->where($filter)->sort($sort)->limit($limit);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the class of a one-to-one component. If $component is null, return all of the one-to-one components and
|
||||
* their classes.
|
||||
@ -1626,7 +1626,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
|
||||
if($component) {
|
||||
$hasOne = Config::inst()->get($class, 'has_one', Config::UNINHERITED);
|
||||
|
||||
|
||||
if(isset($hasOne[$component])) {
|
||||
return $hasOne[$component];
|
||||
}
|
||||
@ -1635,7 +1635,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
// Validate the data
|
||||
foreach($newItems as $k => $v) {
|
||||
if(!is_string($k) || is_numeric($k) || !is_string($v)) {
|
||||
user_error("$class::\$has_one has a bad entry: "
|
||||
user_error("$class::\$has_one has a bad entry: "
|
||||
. var_export($k,true). " => " . var_export($v,true) . ". Each map key should be a"
|
||||
. " relationship name, and the map value should be the data class to join to.", E_USER_ERROR);
|
||||
}
|
||||
@ -1645,7 +1645,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
return isset($items) ? $items : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the class of a remote belongs_to relationship. If no component is specified a map of all components and
|
||||
* their class name will be returned.
|
||||
@ -1657,7 +1657,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
*/
|
||||
public function belongs_to($component = null, $classOnly = true) {
|
||||
$belongsTo = $this->config()->belongs_to;
|
||||
|
||||
|
||||
if($component) {
|
||||
if($belongsTo && array_key_exists($component, $belongsTo)) {
|
||||
$belongsTo = $belongsTo[$component];
|
||||
@ -1665,14 +1665,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if($belongsTo && $classOnly) {
|
||||
return preg_replace('/(.+)?\..+/', '$1', $belongsTo);
|
||||
} else {
|
||||
return $belongsTo ? $belongsTo : array();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return all of the database fields defined in self::$db and all the parent classes.
|
||||
* Doesn't include any fields specified by self::$has_one. Use $this->has_one() to get these fields
|
||||
@ -1705,7 +1705,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
// Validate the data
|
||||
foreach($dbItems as $k => $v) {
|
||||
if(!is_string($k) || is_numeric($k) || !is_string($v)) {
|
||||
user_error("$class::\$db has a bad entry: "
|
||||
user_error("$class::\$db has a bad entry: "
|
||||
. var_export($k,true). " => " . var_export($v,true) . ". Each map key should be a"
|
||||
. " property name, and the map value should be the property type.", E_USER_ERROR);
|
||||
}
|
||||
@ -1729,7 +1729,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
*/
|
||||
public function has_many($component = null, $classOnly = true) {
|
||||
$hasMany = $this->config()->has_many;
|
||||
|
||||
|
||||
if($component) {
|
||||
if($hasMany && array_key_exists($component, $hasMany)) {
|
||||
$hasMany = $hasMany[$component];
|
||||
@ -1737,7 +1737,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if($hasMany && $classOnly) {
|
||||
return preg_replace('/(.+)?\..+/', '$1', $hasMany);
|
||||
} else {
|
||||
@ -1747,10 +1747,10 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
|
||||
/**
|
||||
* Return the many-to-many extra fields specification.
|
||||
*
|
||||
*
|
||||
* If you don't specify a component name, it returns all
|
||||
* extra fields for all components available.
|
||||
*
|
||||
*
|
||||
* @param string $component Name of component
|
||||
* @return array
|
||||
*/
|
||||
@ -1770,13 +1770,13 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
if(isset($extraFields[$component])) {
|
||||
return $extraFields[$component];
|
||||
}
|
||||
|
||||
|
||||
$manyMany = $SNG_class->stat('many_many');
|
||||
$candidate = (isset($manyMany[$component])) ? $manyMany[$component] : null;
|
||||
if($candidate) {
|
||||
$SNG_candidate = singleton($candidate);
|
||||
$candidateManyMany = $SNG_candidate->stat('belongs_many_many');
|
||||
|
||||
|
||||
// Find the relation given the class
|
||||
if($candidateManyMany) foreach($candidateManyMany as $relation => $relatedClass) {
|
||||
if($relatedClass == $class) {
|
||||
@ -1784,7 +1784,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if($relationName) {
|
||||
$extraFields = $SNG_candidate->stat('many_many_extraFields');
|
||||
if(isset($extraFields[$relationName])) {
|
||||
@ -1792,30 +1792,30 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$manyMany = $SNG_class->stat('belongs_many_many');
|
||||
$candidate = (isset($manyMany[$component])) ? $manyMany[$component] : null;
|
||||
if($candidate) {
|
||||
$SNG_candidate = singleton($candidate);
|
||||
$candidateManyMany = $SNG_candidate->stat('many_many');
|
||||
|
||||
|
||||
// Find the relation given the class
|
||||
if($candidateManyMany) foreach($candidateManyMany as $relation => $relatedClass) {
|
||||
if($relatedClass == $class) {
|
||||
$relationName = $relation;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$extraFields = $SNG_candidate->stat('many_many_extraFields');
|
||||
if(isset($extraFields[$relationName])) {
|
||||
return $extraFields[$relationName];
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
} else {
|
||||
// Find all the extra fields for all components
|
||||
$newItems = (array)Config::inst()->get($class, 'many_many_extraFields', Config::UNINHERITED);
|
||||
|
||||
|
||||
foreach($newItems as $k => $v) {
|
||||
if(!is_array($v)) {
|
||||
user_error(
|
||||
@ -1826,14 +1826,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$items = isset($items) ? array_merge($newItems, $items) : $newItems;
|
||||
}
|
||||
}
|
||||
|
||||
return isset($items) ? $items : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return information about a many-to-many component.
|
||||
* The return value is an array of (parentclass, childclass). If $component is null, then all many-many
|
||||
@ -1887,18 +1887,18 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
// Validate the data
|
||||
foreach($newItems as $k => $v) {
|
||||
if(!is_string($k) || is_numeric($k) || !is_string($v)) {
|
||||
user_error("$class::\$many_many has a bad entry: "
|
||||
user_error("$class::\$many_many has a bad entry: "
|
||||
. var_export($k,true). " => " . var_export($v,true) . ". Each map key should be a"
|
||||
. " relationship name, and the map value should be the data class to join to.", E_USER_ERROR);
|
||||
}
|
||||
}
|
||||
$items = isset($items) ? array_merge($newItems, $items) : $newItems;
|
||||
|
||||
|
||||
$newItems = (array)Config::inst()->get($class, 'belongs_many_many', Config::UNINHERITED);
|
||||
// Validate the data
|
||||
foreach($newItems as $k => $v) {
|
||||
if(!is_string($k) || is_numeric($k) || !is_string($v)) {
|
||||
user_error("$class::\$belongs_many_many has a bad entry: "
|
||||
user_error("$class::\$belongs_many_many has a bad entry: "
|
||||
. var_export($k,true). " => " . var_export($v,true) . ". Each map key should be a"
|
||||
. " relationship name, and the map value should be the data class to join to.", E_USER_ERROR);
|
||||
}
|
||||
@ -1907,20 +1907,20 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$items = isset($items) ? array_merge($newItems, $items) : $newItems;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return isset($items) ? $items : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This returns an array (if it exists) describing the database extensions that are required, or false if none
|
||||
*
|
||||
*
|
||||
* This is experimental, and is currently only a Postgres-specific enhancement.
|
||||
*
|
||||
*
|
||||
* @return array or false
|
||||
*/
|
||||
public function database_extensions($class){
|
||||
$extensions = Config::inst()->get($class, 'database_extensions', Config::UNINHERITED);
|
||||
|
||||
|
||||
if($extensions)
|
||||
return $extensions;
|
||||
else
|
||||
@ -1935,12 +1935,12 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
*/
|
||||
public function getDefaultSearchContext() {
|
||||
return new SearchContext(
|
||||
$this->class,
|
||||
$this->scaffoldSearchFields(),
|
||||
$this->class,
|
||||
$this->scaffoldSearchFields(),
|
||||
$this->defaultSearchFilters()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine which properties on the DataObject are
|
||||
* searchable, and map them to their default {@link FormField}
|
||||
@ -1950,7 +1950,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* how generic or specific the field type is.
|
||||
*
|
||||
* Used by {@link SearchContext}.
|
||||
*
|
||||
*
|
||||
* @param array $_params
|
||||
* 'fieldClasses': Associative array of field names as keys and FormField classes as values
|
||||
* 'restrictFields': Numeric array of a field name whitelist
|
||||
@ -1967,7 +1967,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$fields = new FieldList();
|
||||
foreach($this->searchableFields() as $fieldName => $spec) {
|
||||
if($params['restrictFields'] && !in_array($fieldName, $params['restrictFields'])) continue;
|
||||
|
||||
|
||||
// If a custom fieldclass is provided as a string, use it
|
||||
if($params['fieldClasses'] && isset($params['fieldClasses'][$fieldName])) {
|
||||
$fieldClass = $params['fieldClasses'][$fieldName];
|
||||
@ -1978,17 +1978,17 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
if(is_string($spec['field'])) {
|
||||
$fieldClass = $spec['field'];
|
||||
$field = new $fieldClass($fieldName);
|
||||
|
||||
|
||||
// If it's a FormField object, then just use that object directly.
|
||||
} else if($spec['field'] instanceof FormField) {
|
||||
$field = $spec['field'];
|
||||
|
||||
|
||||
// Otherwise we have a bug
|
||||
} else {
|
||||
user_error("Bad value for searchable_fields, 'field' value: "
|
||||
. var_export($spec['field'], true), E_USER_WARNING);
|
||||
}
|
||||
|
||||
|
||||
// Otherwise, use the database field's scaffolder
|
||||
} else {
|
||||
$field = $this->relObject($fieldName)->scaffoldSearchField();
|
||||
@ -2010,7 +2010,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* Field labels/titles will be auto generated from {@link DataObject::fieldLabels()}.
|
||||
*
|
||||
* @uses FormScaffolder
|
||||
*
|
||||
*
|
||||
* @param array $_params Associative array passing through properties to {@link FormScaffolder}.
|
||||
* @return FieldList
|
||||
*/
|
||||
@ -2025,27 +2025,27 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
),
|
||||
(array)$_params
|
||||
);
|
||||
|
||||
|
||||
$fs = new FormScaffolder($this);
|
||||
$fs->tabbed = $params['tabbed'];
|
||||
$fs->includeRelations = $params['includeRelations'];
|
||||
$fs->restrictFields = $params['restrictFields'];
|
||||
$fs->fieldClasses = $params['fieldClasses'];
|
||||
$fs->ajaxSafe = $params['ajaxSafe'];
|
||||
|
||||
|
||||
return $fs->getFieldList();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Allows user code to hook into DataObject::getCMSFields prior to updateCMSFields
|
||||
* being called on extensions
|
||||
*
|
||||
*
|
||||
* @param callable $callback The callback to execute
|
||||
*/
|
||||
protected function beforeUpdateCMSFields($callback) {
|
||||
$this->beforeExtending('updateCMSFields', $callback);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Centerpiece of every data administration interface in Silverstripe,
|
||||
* which returns a {@link FieldList} suitable for a {@link Form} object.
|
||||
@ -2076,16 +2076,16 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
'tabbed' => true,
|
||||
'ajaxSafe' => true
|
||||
));
|
||||
|
||||
|
||||
$this->extend('updateCMSFields', $tabbedFields);
|
||||
|
||||
|
||||
return $tabbedFields;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* need to be overload by solid dataobject, so that the customised actions of that dataobject,
|
||||
* including that dataobject's extensions customised actions could be added to the EditForm.
|
||||
*
|
||||
*
|
||||
* @return an Empty FieldList(); need to be overload by solid subclass
|
||||
*/
|
||||
public function getCMSActions() {
|
||||
@ -2093,14 +2093,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$this->extend('updateCMSActions', $actions);
|
||||
return $actions;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Used for simple frontend forms without relation editing
|
||||
* or {@link TabSet} behaviour. Uses {@link scaffoldFormFields()}
|
||||
* by default. To customize, either overload this method in your
|
||||
* subclass, or extend it by {@link DataExtension->updateFrontEndFields()}.
|
||||
*
|
||||
*
|
||||
* @todo Decide on naming for "website|frontend|site|page" and stick with it in the API
|
||||
*
|
||||
* @param array $params See {@link scaffoldFormFields()}
|
||||
@ -2109,7 +2109,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
public function getFrontEndFields($params = null) {
|
||||
$untabbedFields = $this->scaffoldFormFields($params);
|
||||
$this->extend('updateFrontEndFields', $untabbedFields);
|
||||
|
||||
|
||||
return $untabbedFields;
|
||||
}
|
||||
|
||||
@ -2148,7 +2148,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
// or a valid record has been loaded from the database
|
||||
$value = (isset($this->record[$field])) ? $this->record[$field] : null;
|
||||
if($value || $this->exists()) $fieldObj->setValue($value, $this->record, false);
|
||||
|
||||
|
||||
$this->record[$field] = $fieldObj;
|
||||
|
||||
return $this->record[$field];
|
||||
@ -2178,7 +2178,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
|
||||
$dataQuery = new DataQuery($tableClass);
|
||||
|
||||
|
||||
// Reset query parameter context to that of this DataObject
|
||||
if($params = $this->getSourceQueryParams()) {
|
||||
foreach($params as $key => $value) $dataQuery->setQueryParam($key, $value);
|
||||
@ -2235,7 +2235,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
|
||||
/**
|
||||
* Return the fields that have changed.
|
||||
*
|
||||
*
|
||||
* The change level affects what the functions defines as "changed":
|
||||
* - Level 1 will return strict changes, even !== ones.
|
||||
* - Level 2 is more lenient, it will only return real data changes, for example a change from 0 to null
|
||||
@ -2254,14 +2254,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
*/
|
||||
public function getChangedFields($databaseFieldsOnly = false, $changeLevel = 1) {
|
||||
$changedFields = array();
|
||||
|
||||
|
||||
// Update the changed array with references to changed obj-fields
|
||||
foreach($this->record as $k => $v) {
|
||||
if(is_object($v) && method_exists($v, 'isChanged') && $v->isChanged()) {
|
||||
$this->changed[$k] = 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if($databaseFieldsOnly) {
|
||||
$databaseFields = $this->inheritedDatabaseFields();
|
||||
$databaseFields['ID'] = true;
|
||||
@ -2281,7 +2281,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if($fields) foreach($fields as $name => $level) {
|
||||
$changedFields[$name] = array(
|
||||
'before' => array_key_exists($name, $this->original) ? $this->original[$name] : null,
|
||||
@ -2292,11 +2292,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
|
||||
return $changedFields;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Uses {@link getChangedFields()} to determine if fields have been changed
|
||||
* since loading them from the database.
|
||||
*
|
||||
*
|
||||
* @param string $fieldName Name of the database field to check, will check for any if not given
|
||||
* @param int $changeLevel See {@link getChangedFields()}
|
||||
* @return boolean
|
||||
@ -2305,7 +2305,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$changed = $this->getChangedFields(false, $changeLevel);
|
||||
if(!isset($fieldName)) {
|
||||
return !empty($changed);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return array_key_exists($fieldName, $changed);
|
||||
}
|
||||
@ -2340,14 +2340,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
if(is_object($val) && $this->db($fieldName)) {
|
||||
user_error('DataObject::setField: passed an object that is not a DBField', E_USER_WARNING);
|
||||
}
|
||||
|
||||
|
||||
// if a field is not existing or has strictly changed
|
||||
if(!isset($this->record[$fieldName]) || $this->record[$fieldName] !== $val) {
|
||||
// TODO Add check for php-level defaults which are not set in the db
|
||||
// TODO Add check for hidden input-fields (readonly) which are not set in the db
|
||||
// At the very least, the type has changed
|
||||
$this->changed[$fieldName] = 1;
|
||||
|
||||
|
||||
if((!isset($this->record[$fieldName]) && $val) || (isset($this->record[$fieldName])
|
||||
&& $this->record[$fieldName] != $val)) {
|
||||
|
||||
@ -2393,8 +2393,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given field exists in a database column on any of
|
||||
* the objects tables and optionally look up a dynamic getter with
|
||||
* Returns true if the given field exists in a database column on any of
|
||||
* the objects tables and optionally look up a dynamic getter with
|
||||
* get<fieldName>().
|
||||
*
|
||||
* @param string $field Name of the field
|
||||
@ -2421,7 +2421,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
|
||||
return array_key_exists($field, $this->inheritedDatabaseFields());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the field type of the given field, if it belongs to this class, and not a parent.
|
||||
* Note that the field type will not include constructor arguments in round brackets, only the classname.
|
||||
@ -2437,17 +2437,18 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
if($field == "Created" && get_parent_class($this) == "DataObject") return "SS_Datetime";
|
||||
|
||||
// Add fields from Versioned extension
|
||||
if($field == 'Version' && $this->hasExtension('Versioned')) {
|
||||
if($field == 'Version' && $this->hasExtension('Versioned')) {
|
||||
return 'Int';
|
||||
}
|
||||
// get cached fieldmap
|
||||
$fieldMap = isset(DataObject::$cache_has_own_table_field[$this->class])
|
||||
? DataObject::$cache_has_own_table_field[$this->class] : null;
|
||||
|
||||
$lClass = strtolower($this->class);
|
||||
$fieldMap = isset(DataObject::$cache_has_own_table_field[$lClass])
|
||||
? DataObject::$cache_has_own_table_field[$lClass] : null;
|
||||
|
||||
// if no fieldmap is cached, get all fields
|
||||
if(!$fieldMap) {
|
||||
$fieldMap = Config::inst()->get($this->class, 'db', Config::UNINHERITED);
|
||||
|
||||
|
||||
// all $db fields on this specific class (no parents)
|
||||
foreach(self::composite_fields($this->class, false) as $fieldname => $fieldtype) {
|
||||
$combined_db = singleton($fieldtype)->compositeDatabaseFields();
|
||||
@ -2455,7 +2456,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$fieldMap[$fieldname.$name] = $type;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// all has_one relations on this specific class,
|
||||
// add foreign key
|
||||
$hasOne = Config::inst()->get($this->class, 'has_one', Config::UNINHERITED);
|
||||
@ -2464,7 +2465,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
|
||||
// set cached fieldmap
|
||||
DataObject::$cache_has_own_table_field[$this->class] = $fieldMap;
|
||||
DataObject::$cache_has_own_table_field[$lClass] = $fieldMap;
|
||||
}
|
||||
|
||||
// Remove string-based "constructor-arguments" from the DBField definition
|
||||
@ -2473,7 +2474,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
else return $fieldMap[$field]['type'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if given class has its own table. Uses the rules for whether the table should exist rather than
|
||||
* actually looking in the database.
|
||||
@ -2482,20 +2483,20 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_own_table($dataClass) {
|
||||
if(!is_subclass_of($dataClass,'DataObject')) return false;
|
||||
|
||||
if(!isset(DataObject::$cache_has_own_table[$dataClass])) {
|
||||
if(get_parent_class($dataClass) == 'DataObject') {
|
||||
DataObject::$cache_has_own_table[$dataClass] = true;
|
||||
if(!is_subclass_of($dataClass, 'DataObject')) return false;
|
||||
$lDataClass = strtolower($dataClass);
|
||||
|
||||
if(!isset(DataObject::$cache_has_own_table[$lDataClass])) {
|
||||
if(get_parent_class($dataClass) == 'DataObject' || Config::inst()->get($dataClass, 'db', Config::UNINHERITED)
|
||||
|| Config::inst()->get($dataClass, 'has_one', Config::UNINHERITED)) {
|
||||
DataObject::$cache_has_own_table[$lDataClass] = $dataClass;
|
||||
} else {
|
||||
DataObject::$cache_has_own_table[$dataClass]
|
||||
= Config::inst()->get($dataClass, 'db', Config::UNINHERITED)
|
||||
|| Config::inst()->get($dataClass, 'has_one', Config::UNINHERITED);
|
||||
DataObject::$cache_has_own_table[$lDataClass] = false;
|
||||
}
|
||||
}
|
||||
return DataObject::$cache_has_own_table[$dataClass];
|
||||
return (bool)DataObject::$cache_has_own_table[$lDataClass];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the member is allowed to do the given action.
|
||||
* See {@link extendedCan()} for a more versatile tri-state permission control.
|
||||
@ -2569,11 +2570,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
/**
|
||||
* Process tri-state responses from permission-alterting extensions. The extensions are
|
||||
* expected to return one of three values:
|
||||
*
|
||||
*
|
||||
* - false: Disallow this permission, regardless of what other extensions say
|
||||
* - true: Allow this permission, as long as no other extensions return false
|
||||
* - NULL: Don't affect the outcome
|
||||
*
|
||||
*
|
||||
* This method itself returns a tri-state value, and is designed to be used like this:
|
||||
*
|
||||
* <code>
|
||||
@ -2581,7 +2582,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* if($extended !== null) return $extended;
|
||||
* else return $normalValue;
|
||||
* </code>
|
||||
*
|
||||
*
|
||||
* @param String $methodName Method on the same object, e.g. {@link canEdit()}
|
||||
* @param Member|int $member
|
||||
* @return boolean|null
|
||||
@ -2592,12 +2593,12 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
// Remove NULLs
|
||||
$results = array_filter($results, function($v) {return !is_null($v);});
|
||||
// If there are any non-NULL responses, then return the lowest one of them.
|
||||
// If any explicitly deny the permission, then we don't get access
|
||||
// If any explicitly deny the permission, then we don't get access
|
||||
if($results) return min($results);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Member $member
|
||||
* @return boolean
|
||||
@ -2676,7 +2677,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
// If we have a CompositeDBField object in $this->record, then return that
|
||||
if(isset($this->record[$fieldName]) && is_object($this->record[$fieldName])) {
|
||||
return $this->record[$fieldName];
|
||||
|
||||
|
||||
// Special case for ID field
|
||||
} else if($fieldName == 'ID') {
|
||||
return new PrimaryKey($fieldName, $this);
|
||||
@ -2694,7 +2695,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$obj = Object::create_from_string($helper, $fieldName);
|
||||
$obj->setValue($this->$fieldName, $this->record, false);
|
||||
return $obj;
|
||||
|
||||
|
||||
// Special case for has_one relationships
|
||||
} else if(preg_match('/ID$/', $fieldName) && $this->has_one(substr($fieldName,0,-2))) {
|
||||
$val = $this->$fieldName;
|
||||
@ -2705,7 +2706,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
/**
|
||||
* Traverses to a DBField referenced by relationships between data objects.
|
||||
*
|
||||
* The path to the related field is specified with dot separated syntax
|
||||
* The path to the related field is specified with dot separated syntax
|
||||
* (eg: Parent.Child.Child.FieldName).
|
||||
*
|
||||
* @param string $fieldPath
|
||||
@ -2767,7 +2768,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$component = $component->relation($relation);
|
||||
} elseif($component instanceof DataObject
|
||||
&& ($dbObject = $component->dbObject($relation))
|
||||
) {
|
||||
) {
|
||||
// Select db object
|
||||
$component = $dbObject;
|
||||
} else {
|
||||
@ -2775,7 +2776,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Bail if the component is null
|
||||
if(!$component) {
|
||||
return null;
|
||||
@ -2789,7 +2790,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
/**
|
||||
* Temporary hack to return an association name, based on class, to get around the mangle
|
||||
* of having to deal with reverse lookup of relationships to determine autogenerated foreign keys.
|
||||
*
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public function getReverseAssociation($className) {
|
||||
@ -2805,7 +2806,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$has_one = array_flip($this->has_one());
|
||||
if (array_key_exists($className, $has_one)) return $has_one[$className];
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -2831,12 +2832,12 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
if($callerClass == 'DataObject') {
|
||||
throw new \InvalidArgumentException('Call <classname>::get() instead of DataObject::get()');
|
||||
}
|
||||
|
||||
|
||||
if($filter || $sort || $join || $limit || ($containerClass != 'DataList')) {
|
||||
throw new \InvalidArgumentException('If calling <classname>::get() then you shouldn\'t pass any other'
|
||||
. ' arguments');
|
||||
}
|
||||
|
||||
|
||||
$result = DataList::create(get_called_class());
|
||||
$result->setDataModel(DataModel::inst());
|
||||
return $result;
|
||||
@ -2847,7 +2848,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
'The $join argument has been removed. Use leftJoin($table, $joinClause) instead.'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
$result = DataList::create($callerClass)->where($filter)->sort($sort);
|
||||
|
||||
if($limit && strpos($limit, ',') !== false) {
|
||||
@ -2860,7 +2861,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$result->setDataModel(DataModel::inst());
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated 3.1 Use DataList::create and DataList to do your querying
|
||||
*/
|
||||
@ -2910,10 +2911,10 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$cacheKey .= '-' . implode("-", $extra);
|
||||
}
|
||||
$cacheKey = md5($cacheKey);
|
||||
|
||||
|
||||
// Flush destroyed items out of the cache
|
||||
if($cache && isset(DataObject::$_cache_get_one[$callerClass][$cacheKey])
|
||||
&& DataObject::$_cache_get_one[$callerClass][$cacheKey] instanceof DataObject
|
||||
if($cache && isset(DataObject::$_cache_get_one[$callerClass][$cacheKey])
|
||||
&& DataObject::$_cache_get_one[$callerClass][$cacheKey] instanceof DataObject
|
||||
&& DataObject::$_cache_get_one[$callerClass][$cacheKey]->destroyed) {
|
||||
|
||||
DataObject::$_cache_get_one[$callerClass][$cacheKey] = false;
|
||||
@ -2937,12 +2938,12 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* Also clears any cached aggregate data.
|
||||
*
|
||||
* @param boolean $persistent When true will also clear persistent data stored in the Cache system.
|
||||
* When false will just clear session-local cached data
|
||||
* When false will just clear session-local cached data
|
||||
* @return DataObject $this
|
||||
*/
|
||||
public function flushCache($persistent = true) {
|
||||
if($persistent) Aggregate::flushCache($this->class);
|
||||
|
||||
|
||||
if($this->class == 'DataObject') {
|
||||
DataObject::$_cache_get_one = array();
|
||||
return $this;
|
||||
@ -2952,9 +2953,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
foreach($classes as $class) {
|
||||
if(isset(DataObject::$_cache_get_one[$class])) unset(DataObject::$_cache_get_one[$class]);
|
||||
}
|
||||
|
||||
|
||||
$this->extend('flushCache');
|
||||
|
||||
|
||||
$this->components = array();
|
||||
return $this;
|
||||
}
|
||||
@ -2970,7 +2971,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
DataObject::$_cache_get_one = array();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reset all global caches associated with DataObject.
|
||||
*/
|
||||
@ -3020,7 +3021,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
|
||||
/**
|
||||
* @var Array Parameters used in the query that built this object.
|
||||
* This can be used by decorators (e.g. lazy loading) to
|
||||
* This can be used by decorators (e.g. lazy loading) to
|
||||
* run additional queries using the same context.
|
||||
*/
|
||||
protected $sourceQueryParams;
|
||||
@ -3093,7 +3094,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
|
||||
/**
|
||||
* Check the database schema and update it as necessary.
|
||||
*
|
||||
*
|
||||
* @uses DataExtension->augmentDatabase()
|
||||
*/
|
||||
public function requireTable() {
|
||||
@ -3129,7 +3130,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
"{$this->class}ID" => true,
|
||||
(($this->class == $childClass) ? "ChildID" : "{$childClass}ID") => true,
|
||||
);
|
||||
|
||||
|
||||
DB::requireTable("{$this->class}_$relationship", $manymanyFields, $manymanyIndexes, true, null,
|
||||
$extensions);
|
||||
}
|
||||
@ -3144,7 +3145,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* database is built, after the database tables have all been created. Overload
|
||||
* this to add default records when the database is built, but make sure you
|
||||
* call parent::requireDefaultRecords().
|
||||
*
|
||||
*
|
||||
* @uses DataExtension->requireDefaultRecords()
|
||||
*/
|
||||
public function requireDefaultRecords() {
|
||||
@ -3161,11 +3162,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
DB::alteration_message("Added default records to $className table","created");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Let any extentions make their own database default data
|
||||
$this->extend('requireDefaultRecords', $dummy);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns fields bu traversing the class heirachy in a bottom-up direction.
|
||||
*
|
||||
@ -3179,18 +3180,18 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
public function inheritedDatabaseFields() {
|
||||
$fields = array();
|
||||
$currentObj = $this->class;
|
||||
|
||||
|
||||
while($currentObj != 'DataObject') {
|
||||
$fields = array_merge($fields, self::custom_database_fields($currentObj));
|
||||
$currentObj = get_parent_class($currentObj);
|
||||
}
|
||||
|
||||
|
||||
return (array) $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default searchable fields for this object, as defined in the
|
||||
* $searchable_fields list. If searchable fields are not defined on the
|
||||
* Get the default searchable fields for this object, as defined in the
|
||||
* $searchable_fields list. If searchable fields are not defined on the
|
||||
* data object, uses a default selection of summary fields.
|
||||
*
|
||||
* @return array
|
||||
@ -3199,7 +3200,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
// can have mixed format, need to make consistent in most verbose form
|
||||
$fields = $this->stat('searchable_fields');
|
||||
$labels = $this->fieldLabels();
|
||||
|
||||
|
||||
// fallback to summary fields (unless empty array is explicitly specified)
|
||||
if( ! $fields && ! is_array($fields)) {
|
||||
$summaryFields = array_keys($this->summaryFields());
|
||||
@ -3223,7 +3224,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// we need to make sure the format is unified before
|
||||
// augmenting fields, so extensions can apply consistent checks
|
||||
// but also after augmenting fields, because the extension
|
||||
@ -3263,13 +3264,13 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
|
||||
$fields = $rewrite;
|
||||
|
||||
|
||||
// apply DataExtensions if present
|
||||
$this->extend('updateSearchableFields', $fields);
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get any user defined searchable fields labels that
|
||||
* exist. Allows overriding of default field names in the form
|
||||
@ -3282,23 +3283,23 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* would generally only be set in the case of more complex relationships
|
||||
* between data object being required in the search interface.
|
||||
*
|
||||
* Generates labels based on name of the field itself, if no static property
|
||||
* Generates labels based on name of the field itself, if no static property
|
||||
* {@link self::field_labels} exists.
|
||||
*
|
||||
* @uses $field_labels
|
||||
* @uses FormField::name_to_label()
|
||||
*
|
||||
* @param boolean $includerelations a boolean value to indicate if the labels returned include relation fields
|
||||
*
|
||||
*
|
||||
* @return array|string Array of all element labels if no argument given, otherwise the label of the field
|
||||
*/
|
||||
public function fieldLabels($includerelations = true) {
|
||||
$cacheKey = $this->class . '_' . $includerelations;
|
||||
|
||||
|
||||
if(!isset(self::$_cache_field_labels[$cacheKey])) {
|
||||
$customLabels = $this->stat('field_labels');
|
||||
$autoLabels = array();
|
||||
|
||||
|
||||
// get all translated static properties as defined in i18nCollectStatics()
|
||||
$ancestry = ClassInfo::ancestry($this->class);
|
||||
$ancestry = array_reverse($ancestry);
|
||||
@ -3321,20 +3322,20 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
|
||||
$labels = array_merge((array)$autoLabels, (array)$customLabels);
|
||||
$this->extend('updateFieldLabels', $labels);
|
||||
$this->extend('updateFieldLabels', $labels);
|
||||
self::$_cache_field_labels[$cacheKey] = $labels;
|
||||
}
|
||||
|
||||
|
||||
return self::$_cache_field_labels[$cacheKey];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a human-readable label for a single field,
|
||||
* see {@link fieldLabels()} for more details.
|
||||
*
|
||||
*
|
||||
* @uses fieldLabels()
|
||||
* @uses FormField::name_to_label()
|
||||
*
|
||||
*
|
||||
* @param string $name Name of the field
|
||||
* @return string Label of the field
|
||||
*/
|
||||
@ -3368,7 +3369,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
if ($this->hasField('FirstName')) $fields['FirstName'] = 'First Name';
|
||||
}
|
||||
$this->extend("updateSummaryFields", $fields);
|
||||
|
||||
|
||||
// Final fail-over, just list ID field
|
||||
if(!$fields) $fields['ID'] = 'ID';
|
||||
|
||||
@ -3400,7 +3401,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
|
||||
foreach($this->searchableFields() as $name => $spec) {
|
||||
$filterClass = $spec['filter'];
|
||||
|
||||
|
||||
if($spec['filter'] instanceof SearchFilter) {
|
||||
$filters[$name] = $spec['filter'];
|
||||
} else {
|
||||
@ -3427,8 +3428,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
/*
|
||||
* @ignore
|
||||
*/
|
||||
private static $subclass_access = true;
|
||||
|
||||
private static $subclass_access = true;
|
||||
|
||||
/**
|
||||
* Temporarily disable subclass access in data object qeur
|
||||
*/
|
||||
@ -3438,7 +3439,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
public static function enable_subclass_access() {
|
||||
self::$subclass_access = true;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------------------------//
|
||||
|
||||
/**
|
||||
@ -3462,13 +3463,13 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
"Created" => "SS_Datetime",
|
||||
"Title" => 'Text',
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* Specify custom options for a CREATE TABLE call.
|
||||
* Can be used to specify a custom storage engine for specific database table.
|
||||
* All options have to be keyed for a specific database implementation,
|
||||
* identified by their class name (extending from {@link SS_Database}).
|
||||
*
|
||||
*
|
||||
* <code>
|
||||
* array(
|
||||
* 'MySQLDatabase' => 'ENGINE=MyISAM'
|
||||
@ -3477,7 +3478,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
*
|
||||
* Caution: This API is experimental, and might not be
|
||||
* included in the next major release. Please use with care.
|
||||
*
|
||||
*
|
||||
* @var array
|
||||
* @config
|
||||
*/
|
||||
@ -3489,7 +3490,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* If a field is in this array, then create a database index
|
||||
* on that field. This is a map from fieldname to index type.
|
||||
* See {@link SS_Database->requireIndex()} and custom subclasses for details on the array notation.
|
||||
*
|
||||
*
|
||||
* @var array
|
||||
* @config
|
||||
*/
|
||||
@ -3499,11 +3500,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* Inserts standard column-values when a DataObject
|
||||
* is instanciated. Does not insert default records {@see $default_records}.
|
||||
* This is a map from fieldname to default value.
|
||||
*
|
||||
*
|
||||
* - If you would like to change a default value in a sub-class, just specify it.
|
||||
* - If you would like to disable the default value given by a parent class, set the default value to 0,'',
|
||||
* or false in your subclass. Setting it to null won't work.
|
||||
*
|
||||
*
|
||||
* @var array
|
||||
* @config
|
||||
*/
|
||||
@ -3536,7 +3537,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* @config
|
||||
*/
|
||||
private static $has_one = null;
|
||||
|
||||
|
||||
/**
|
||||
* A meta-relationship that allows you to define the reverse side of a {@link DataObject::$has_one}.
|
||||
*
|
||||
@ -3550,7 +3551,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* @config
|
||||
*/
|
||||
private static $belongs_to;
|
||||
|
||||
|
||||
/**
|
||||
* This defines a one-to-many relationship. It is a map of component name to the remote data class.
|
||||
*
|
||||
@ -3575,7 +3576,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
/**
|
||||
* Extra fields to include on the connecting many-many table.
|
||||
* This is a map from field name to field type.
|
||||
*
|
||||
*
|
||||
* Example code:
|
||||
* <code>
|
||||
* public static $many_many_extraFields = array(
|
||||
@ -3584,7 +3585,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* )
|
||||
* );
|
||||
* </code>
|
||||
*
|
||||
*
|
||||
* @var array
|
||||
* @config
|
||||
*/
|
||||
@ -3616,7 +3617,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* "Name" => "PartialMatchFilter"
|
||||
* );
|
||||
* </code>
|
||||
*
|
||||
*
|
||||
* Overriding the default form fields, with a custom defined field.
|
||||
* The 'filter' parameter will be generated from {@link DBField::$default_search_filter_class}.
|
||||
* The 'title' parameter will be generated from {@link DataObject->fieldLabels()}.
|
||||
@ -3632,7 +3633,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* <code>
|
||||
* static $searchable_fields = array(
|
||||
* "Organisation.ZipCode" => array(
|
||||
* "field" => "TextField",
|
||||
* "field" => "TextField",
|
||||
* "filter" => "PartialMatchFilter",
|
||||
* "title" => 'Organisation ZIP'
|
||||
* )
|
||||
@ -3655,44 +3656,44 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* @config
|
||||
*/
|
||||
private static $summary_fields = null;
|
||||
|
||||
|
||||
/**
|
||||
* Provides a list of allowed methods that can be called via RESTful api.
|
||||
*/
|
||||
public static $allowed_actions = null;
|
||||
|
||||
|
||||
/**
|
||||
* Collect all static properties on the object
|
||||
* which contain natural language, and need to be translated.
|
||||
* The full entity name is composed from the class name and a custom identifier.
|
||||
*
|
||||
*
|
||||
* @return array A numerical array which contains one or more entities in array-form.
|
||||
* Each numeric entity array contains the "arguments" for a _t() call as array values:
|
||||
* $entity, $string, $priority, $context.
|
||||
*/
|
||||
public function provideI18nEntities() {
|
||||
$entities = array();
|
||||
|
||||
|
||||
$entities["{$this->class}.SINGULARNAME"] = array(
|
||||
$this->singular_name(),
|
||||
|
||||
|
||||
'Singular name of the object, used in dropdowns and to generally identify a single object in the interface'
|
||||
);
|
||||
|
||||
$entities["{$this->class}.PLURALNAME"] = array(
|
||||
$this->plural_name(),
|
||||
|
||||
|
||||
'Pural name of the object, used in dropdowns and to generally identify a collection of this object in the'
|
||||
. ' interface'
|
||||
);
|
||||
|
||||
|
||||
return $entities;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the given method/parameter has a value
|
||||
* (Uses the DBField::hasValue if the parameter is a database field)
|
||||
*
|
||||
*
|
||||
* @param string $field The field name
|
||||
* @param array $arguments
|
||||
* @param bool $cache
|
||||
|
@ -12,34 +12,34 @@
|
||||
* @package framework
|
||||
*/
|
||||
class DataQuery {
|
||||
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $dataClass;
|
||||
|
||||
|
||||
/**
|
||||
* @var SQLQuery
|
||||
*/
|
||||
protected $query;
|
||||
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $collidingFields = array();
|
||||
|
||||
private $queriedColumns = null;
|
||||
|
||||
|
||||
/**
|
||||
* @var Boolean
|
||||
*/
|
||||
private $queryFinalised = false;
|
||||
|
||||
|
||||
// TODO: replace subclass_access with this
|
||||
protected $querySubclasses = true;
|
||||
// TODO: replace restrictclasses with this
|
||||
protected $filterByClassName = true;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new DataQuery.
|
||||
*
|
||||
@ -49,14 +49,14 @@ class DataQuery {
|
||||
$this->dataClass = $dataClass;
|
||||
$this->initialiseQuery();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clone this object
|
||||
*/
|
||||
public function __clone() {
|
||||
$this->query = clone $this->query;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the {@link DataObject} class that is being queried.
|
||||
*/
|
||||
@ -71,8 +71,8 @@ class DataQuery {
|
||||
public function query() {
|
||||
return $this->getFinalisedQuery();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Remove a filter from the query
|
||||
*/
|
||||
@ -96,7 +96,7 @@ class DataQuery {
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set up the simplest initial query
|
||||
*/
|
||||
@ -104,7 +104,7 @@ class DataQuery {
|
||||
// Get the tables to join to.
|
||||
// Don't get any subclass tables - let lazy loading do that.
|
||||
$tableClasses = ClassInfo::ancestry($this->dataClass, true);
|
||||
|
||||
|
||||
// Error checking
|
||||
if(!$tableClasses) {
|
||||
if(!SS_ClassLoader::instance()->hasManifest()) {
|
||||
@ -122,7 +122,7 @@ class DataQuery {
|
||||
// Build our intial query
|
||||
$this->query = new SQLQuery(array());
|
||||
$this->query->setDistinct(true);
|
||||
|
||||
|
||||
if($sort = singleton($this->dataClass)->stat('default_sort')) {
|
||||
$this->sort($sort);
|
||||
}
|
||||
@ -169,7 +169,7 @@ class DataQuery {
|
||||
$tableClasses = $ancestorTables;
|
||||
}
|
||||
|
||||
$tableNames = array_keys($tableClasses);
|
||||
$tableNames = array_values($tableClasses);
|
||||
$baseClass = $tableNames[0];
|
||||
|
||||
// Iterate over the tables and check what we need to select from them. If any selects are made (or the table is
|
||||
@ -183,10 +183,10 @@ class DataQuery {
|
||||
$tableFields = DataObject::database_fields($tableClass);
|
||||
$selectColumns = array_intersect($queriedColumns, array_keys($tableFields));
|
||||
}
|
||||
|
||||
|
||||
// If this is a subclass without any explicitly requested columns, omit this from the query
|
||||
if(!in_array($tableClass, $ancestorTables) && empty($selectColumns)) continue;
|
||||
|
||||
|
||||
// Select necessary columns (unless an explicitly empty array)
|
||||
if($selectColumns !== array()) {
|
||||
$this->selectColumnsFromTable($query, $tableClass, $selectColumns);
|
||||
@ -197,7 +197,7 @@ class DataQuery {
|
||||
$query->addLeftJoin($tableClass, "\"$tableClass\".\"ID\" = \"$baseClass\".\"ID\"", $tableClass, 10);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Resolve colliding fields
|
||||
if($this->collidingFields) {
|
||||
foreach($this->collidingFields as $k => $collisions) {
|
||||
@ -246,7 +246,7 @@ class DataQuery {
|
||||
|
||||
/**
|
||||
* Ensure that if a query has an order by clause, those columns are present in the select.
|
||||
*
|
||||
*
|
||||
* @param SQLQuery $query
|
||||
* @return null
|
||||
*/
|
||||
@ -259,7 +259,7 @@ class DataQuery {
|
||||
$i = 0;
|
||||
foreach($orderby as $k => $dir) {
|
||||
$newOrderby[$k] = $dir;
|
||||
|
||||
|
||||
// don't touch functions in the ORDER BY or public function calls
|
||||
// selected as fields
|
||||
if(strpos($k, '(') !== false) continue;
|
||||
@ -275,26 +275,26 @@ class DataQuery {
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if(count($parts) == 1) {
|
||||
$databaseFields = DataObject::database_fields($baseClass);
|
||||
|
||||
// database_fields() doesn't return ID, so we need to
|
||||
|
||||
// database_fields() doesn't return ID, so we need to
|
||||
// manually add it here
|
||||
$databaseFields['ID'] = true;
|
||||
|
||||
|
||||
if(isset($databaseFields[$parts[0]])) {
|
||||
$qualCol = "\"$baseClass\".\"{$parts[0]}\"";
|
||||
} else {
|
||||
$qualCol = "\"$parts[0]\"";
|
||||
}
|
||||
|
||||
|
||||
// remove original sort
|
||||
unset($newOrderby[$k]);
|
||||
|
||||
// add new columns sort
|
||||
$newOrderby[$qualCol] = $dir;
|
||||
|
||||
|
||||
// To-do: Remove this if block once SQLQuery::$select has been refactored to store getSelect()
|
||||
// format internally; then this check can be part of selectField()
|
||||
$selects = $query->getSelect();
|
||||
@ -306,7 +306,7 @@ class DataQuery {
|
||||
|
||||
if(!in_array($qualCol, $query->getSelect())) {
|
||||
unset($newOrderby[$k]);
|
||||
|
||||
|
||||
$newOrderby["\"_SortColumn$i\""] = $dir;
|
||||
$query->selectField($qualCol, "_SortColumn$i");
|
||||
|
||||
@ -345,7 +345,7 @@ class DataQuery {
|
||||
|
||||
/**
|
||||
* Return the maximum value of the given field in this DataList
|
||||
*
|
||||
*
|
||||
* @param String $field Unquoted database column name (will be escaped automatically)
|
||||
*/
|
||||
public function max($field) {
|
||||
@ -354,16 +354,16 @@ class DataQuery {
|
||||
|
||||
/**
|
||||
* Return the minimum value of the given field in this DataList
|
||||
*
|
||||
*
|
||||
* @param String $field Unquoted database column name (will be escaped automatically)
|
||||
*/
|
||||
public function min($field) {
|
||||
return $this->aggregate(sprintf('MIN("%s")', Convert::raw2sql($field)));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the average value of the given field in this DataList
|
||||
*
|
||||
*
|
||||
* @param String $field Unquoted database column name (will be escaped automatically)
|
||||
*/
|
||||
public function avg($field) {
|
||||
@ -372,13 +372,13 @@ class DataQuery {
|
||||
|
||||
/**
|
||||
* Return the sum of the values of the given field in this DataList
|
||||
*
|
||||
*
|
||||
* @param String $field Unquoted database column name (will be escaped automatically)
|
||||
*/
|
||||
public function sum($field) {
|
||||
return $this->aggregate(sprintf('SUM("%s")', Convert::raw2sql($field)));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Runs a raw aggregate expression. Please handle escaping yourself
|
||||
*/
|
||||
@ -415,7 +415,7 @@ class DataQuery {
|
||||
if($expressionForField = $query->expressionForField($k)) {
|
||||
if(!isset($this->collidingFields[$k])) $this->collidingFields[$k] = array($expressionForField);
|
||||
$this->collidingFields[$k][] = "\"$tableClass\".\"$k\"";
|
||||
|
||||
|
||||
} else {
|
||||
$query->selectField("\"$tableClass\".\"$k\"", $k);
|
||||
}
|
||||
@ -428,20 +428,20 @@ class DataQuery {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Append a GROUP BY clause to this query.
|
||||
*
|
||||
*
|
||||
* @param String $groupby Escaped SQL statement
|
||||
*/
|
||||
public function groupby($groupby) {
|
||||
$this->query->addGroupBy($groupby);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Append a HAVING clause to this query.
|
||||
*
|
||||
*
|
||||
* @param String $having Escaped SQL statement
|
||||
*/
|
||||
public function having($having) {
|
||||
@ -494,7 +494,7 @@ class DataQuery {
|
||||
|
||||
/**
|
||||
* Append a WHERE with OR.
|
||||
*
|
||||
*
|
||||
* @example $dataQuery->whereAny(array("\"Monkey\" = 'Chimp'", "\"Color\" = 'Brown'"));
|
||||
* @see where()
|
||||
*
|
||||
@ -507,7 +507,7 @@ class DataQuery {
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the ORDER BY clause of this query
|
||||
*
|
||||
@ -524,10 +524,10 @@ class DataQuery {
|
||||
} else {
|
||||
$this->query->addOrderBy($sort, $direction);
|
||||
}
|
||||
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reverse order by clause
|
||||
*
|
||||
@ -537,10 +537,10 @@ class DataQuery {
|
||||
$this->query->reverseOrderBy();
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the limit of this query.
|
||||
*
|
||||
*
|
||||
* @param int $limit
|
||||
* @param int $offset
|
||||
*/
|
||||
@ -562,7 +562,7 @@ class DataQuery {
|
||||
|
||||
/**
|
||||
* Add an INNER JOIN clause to this query.
|
||||
*
|
||||
*
|
||||
* @param String $table The unquoted table name to join to.
|
||||
* @param String $onClause The filter for the join (escaped SQL statement)
|
||||
* @param String $alias An optional alias name (unquoted)
|
||||
@ -576,7 +576,7 @@ class DataQuery {
|
||||
|
||||
/**
|
||||
* Add a LEFT JOIN clause to this query.
|
||||
*
|
||||
*
|
||||
* @param String $table The unquoted table to join to.
|
||||
* @param String $onClause The filter for the join (escaped SQL statement).
|
||||
* @param String $alias An optional alias name (unquoted)
|
||||
@ -592,18 +592,18 @@ class DataQuery {
|
||||
* Traverse the relationship fields, and add the table
|
||||
* mappings to the query object state. This has to be called
|
||||
* in any overloaded {@link SearchFilter->apply()} methods manually.
|
||||
*
|
||||
*
|
||||
* @param String|array $relation The array/dot-syntax relation to follow
|
||||
* @return The model class of the related item
|
||||
*/
|
||||
public function applyRelation($relation) {
|
||||
// NO-OP
|
||||
if(!$relation) return $this->dataClass;
|
||||
|
||||
|
||||
if(is_string($relation)) $relation = explode(".", $relation);
|
||||
|
||||
$modelClass = $this->dataClass;
|
||||
|
||||
|
||||
foreach($relation as $rel) {
|
||||
$model = singleton($modelClass);
|
||||
if ($component = $model->has_one($rel)) {
|
||||
@ -612,7 +612,7 @@ class DataQuery {
|
||||
$realModelClass = ClassInfo::table_for_object_field($modelClass, "{$foreignKey}ID");
|
||||
$this->query->addLeftJoin($component,
|
||||
"\"$component\".\"ID\" = \"{$realModelClass}\".\"{$foreignKey}ID\"");
|
||||
|
||||
|
||||
/**
|
||||
* add join clause to the component's ancestry classes so that the search filter could search on
|
||||
* its ancestor fields.
|
||||
@ -667,15 +667,15 @@ class DataQuery {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $modelClass;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes the result of query from this query.
|
||||
*
|
||||
*
|
||||
* @param DataQuery $subtractQuery
|
||||
* @param string $field
|
||||
* @param string $field
|
||||
*/
|
||||
public function subtract(DataQuery $subtractQuery, $field='ID') {
|
||||
$fieldExpression = $subtractQuery->expressionForField($field);
|
||||
@ -690,15 +690,15 @@ class DataQuery {
|
||||
|
||||
/**
|
||||
* Select the given fields from the given table.
|
||||
*
|
||||
*
|
||||
* @param String $table Unquoted table name (will be escaped automatically)
|
||||
* @param Array $fields Database column names (will be escaped automatically)
|
||||
*/
|
||||
public function selectFromTable($table, $fields) {
|
||||
$table = Convert::raw2sql($table);
|
||||
$fieldExpressions = array_map(create_function('$item',
|
||||
$fieldExpressions = array_map(create_function('$item',
|
||||
"return '\"$table\".\"' . Convert::raw2sql(\$item) . '\"';"), $fields);
|
||||
|
||||
|
||||
$this->query->setSelect($fieldExpressions);
|
||||
|
||||
return $this;
|
||||
@ -706,7 +706,7 @@ class DataQuery {
|
||||
|
||||
/**
|
||||
* Query the given field column from the database and return as an array.
|
||||
*
|
||||
*
|
||||
* @param string $field See {@link expressionForField()}.
|
||||
* @return array List of column values for the specified column
|
||||
*/
|
||||
@ -720,31 +720,31 @@ class DataQuery {
|
||||
|
||||
return $query->execute()->column($field);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param String $field Select statement identifier, either the unquoted column name,
|
||||
* the full composite SQL statement, or the alias set through {@link SQLQuery->selectField()}.
|
||||
* @return String The expression used to query this field via this DataQuery
|
||||
*/
|
||||
protected function expressionForField($field) {
|
||||
|
||||
|
||||
// Prepare query object for selecting this field
|
||||
$query = $this->getFinalisedQuery(array($field));
|
||||
|
||||
|
||||
// Allow query to define the expression for this field
|
||||
$expression = $query->expressionForField($field);
|
||||
if(!empty($expression)) return $expression;
|
||||
|
||||
|
||||
// Special case for ID, if not provided
|
||||
if($field === 'ID') {
|
||||
$baseClass = ClassInfo::baseDataClass($this->dataClass);
|
||||
return "\"$baseClass\".\"ID\"";
|
||||
return "\"$baseClass\".\"ID\"";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the given field expressions.
|
||||
*
|
||||
*
|
||||
* @param $fieldExpression String The field to select (escaped SQL statement)
|
||||
* @param $alias String The alias of that field (escaped SQL statement)
|
||||
*/
|
||||
@ -759,7 +759,7 @@ class DataQuery {
|
||||
* @todo This will probably be made obsolete if we have subclasses of DataList and/or DataQuery.
|
||||
*/
|
||||
private $queryParams;
|
||||
|
||||
|
||||
/**
|
||||
* Set an arbitrary query parameter, that can be used by decorators to add additional meta-data to the query.
|
||||
* It's expected that the $key will be namespaced, e.g, 'Versioned.stage' instead of just 'stage'.
|
||||
@ -767,7 +767,7 @@ class DataQuery {
|
||||
public function setQueryParam($key, $value) {
|
||||
$this->queryParams[$key] = $value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set an arbitrary query parameter, that can be used by decorators to add additional meta-data to the query.
|
||||
*/
|
||||
@ -788,7 +788,7 @@ class DataQuery {
|
||||
/**
|
||||
* Represents a subgroup inside a WHERE clause in a {@link DataQuery}
|
||||
*
|
||||
* Stores the clauses for the subgroup inside a specific {@link SQLQuery}
|
||||
* Stores the clauses for the subgroup inside a specific {@link SQLQuery}
|
||||
* object.
|
||||
*
|
||||
* All non-where methods call their DataQuery versions, which uses the base
|
||||
@ -833,7 +833,7 @@ class DataQuery_SubGroup extends DataQuery {
|
||||
|
||||
/**
|
||||
* Set a WHERE with OR.
|
||||
*
|
||||
*
|
||||
* @example $dataQuery->whereAny(array("\"Monkey\" = 'Chimp'", "\"Color\" = 'Brown'"));
|
||||
* @see where()
|
||||
*
|
||||
@ -855,10 +855,10 @@ class DataQuery_SubGroup extends DataQuery {
|
||||
}
|
||||
|
||||
$sql = DB::getConn()->sqlWhereToString(
|
||||
$this->whereQuery->getWhere(),
|
||||
$this->whereQuery->getWhere(),
|
||||
$this->whereQuery->getConnective()
|
||||
);
|
||||
|
||||
|
||||
$sql = preg_replace('[^\s*WHERE\s*]', '', $sql);
|
||||
|
||||
return $sql;
|
||||
|
@ -6,10 +6,18 @@
|
||||
*/
|
||||
class ClassInfoTest extends SapphireTest {
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
ClassInfo::reset_db_cache();
|
||||
}
|
||||
|
||||
public function testExists() {
|
||||
$this->assertTrue(ClassInfo::exists('Object'));
|
||||
$this->assertTrue(ClassInfo::exists('object'));
|
||||
$this->assertTrue(ClassInfo::exists('ClassInfoTest'));
|
||||
$this->assertTrue(ClassInfo::exists('CLASSINFOTEST'));
|
||||
$this->assertTrue(ClassInfo::exists('stdClass'));
|
||||
$this->assertTrue(ClassInfo::exists('stdCLASS'));
|
||||
}
|
||||
|
||||
public function testSubclassesFor() {
|
||||
@ -22,23 +30,33 @@ class ClassInfoTest extends SapphireTest {
|
||||
),
|
||||
'ClassInfo::subclassesFor() returns only direct subclasses and doesnt include base class'
|
||||
);
|
||||
ClassInfo::reset_db_cache();
|
||||
$this->assertEquals(
|
||||
ClassInfo::subclassesFor('classinfotest_baseclass'),
|
||||
array(
|
||||
'ClassInfoTest_BaseClass' => 'ClassInfoTest_BaseClass',
|
||||
'ClassInfoTest_ChildClass' => 'ClassInfoTest_ChildClass',
|
||||
'ClassInfoTest_GrandChildClass' => 'ClassInfoTest_GrandChildClass'
|
||||
),
|
||||
'ClassInfo::subclassesFor() is acting in a case sensitive way when it should not'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function testClassesForFolder() {
|
||||
//$baseFolder = Director::baseFolder() . '/' . FRAMEWORK_DIR . '/tests/_ClassInfoTest';
|
||||
//$manifestInfo = ManifestBuilder::get_manifest_info($baseFolder);
|
||||
|
||||
|
||||
$classes = ClassInfo::classes_for_folder(FRAMEWORK_DIR . '/tests');
|
||||
$this->assertContains(
|
||||
'classinfotest',
|
||||
$classes,
|
||||
'ClassInfo::classes_for_folder() returns classes matching the filename'
|
||||
);
|
||||
// $this->assertContains(
|
||||
// 'ClassInfoTest_BaseClass',
|
||||
// $classes,
|
||||
// 'ClassInfo::classes_for_folder() returns additional classes not matching the filename'
|
||||
// );
|
||||
$this->assertContains(
|
||||
'classinfotest_baseclass',
|
||||
$classes,
|
||||
'ClassInfo::classes_for_folder() returns additional classes not matching the filename'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -46,8 +64,11 @@ class ClassInfoTest extends SapphireTest {
|
||||
*/
|
||||
public function testBaseDataClass() {
|
||||
$this->assertEquals('ClassInfoTest_BaseClass', ClassInfo::baseDataClass('ClassInfoTest_BaseClass'));
|
||||
$this->assertEquals('ClassInfoTest_BaseClass', ClassInfo::baseDataClass('classinfotest_baseclass'));
|
||||
$this->assertEquals('ClassInfoTest_BaseClass', ClassInfo::baseDataClass('ClassInfoTest_ChildClass'));
|
||||
$this->assertEquals('ClassInfoTest_BaseClass', ClassInfo::baseDataClass('CLASSINFOTEST_CHILDCLASS'));
|
||||
$this->assertEquals('ClassInfoTest_BaseClass', ClassInfo::baseDataClass('ClassInfoTest_GrandChildClass'));
|
||||
$this->assertEquals('ClassInfoTest_BaseClass', ClassInfo::baseDataClass('ClassInfoTest_GRANDChildClass'));
|
||||
|
||||
$this->setExpectedException('InvalidArgumentException');
|
||||
ClassInfo::baseDataClass('DataObject');
|
||||
@ -67,6 +88,13 @@ class ClassInfoTest extends SapphireTest {
|
||||
));
|
||||
$this->assertEquals($expect, $ancestry);
|
||||
|
||||
ClassInfo::reset_db_cache();
|
||||
$this->assertEquals($expect, ClassInfo::ancestry('classINFOTest_Childclass'));
|
||||
|
||||
ClassInfo::reset_db_cache();
|
||||
$this->assertEquals($expect, ClassInfo::ancestry('classINFOTest_Childclass'));
|
||||
|
||||
ClassInfo::reset_db_cache();
|
||||
$ancestry = ClassInfo::ancestry('ClassInfoTest_ChildClass', true);
|
||||
$this->assertEquals(array('ClassInfoTest_BaseClass' => 'ClassInfoTest_BaseClass'), $ancestry,
|
||||
'$tablesOnly option excludes memory-only inheritance classes'
|
||||
@ -89,16 +117,22 @@ class ClassInfoTest extends SapphireTest {
|
||||
'ClassInfoTest_HasFields',
|
||||
);
|
||||
|
||||
|
||||
ClassInfo::reset_db_cache();
|
||||
$this->assertEquals($expect, ClassInfo::dataClassesFor($classes[0]));
|
||||
ClassInfo::reset_db_cache();
|
||||
$this->assertEquals($expect, ClassInfo::dataClassesFor(strtoupper($classes[0])));
|
||||
ClassInfo::reset_db_cache();
|
||||
$this->assertEquals($expect, ClassInfo::dataClassesFor($classes[1]));
|
||||
|
||||
|
||||
$expect = array(
|
||||
'ClassInfoTest_BaseDataClass' => 'ClassInfoTest_BaseDataClass',
|
||||
'ClassInfoTest_HasFields' => 'ClassInfoTest_HasFields',
|
||||
);
|
||||
|
||||
ClassInfo::reset_db_cache();
|
||||
$this->assertEquals($expect, ClassInfo::dataClassesFor($classes[2]));
|
||||
ClassInfo::reset_db_cache();
|
||||
$this->assertEquals($expect, ClassInfo::dataClassesFor(strtolower($classes[2])));
|
||||
}
|
||||
|
||||
public function testTableForObjectField() {
|
||||
@ -106,19 +140,27 @@ class ClassInfoTest extends SapphireTest {
|
||||
ClassInfo::table_for_object_field('ClassInfoTest_WithRelation', 'RelationID')
|
||||
);
|
||||
|
||||
$this->assertEquals('ClassInfoTest_BaseDataClass',
|
||||
$this->assertEquals('ClassInfoTest_WithRelation',
|
||||
ClassInfo::table_for_object_field('ClassInfoTest_withrelation', 'RelationID')
|
||||
);
|
||||
|
||||
$this->assertEquals('ClassInfoTest_BaseDataClass',
|
||||
ClassInfo::table_for_object_field('ClassInfoTest_BaseDataClass', 'Title')
|
||||
);
|
||||
|
||||
$this->assertEquals('ClassInfoTest_BaseDataClass',
|
||||
$this->assertEquals('ClassInfoTest_BaseDataClass',
|
||||
ClassInfo::table_for_object_field('ClassInfoTest_HasFields', 'Title')
|
||||
);
|
||||
|
||||
$this->assertEquals('ClassInfoTest_BaseDataClass',
|
||||
$this->assertEquals('ClassInfoTest_BaseDataClass',
|
||||
ClassInfo::table_for_object_field('ClassInfoTest_NoFields', 'Title')
|
||||
);
|
||||
|
||||
$this->assertEquals('ClassInfoTest_HasFields',
|
||||
$this->assertEquals('ClassInfoTest_BaseDataClass',
|
||||
ClassInfo::table_for_object_field('classinfotest_nofields', 'Title')
|
||||
);
|
||||
|
||||
$this->assertEquals('ClassInfoTest_HasFields',
|
||||
ClassInfo::table_for_object_field('ClassInfoTest_HasFields', 'Description')
|
||||
);
|
||||
|
||||
@ -147,7 +189,7 @@ class ClassInfoTest extends SapphireTest {
|
||||
*/
|
||||
|
||||
class ClassInfoTest_BaseClass extends DataObject {
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -156,7 +198,7 @@ class ClassInfoTest_BaseClass extends DataObject {
|
||||
*/
|
||||
|
||||
class ClassInfoTest_ChildClass extends ClassInfoTest_BaseClass {
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -165,7 +207,7 @@ class ClassInfoTest_ChildClass extends ClassInfoTest_BaseClass {
|
||||
*/
|
||||
|
||||
class ClassInfoTest_GrandChildClass extends ClassInfoTest_ChildClass {
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -5,7 +5,7 @@
|
||||
* @subpackage tests
|
||||
*/
|
||||
class DataListTest extends SapphireTest {
|
||||
|
||||
|
||||
// Borrow the model from DataObjectTest
|
||||
protected static $fixture_file = 'DataObjectTest.yml';
|
||||
|
||||
@ -69,32 +69,32 @@ class DataListTest extends SapphireTest {
|
||||
$newList = $fullList->subtract($subtractList);
|
||||
$this->assertEquals(2, $newList->Count(), 'List should only contain two objects after subtraction');
|
||||
}
|
||||
|
||||
|
||||
public function testSubtractBadDataclassThrowsException(){
|
||||
$this->setExpectedException('InvalidArgumentException');
|
||||
$teamsComments = DataObjectTest_TeamComment::get();
|
||||
$teams = DataObjectTest_Team::get();
|
||||
$teamsComments->subtract($teams);
|
||||
}
|
||||
|
||||
|
||||
public function testListCreationSortAndLimit() {
|
||||
// By default, a DataList will contain all items of that class
|
||||
$list = DataObjectTest_TeamComment::get()->sort('ID');
|
||||
|
||||
|
||||
// We can iterate on the DataList
|
||||
$names = array();
|
||||
foreach($list as $item) {
|
||||
$names[] = $item->Name;
|
||||
}
|
||||
$this->assertEquals(array('Joe', 'Bob', 'Phil'), $names);
|
||||
|
||||
|
||||
// If we don't want to iterate, we can extract a single column from the list with column()
|
||||
$this->assertEquals(array('Joe', 'Bob', 'Phil'), $list->column('Name'));
|
||||
|
||||
|
||||
// We can sort a list
|
||||
$list = $list->sort('Name');
|
||||
$this->assertEquals(array('Bob', 'Joe', 'Phil'), $list->column('Name'));
|
||||
|
||||
|
||||
// We can also restrict the output to a range
|
||||
$this->assertEquals(array('Joe', 'Phil'), $list->limit(2, 1)->column('Name'));
|
||||
}
|
||||
@ -137,12 +137,17 @@ class DataListTest extends SapphireTest {
|
||||
$list = DataObjectTest_TeamComment::get();
|
||||
$this->assertEquals('DataObjectTest_TeamComment',$list->dataClass());
|
||||
}
|
||||
|
||||
|
||||
public function testDataClassCaseInsensitive() {
|
||||
$list = DataList::create('dataobjecttest_teamcomment');
|
||||
$this->assertTrue($list->exists());
|
||||
}
|
||||
|
||||
public function testClone() {
|
||||
$list = DataObjectTest_TeamComment::get();
|
||||
$this->assertEquals($list, clone($list));
|
||||
}
|
||||
|
||||
|
||||
public function testSql() {
|
||||
$db = DB::getConn();
|
||||
$list = DataObjectTest_TeamComment::get();
|
||||
@ -154,7 +159,7 @@ class DataListTest extends SapphireTest {
|
||||
. ' END AS "RecordClassName" FROM "DataObjectTest_TeamComment"';
|
||||
$this->assertEquals($expected, $list->sql());
|
||||
}
|
||||
|
||||
|
||||
public function testInnerJoin() {
|
||||
$db = DB::getConn();
|
||||
|
||||
@ -175,7 +180,7 @@ class DataListTest extends SapphireTest {
|
||||
|
||||
$this->assertEquals($expected, $list->sql());
|
||||
}
|
||||
|
||||
|
||||
public function testLeftJoin() {
|
||||
$db = DB::getConn();
|
||||
|
||||
@ -199,7 +204,7 @@ class DataListTest extends SapphireTest {
|
||||
// Test with namespaces (with non-sensical join, but good enough for testing)
|
||||
$list = DataObjectTest_TeamComment::get();
|
||||
$list = $list->leftJoin(
|
||||
'DataObjectTest\NamespacedClass',
|
||||
'DataObjectTest\NamespacedClass',
|
||||
'"DataObjectTest\NamespacedClass"."ID" = "DataObjectTest_TeamComment"."ID"'
|
||||
);
|
||||
|
||||
@ -209,9 +214,9 @@ class DataListTest extends SapphireTest {
|
||||
. '"DataObjectTest_TeamComment"."Name", '
|
||||
. '"DataObjectTest_TeamComment"."Comment", '
|
||||
. '"DataObjectTest_TeamComment"."TeamID", '
|
||||
. '"DataObjectTest_TeamComment"."ID", '
|
||||
. '"DataObjectTest_TeamComment"."ID", '
|
||||
. 'CASE WHEN "DataObjectTest_TeamComment"."ClassName" IS NOT NULL '
|
||||
. 'THEN "DataObjectTest_TeamComment"."ClassName" '
|
||||
. 'THEN "DataObjectTest_TeamComment"."ClassName" '
|
||||
. 'ELSE ' . $db->prepStringForDB('DataObjectTest_TeamComment') . ' END AS "RecordClassName" '
|
||||
. 'FROM "DataObjectTest_TeamComment" '
|
||||
. 'LEFT JOIN "DataObjectTest\NamespacedClass" ON '
|
||||
@ -219,7 +224,7 @@ class DataListTest extends SapphireTest {
|
||||
$this->assertEquals($expected, $list->sql(), 'Retains backslashes in namespaced classes');
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function testToNestedArray() {
|
||||
$list = DataObjectTest_TeamComment::get()->sort('ID');
|
||||
$nestedArray = $list->toNestedArray();
|
||||
@ -251,7 +256,7 @@ class DataListTest extends SapphireTest {
|
||||
$this->assertEquals($expected[1]['Comment'], $nestedArray[1]['Comment']);
|
||||
$this->assertEquals($expected[2]['TeamID'], $nestedArray[2]['TeamID']);
|
||||
}
|
||||
|
||||
|
||||
public function testMap() {
|
||||
$map = DataObjectTest_TeamComment::get()->map()->toArray();
|
||||
$expected = array(
|
||||
@ -259,7 +264,7 @@ class DataListTest extends SapphireTest {
|
||||
$this->idFromFixture('DataObjectTest_TeamComment', 'comment2') => 'Bob',
|
||||
$this->idFromFixture('DataObjectTest_TeamComment', 'comment3') => 'Phil'
|
||||
);
|
||||
|
||||
|
||||
$this->assertEquals($expected, $map);
|
||||
$otherMap = DataObjectTest_TeamComment::get()->map('Name', 'TeamID')->toArray();
|
||||
$otherExpected = array(
|
||||
@ -267,22 +272,22 @@ class DataListTest extends SapphireTest {
|
||||
'Bob' => $this->objFromFixture('DataObjectTest_TeamComment', 'comment2')->TeamID,
|
||||
'Phil' => $this->objFromFixture('DataObjectTest_TeamComment', 'comment3')->TeamID
|
||||
);
|
||||
|
||||
|
||||
$this->assertEquals($otherExpected, $otherMap);
|
||||
}
|
||||
|
||||
|
||||
public function testEach() {
|
||||
$list = DataObjectTest_TeamComment::get();
|
||||
|
||||
|
||||
$count = 0;
|
||||
$test = $this;
|
||||
|
||||
|
||||
$list->each(function($item) use (&$count, $test) {
|
||||
$count++;
|
||||
|
||||
|
||||
$test->assertTrue(is_a($item, "DataObjectTest_TeamComment"));
|
||||
});
|
||||
|
||||
|
||||
$this->assertEquals($list->Count(), $count);
|
||||
}
|
||||
|
||||
@ -290,16 +295,16 @@ class DataListTest extends SapphireTest {
|
||||
// We can use raw SQL queries with where. This is only recommended for advanced uses;
|
||||
// if you can, you should use filter().
|
||||
$list = DataObjectTest_TeamComment::get();
|
||||
|
||||
|
||||
// where() returns a new DataList, like all the other modifiers, so it can be chained.
|
||||
$list2 = $list->where('"Name" = \'Joe\'');
|
||||
$this->assertEquals(array('This is a team comment by Joe'), $list2->column('Comment'));
|
||||
|
||||
|
||||
// The where() clauses are chained together with AND
|
||||
$list3 = $list2->where('"Name" = \'Bob\'');
|
||||
$this->assertEquals(array(), $list3->column('Comment'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test DataList->byID()
|
||||
*/
|
||||
@ -307,24 +312,24 @@ class DataListTest extends SapphireTest {
|
||||
// We can get a single item by ID.
|
||||
$id = $this->idFromFixture('DataObjectTest_Team','team2');
|
||||
$team = DataObjectTest_Team::get()->byID($id);
|
||||
|
||||
|
||||
// byID() returns a DataObject, rather than a DataList
|
||||
$this->assertInstanceOf('DataObjectTest_Team', $team);
|
||||
$this->assertEquals('Team 2', $team->Title);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test DataList->removeByID()
|
||||
*/
|
||||
public function testRemoveByID() {
|
||||
$list = DataObjectTest_Team::get();
|
||||
$id = $this->idFromFixture('DataObjectTest_Team','team2');
|
||||
|
||||
|
||||
$this->assertNotNull($list->byID($id));
|
||||
$list->removeByID($id);
|
||||
$this->assertNull($list->byID($id));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test DataList->canSortBy()
|
||||
*/
|
||||
@ -333,28 +338,28 @@ class DataListTest extends SapphireTest {
|
||||
$team = DataObjectTest_Team::get();
|
||||
$this->assertTrue($team->canSortBy("Title"));
|
||||
$this->assertFalse($team->canSortBy("SomethingElse"));
|
||||
|
||||
|
||||
// Subclasses
|
||||
$subteam = DataObjectTest_SubTeam::get();
|
||||
$this->assertTrue($subteam->canSortBy("Title"));
|
||||
$this->assertTrue($subteam->canSortBy("SubclassDatabaseField"));
|
||||
}
|
||||
|
||||
|
||||
public function testDataListArrayAccess() {
|
||||
$list = DataObjectTest_Team::get()->sort('Title');
|
||||
|
||||
|
||||
// We can use array access to refer to single items in the DataList, as if it were an array
|
||||
$this->assertEquals("Subteam 1", $list[0]->Title);
|
||||
$this->assertEquals("Subteam 3", $list[2]->Title);
|
||||
$this->assertEquals("Team 2", $list[4]->Title);
|
||||
}
|
||||
|
||||
|
||||
public function testFind() {
|
||||
$list = DataObjectTest_Team::get();
|
||||
$record = $list->find('Title', 'Team 1');
|
||||
$this->assertEquals($this->idFromFixture('DataObjectTest_Team', 'team1'), $record->ID);
|
||||
}
|
||||
|
||||
|
||||
public function testFindById() {
|
||||
$list = DataObjectTest_Team::get();
|
||||
$record = $list->find('ID', $this->idFromFixture('DataObjectTest_Team', 'team1'));
|
||||
@ -363,70 +368,70 @@ class DataListTest extends SapphireTest {
|
||||
$record = $list->find('ID', $this->idFromFixture('DataObjectTest_Team', 'team2'));
|
||||
$this->assertEquals('Team 2', $record->Title);
|
||||
}
|
||||
|
||||
|
||||
public function testSimpleSort() {
|
||||
$list = DataObjectTest_TeamComment::get();
|
||||
$list = $list->sort('Name');
|
||||
$this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob');
|
||||
$this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
|
||||
}
|
||||
|
||||
|
||||
public function testSimpleSortOneArgumentASC() {
|
||||
$list = DataObjectTest_TeamComment::get();
|
||||
$list = $list->sort('Name ASC');
|
||||
$this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob');
|
||||
$this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
|
||||
}
|
||||
|
||||
|
||||
public function testSimpleSortOneArgumentDESC() {
|
||||
$list = DataObjectTest_TeamComment::get();
|
||||
$list = $list->sort('Name DESC');
|
||||
$this->assertEquals('Phil', $list->first()->Name, 'Last comment should be from Phil');
|
||||
$this->assertEquals('Bob', $list->last()->Name, 'First comment should be from Bob');
|
||||
}
|
||||
|
||||
|
||||
public function testSortOneArgumentMultipleColumns() {
|
||||
$list = DataObjectTest_TeamComment::get();
|
||||
$list = $list->sort('TeamID ASC, Name DESC');
|
||||
$this->assertEquals('Joe', $list->first()->Name, 'First comment should be from Bob');
|
||||
$this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
|
||||
}
|
||||
|
||||
|
||||
public function testSimpleSortASC() {
|
||||
$list = DataObjectTest_TeamComment::get();
|
||||
$list = $list->sort('Name', 'asc');
|
||||
$this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob');
|
||||
$this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
|
||||
}
|
||||
|
||||
|
||||
public function testSimpleSortDESC() {
|
||||
$list = DataObjectTest_TeamComment::get();
|
||||
$list = $list->sort('Name', 'desc');
|
||||
$this->assertEquals('Phil', $list->first()->Name, 'Last comment should be from Phil');
|
||||
$this->assertEquals('Bob', $list->last()->Name, 'First comment should be from Bob');
|
||||
}
|
||||
|
||||
|
||||
public function testSortWithArraySyntaxSortASC() {
|
||||
$list = DataObjectTest_TeamComment::get();
|
||||
$list = $list->sort(array('Name'=>'asc'));
|
||||
$this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob');
|
||||
$this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
|
||||
}
|
||||
|
||||
|
||||
public function testSortWithArraySyntaxSortDESC() {
|
||||
$list = DataObjectTest_TeamComment::get();
|
||||
$list = $list->sort(array('Name'=>'desc'));
|
||||
$this->assertEquals('Phil', $list->first()->Name, 'Last comment should be from Phil');
|
||||
$this->assertEquals('Bob', $list->last()->Name, 'First comment should be from Bob');
|
||||
}
|
||||
|
||||
|
||||
public function testSortWithMultipleArraySyntaxSort() {
|
||||
$list = DataObjectTest_TeamComment::get();
|
||||
$list = $list->sort(array('TeamID'=>'asc','Name'=>'desc'));
|
||||
$this->assertEquals('Joe', $list->first()->Name, 'First comment should be from Bob');
|
||||
$this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* $list->filter('Name', 'bob'); // only bob in the list
|
||||
*/
|
||||
@ -437,7 +442,7 @@ class DataListTest extends SapphireTest {
|
||||
$this->assertEquals('Team 2', $list->first()->Title, 'List should only contain Team 2');
|
||||
$this->assertEquals('Team 2', $list->last()->Title, 'Last should only contain Team 2');
|
||||
}
|
||||
|
||||
|
||||
public function testSimpleFilterEndsWith() {
|
||||
$list = DataObjectTest_TeamComment::get();
|
||||
$list = $list->filter('Name:EndsWith', 'b');
|
||||
@ -518,7 +523,7 @@ class DataListTest extends SapphireTest {
|
||||
$this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob');
|
||||
$this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
|
||||
}
|
||||
|
||||
|
||||
public function testMultipleFilterWithNoMatch() {
|
||||
$list = DataObjectTest_TeamComment::get();
|
||||
$list = $list->filter(array('Name'=>'Bob', 'Comment'=>'Phil is a unique guy, and comments on team2'));
|
||||
@ -535,13 +540,13 @@ class DataListTest extends SapphireTest {
|
||||
$this->assertEquals(1, $list->count());
|
||||
$this->assertEquals('Bob', $list->first()->Name, 'Only comment should be from Bob');
|
||||
}
|
||||
|
||||
|
||||
public function testFilterMultipleWithTwoMatches() {
|
||||
$list = DataObjectTest_TeamComment::get();
|
||||
$list = $list->filter(array('TeamID'=>$this->idFromFixture('DataObjectTest_Team', 'team1')));
|
||||
$this->assertEquals(2, $list->count());
|
||||
}
|
||||
|
||||
|
||||
public function testFilterMultipleWithArrayFilter() {
|
||||
$list = DataObjectTest_TeamComment::get();
|
||||
$list = $list->filter(array('Name'=>array('Bob','Phil')));
|
||||
@ -559,7 +564,7 @@ class DataListTest extends SapphireTest {
|
||||
$this->assertEquals('Bob', $list->first()->Name);
|
||||
$this->assertEquals('Joe', $list->last()->Name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* $list->filter(array('Name'=>array('aziz','bob'), 'Age'=>array(21, 43)));
|
||||
*/
|
||||
@ -587,7 +592,7 @@ class DataListTest extends SapphireTest {
|
||||
$list = DataObjectTest_TeamComment::get();
|
||||
$list = $list->filterAny('Name', 'Bob');
|
||||
$this->assertEquals(1, $list->count());
|
||||
}
|
||||
}
|
||||
|
||||
public function testFilterAnyMultipleArray() {
|
||||
$list = DataObjectTest_TeamComment::get();
|
||||
@ -608,13 +613,13 @@ class DataListTest extends SapphireTest {
|
||||
$list = $list->sort('Name');
|
||||
$this->assertEquals(2, $list->count());
|
||||
$this->assertEquals(
|
||||
'Bob',
|
||||
$list->offsetGet(0)->Name,
|
||||
'Bob',
|
||||
$list->offsetGet(0)->Name,
|
||||
'Results should include comments from Bob, matched by comment and team'
|
||||
);
|
||||
$this->assertEquals(
|
||||
'Joe',
|
||||
$list->offsetGet(1)->Name,
|
||||
'Joe',
|
||||
$list->offsetGet(1)->Name,
|
||||
'Results should include comments by Joe, matched by name and team (not by comment)'
|
||||
);
|
||||
|
||||
@ -630,12 +635,12 @@ class DataListTest extends SapphireTest {
|
||||
$list = $list->filter(array('Name' => 'Bob'));
|
||||
$this->assertEquals(1, $list->count());
|
||||
$this->assertEquals(
|
||||
'Bob',
|
||||
$list->offsetGet(0)->Name,
|
||||
'Bob',
|
||||
$list->offsetGet(0)->Name,
|
||||
'Results should include comments from Bob, matched by name and team'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function testFilterAnyMultipleWithArrayFilter() {
|
||||
$list = DataObjectTest_TeamComment::get();
|
||||
$list = $list->filterAny(array('Name'=>array('Bob','Phil')));
|
||||
@ -643,7 +648,7 @@ class DataListTest extends SapphireTest {
|
||||
$this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob');
|
||||
$this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
|
||||
}
|
||||
|
||||
|
||||
public function testFilterAnyArrayInArray() {
|
||||
$list = DataObjectTest_TeamComment::get();
|
||||
$list = $list->filterAny(array(
|
||||
@ -652,18 +657,18 @@ class DataListTest extends SapphireTest {
|
||||
->sort('Name');
|
||||
$this->assertEquals(3, $list->count());
|
||||
$this->assertEquals(
|
||||
'Bob',
|
||||
$list->offsetGet(0)->Name,
|
||||
'Bob',
|
||||
$list->offsetGet(0)->Name,
|
||||
'Results should include comments from Bob, matched by name and team'
|
||||
);
|
||||
$this->assertEquals(
|
||||
'Joe',
|
||||
$list->offsetGet(1)->Name,
|
||||
'Joe',
|
||||
$list->offsetGet(1)->Name,
|
||||
'Results should include comments by Joe, matched by team (not by name)'
|
||||
);
|
||||
$this->assertEquals(
|
||||
'Phil',
|
||||
$list->offsetGet(2)->Name,
|
||||
'Phil',
|
||||
$list->offsetGet(2)->Name,
|
||||
'Results should include comments from Phil, matched by name (even if he\'s not in Team1)'
|
||||
);
|
||||
}
|
||||
@ -750,7 +755,7 @@ class DataListTest extends SapphireTest {
|
||||
$this->assertEquals('Joe', $list->first()->Name, 'First comment should be from Joe');
|
||||
$this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
|
||||
}
|
||||
//
|
||||
//
|
||||
/**
|
||||
* $list->exclude('Name', array('aziz', 'bob'); // exclude aziz and bob from list
|
||||
*/
|
||||
@ -769,7 +774,7 @@ class DataListTest extends SapphireTest {
|
||||
$list = $list->exclude(array('Name'=>'Bob', 'Comment'=>'Does not match any comments'));
|
||||
$this->assertEquals(3, $list->count());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* $list->exclude(array('Name'=>'bob, 'Age'=>21)); // exclude bob that has Age 21
|
||||
*/
|
||||
@ -786,7 +791,7 @@ class DataListTest extends SapphireTest {
|
||||
$list = DataObjectTest_TeamComment::get();
|
||||
$list = $list->filter('Comment', 'Phil is a unique guy, and comments on team2');
|
||||
$list = $list->exclude('Name', 'Bob');
|
||||
|
||||
|
||||
$this->assertContains(
|
||||
'WHERE ("DataObjectTest_TeamComment"."Comment" = '
|
||||
. '\'Phil is a unique guy, and comments on team2\') '
|
||||
@ -799,7 +804,7 @@ class DataListTest extends SapphireTest {
|
||||
$list = $list->exclude('Name:LessThan', 'Bob');
|
||||
$this->assertContains('WHERE (("DataObjectTest_TeamComment"."Name" >= \'Bob\'))', $list->sql());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* $list->exclude(array('Name'=>'bob, 'Age'=>array(21, 43))); // exclude bob with Age 21 or 43
|
||||
*/
|
||||
@ -814,7 +819,7 @@ class DataListTest extends SapphireTest {
|
||||
$this->assertEquals('Joe', $list->first()->Name, 'First comment should be from Phil');
|
||||
$this->assertEquals('Phil', $list->last()->Name, 'First comment should be from Phil');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* $list->exclude(array('Name'=>'bob, 'Age'=>array(21, 43))); // negative version
|
||||
*/
|
||||
@ -823,7 +828,7 @@ class DataListTest extends SapphireTest {
|
||||
$list = $list->exclude(array('Name'=>'Bob', 'TeamID'=>array(3)));
|
||||
$this->assertEquals(3, $list->count());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* $list->exclude(array('Name'=>array('bob','phil'), 'Age'=>array(21, 43))); //negative version
|
||||
*/
|
||||
@ -834,9 +839,9 @@ class DataListTest extends SapphireTest {
|
||||
'Comment' => 'Phil is a unique guy, and comments on team2'));
|
||||
$this->assertEquals(3, $list->count());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* $list->exclude(array('Name'=>array('bob','phil'), 'Age'=>array(21, 43)));
|
||||
* $list->exclude(array('Name'=>array('bob','phil'), 'Age'=>array(21, 43)));
|
||||
*/
|
||||
public function testMultipleExcludeWithTwoArray() {
|
||||
$list = DataObjectTest_TeamComment::get();
|
||||
@ -847,9 +852,9 @@ class DataListTest extends SapphireTest {
|
||||
$this->assertEquals(1, $list->count());
|
||||
$this->assertEquals('Phil', $list->last()->Name, 'Only comment should be from Phil');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* $list->exclude(array('Name'=>array('bob','phil'), 'Age'=>array(21, 43)));
|
||||
* $list->exclude(array('Name'=>array('bob','phil'), 'Age'=>array(21, 43)));
|
||||
*/
|
||||
public function testMultipleExcludeWithTwoArrayOneTeam() {
|
||||
$list = DataObjectTest_TeamComment::get();
|
||||
@ -863,7 +868,7 @@ class DataListTest extends SapphireTest {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*/
|
||||
public function testSortByRelation() {
|
||||
$list = DataObjectTest_TeamComment::get();
|
||||
@ -874,12 +879,12 @@ class DataListTest extends SapphireTest {
|
||||
$this->assertEquals($this->idFromFixture('DataObjectTest_Team', 'team1'), $list->last()->TeamID,
|
||||
'Last comment should be for Team 1');
|
||||
}
|
||||
|
||||
|
||||
public function testReverse() {
|
||||
$list = DataObjectTest_TeamComment::get();
|
||||
$list = $list->sort('Name');
|
||||
$list = $list->reverse();
|
||||
|
||||
|
||||
$this->assertEquals('Bob', $list->last()->Name, 'Last comment should be from Bob');
|
||||
$this->assertEquals('Phil', $list->first()->Name, 'First comment should be from Phil');
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user