From ffbeac6b7d3ed1a6e02a150573ee4b2f9251cf9c Mon Sep 17 00:00:00 2001 From: Daniel Hensby Date: Fri, 27 Feb 2015 00:10:32 +0000 Subject: [PATCH] Ensuring classinfo is case insensitive --- ...ilverStripeServiceConfigurationLocator.php | 32 +- core/ClassInfo.php | 94 +-- model/DataObject.php | 535 +++++++++--------- model/DataQuery.php | 138 ++--- tests/core/ClassInfoTest.php | 74 ++- tests/model/DataListTest.php | 161 +++--- 6 files changed, 554 insertions(+), 480 deletions(-) diff --git a/control/injector/SilverStripeServiceConfigurationLocator.php b/control/injector/SilverStripeServiceConfigurationLocator.php index a0a22bd25..b26b6fcd9 100644 --- a/control/injector/SilverStripeServiceConfigurationLocator.php +++ b/control/injector/SilverStripeServiceConfigurationLocator.php @@ -1,36 +1,36 @@ 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; diff --git a/core/ClassInfo.php b/core/ClassInfo.php index fed9fe7b3..71681f362 100644 --- a/core/ClassInfo.php +++ b/core/ClassInfo.php @@ -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: * * ClassInfo::subclassesFor('BaseClass'); * array( - * 0 => 'BaseClass', + * 'BaseClass' => 'BaseClass', * 'ChildClass' => 'ChildClass', * 'GrandChildClass' => 'GrandChildClass' * ) * - * + * * @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) { diff --git a/model/DataObject.php b/model/DataObject.php index d2db29b0c..526bb5c4e 100644 --- a/model/DataObject.php +++ b/model/DataObject.php @@ -5,16 +5,16 @@ *

Extensions

* * See {@link Extension} and {@link DataExtension}. - * + * *

Permission Control

- * + * * Object-level access control by {@link Permission}. Permission codes are arbitrary * strings which can be selected on a group-by-group basis. - * + * * * class Article extends DataObject implements PermissionProvider { * static $api_access = true; - * + * * function canView($member = false) { * return Permission::check('ARTICLE_VIEW'); * } @@ -36,13 +36,13 @@ * ); * } * } - * + * * - * Object-level access control by {@link Group} membership: + * Object-level access control by {@link Group} membership: * * 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'); * } - * + * * // ... * } * - * - * 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: * @@ -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(). * * @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: * * @@ -2581,7 +2582,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity * if($extended !== null) return $extended; * else return $normalValue; * - * + * * @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 ::get() instead of DataObject::get()'); } - + if($filter || $sort || $join || $limit || ($containerClass != 'DataList')) { throw new \InvalidArgumentException('If calling ::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}). - * + * * * 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: * * public static $many_many_extraFields = array( @@ -3584,7 +3585,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity * ) * ); * - * + * * @var array * @config */ @@ -3616,7 +3617,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity * "Name" => "PartialMatchFilter" * ); * - * + * * 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 * * 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 diff --git a/model/DataQuery.php b/model/DataQuery.php index 21b259dbd..625848e3f 100644 --- a/model/DataQuery.php +++ b/model/DataQuery.php @@ -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; diff --git a/tests/core/ClassInfoTest.php b/tests/core/ClassInfoTest.php index 844b7c84d..cc6952b7d 100644 --- a/tests/core/ClassInfoTest.php +++ b/tests/core/ClassInfoTest.php @@ -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 { - + } /** diff --git a/tests/model/DataListTest.php b/tests/model/DataListTest.php index 5ff707a03..faf9b353b 100755 --- a/tests/model/DataListTest.php +++ b/tests/model/DataListTest.php @@ -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'); }