Merge pull request #3949 from dhensby/pulls/case-sensitive-class-info

Ensuring classinfo is case insensitive
This commit is contained in:
Damian Mooyman 2015-07-28 22:36:53 +12:00
commit 912b133ba1
6 changed files with 554 additions and 480 deletions

View File

@ -1,36 +1,36 @@
<?php
/**
* Use the SilverStripe configuration system to lookup config for a
* Use the SilverStripe configuration system to lookup config for a
* particular service.
*
* @package framework
* @subpackage injector
*/
class SilverStripeServiceConfigurationLocator extends ServiceConfigurationLocator {
/**
* List of Injector configurations cached from Config in class => config format.
* If any config is false, this denotes that this class and all its parents
* If any config is false, this denotes that this class and all its parents
* have no configuration specified.
*
*
* @var array
*/
protected $configs = array();
public function locateConfigFor($name) {
// Check direct or cached result
$config = $this->configFor($name);
if($config !== null) return $config;
// do parent lookup if it's a class
if (class_exists($name)) {
$parents = array_reverse(array_keys(ClassInfo::ancestry($name)));
$parents = array_reverse(array_values(ClassInfo::ancestry($name)));
array_shift($parents);
foreach ($parents as $parent) {
// have we already got for this?
// have we already got for this?
$config = $this->configFor($parent);
if($config !== null) {
// Cache this result
@ -39,27 +39,27 @@ class SilverStripeServiceConfigurationLocator extends ServiceConfigurationLocato
}
}
}
// there is no parent config, so we'll record that as false so we don't do the expensive
// lookup through parents again
$this->configs[$name] = false;
}
/**
* Retrieves the config for a named service without performing a hierarchy walk
*
*
* @param string $name Name of service
* @return mixed Returns either the configuration data, if there is any. A missing config is denoted
* @return mixed Returns either the configuration data, if there is any. A missing config is denoted
* by a value of either null (there is no direct config assigned and a hierarchy walk is necessary)
* or false (there is no config for this class, nor within the hierarchy for this class).
* or false (there is no config for this class, nor within the hierarchy for this class).
*/
protected function configFor($name) {
// Return cached result
if (isset($this->configs[$name])) {
return $this->configs[$name]; // Potentially false
}
$config = Config::inst()->get('Injector', $name);
if ($config) {
$this->configs[$name] = $config;

View File

@ -3,8 +3,8 @@
/**
* Provides introspection information about the class tree.
*
* It's a cached wrapper around the built-in class functions. SilverStripe uses
* class introspection heavily and without the caching it creates an unfortunate
* It's a cached wrapper around the built-in class functions. SilverStripe uses
* class introspection heavily and without the caching it creates an unfortunate
* performance hit.
*
* @package framework
@ -35,7 +35,7 @@ class ClassInfo {
* @var Array Cache for {@link ancestry()}.
*/
private static $_cache_ancestry = array();
/**
* @todo Move this to SS_Database or DB
*/
@ -52,17 +52,18 @@ class ClassInfo {
return false;
}
}
public static function reset_db_cache() {
self::$_cache_all_tables = null;
self::$_cache_ancestry = array();
}
/**
* Returns the manifest of all classes which are present in the database.
* @param string $class Class name to check enum values for ClassName field
*/
public static function getValidSubClasses($class = 'SiteTree', $includeUnbacked = false) {
$class = self::class_name($class);
$classes = DB::getConn()->enumValuesForField($class, 'ClassName');
if (!$includeUnbacked) $classes = array_filter($classes, array('ClassInfo', 'exists'));
return $classes;
@ -71,7 +72,7 @@ class ClassInfo {
/**
* Returns an array of the current class and all its ancestors and children
* which have a DB table.
*
*
* @param string|object $class
* @todo Move this into data object
* @return array
@ -79,9 +80,7 @@ class ClassInfo {
public static function dataClassesFor($class) {
$result = array();
if (is_object($class)) {
$class = get_class($class);
}
$class = self::class_name($class);
$classes = array_merge(
self::ancestry($class),
@ -102,7 +101,7 @@ class ClassInfo {
* @return string
*/
public static function baseDataClass($class) {
if (is_object($class)) $class = get_class($class);
$class = self::class_name($class);
if (!is_subclass_of($class, 'DataObject')) {
throw new InvalidArgumentException("$class is not a subclass of DataObject");
@ -121,23 +120,25 @@ class ClassInfo {
* Returns a list of classes that inherit from the given class.
* The resulting array includes the base class passed
* through the $class parameter as the first array value.
*
*
* Example usage:
* <code>
* ClassInfo::subclassesFor('BaseClass');
* array(
* 0 => 'BaseClass',
* 'BaseClass' => 'BaseClass',
* 'ChildClass' => 'ChildClass',
* 'GrandChildClass' => 'GrandChildClass'
* )
* </code>
*
*
* @param mixed $class string of the classname or instance of the class
* @return array Names of all subclasses as an associative array.
*/
public static function subclassesFor($class) {
//normalise class case
$className = self::class_name($class);
$descendants = SS_ClassLoader::instance()->getManifest()->getDescendantsOf($class);
$result = array($class => $class);
$result = array($className => $className);
if ($descendants) {
return $result + ArrayLib::valuekey($descendants);
@ -146,6 +147,23 @@ class ClassInfo {
}
}
/**
* Convert a class name in any case and return it as it was defined in PHP
*
* eg: self::class_name('dataobJEct'); //returns 'DataObject'
*
* @param string|object $nameOrObject The classname or object you want to normalise
*
* @return string The normalised class name
*/
public static function class_name($nameOrObject) {
if (is_object($nameOrObject)) {
return get_class($nameOrObject);
}
$reflection = new ReflectionClass($nameOrObject);
return $reflection->getName();
}
/**
* Returns the passed class name along with all its parent class names in an
* array, sorted with the root class first.
@ -155,9 +173,11 @@ class ClassInfo {
* @return array
*/
public static function ancestry($class, $tablesOnly = false) {
if (!is_string($class)) $class = get_class($class);
$class = self::class_name($class);
$cacheKey = $class . '_' . (string)$tablesOnly;
$lClass = strtolower($class);
$cacheKey = $lClass . '_' . (string)$tablesOnly;
$parent = $class;
if(!isset(self::$_cache_ancestry[$cacheKey])) {
$ancestry = array();
@ -166,7 +186,7 @@ class ClassInfo {
$ancestry[$parent] = $parent;
}
} while ($parent = get_parent_class($parent));
self::$_cache_ancestry[$cacheKey] = array_reverse($ancestry);
self::$_cache_ancestry[$cacheKey] = array_reverse($ancestry);
}
return self::$_cache_ancestry[$cacheKey];
@ -184,16 +204,16 @@ class ClassInfo {
* Returns true if the given class implements the given interface
*/
public static function classImplements($className, $interfaceName) {
return in_array($className, SS_ClassLoader::instance()->getManifest()->getImplementorsOf($interfaceName));
return in_array($className, self::implementorsOf($interfaceName));
}
/**
* Get all classes contained in a file.
* @uses ManifestBuilder
*
*
* @todo Doesn't return additional classes that only begin
* with the filename, and have additional naming separated through underscores.
*
*
* @param string $filePath Path to a PHP file (absolute or relative to webroot)
* @return array
*/
@ -205,16 +225,16 @@ class ClassInfo {
foreach($manifest as $class => $compareFilePath) {
if($absFilePath == $compareFilePath) $matchedClasses[] = $class;
}
return $matchedClasses;
}
/**
* Returns all classes contained in a certain folder.
*
* @todo Doesn't return additional classes that only begin
* with the filename, and have additional naming separated through underscores.
*
*
* @param string $folderPath Relative or absolute folder path
* @return array Array of class names
*/
@ -233,25 +253,28 @@ class ClassInfo {
private static $method_from_cache = array();
public static function has_method_from($class, $method, $compclass) {
if (!isset(self::$method_from_cache[$class])) self::$method_from_cache[$class] = array();
$lClass = strtolower($class);
$lMethod = strtolower($method);
$lCompclass = strtolower($compclass);
if (!isset(self::$method_from_cache[$lClass])) self::$method_from_cache[$lClass] = array();
if (!array_key_exists($method, self::$method_from_cache[$class])) {
self::$method_from_cache[$class][$method] = false;
if (!array_key_exists($lMethod, self::$method_from_cache[$lClass])) {
self::$method_from_cache[$lClass][$lMethod] = false;
$classRef = new ReflectionClass($class);
if ($classRef->hasMethod($method)) {
$methodRef = $classRef->getMethod($method);
self::$method_from_cache[$class][$method] = $methodRef->getDeclaringClass()->getName();
self::$method_from_cache[$lClass][$lMethod] = $methodRef->getDeclaringClass()->getName();
}
}
return self::$method_from_cache[$class][$method] == $compclass;
return strtolower(self::$method_from_cache[$lClass][$lMethod]) == $lCompclass;
}
/**
* Returns the table name in the class hierarchy which contains a given
* Returns the table name in the class hierarchy which contains a given
* field column for a {@link DataObject}. If the field does not exist, this
* will return null.
*
@ -261,23 +284,26 @@ class ClassInfo {
* @return string
*/
public static function table_for_object_field($candidateClass, $fieldName) {
if(!$candidateClass || !$fieldName) {
if(!$candidateClass || !$fieldName || !is_subclass_of($candidateClass, 'DataObject')) {
return null;
}
$exists = class_exists($candidateClass);
//normalise class name
$candidateClass = self::class_name($candidateClass);
$exists = self::exists($candidateClass);
while($candidateClass && $candidateClass != 'DataObject' && $exists) {
if(DataObject::has_own_table($candidateClass)) {
$inst = singleton($candidateClass);
if($inst->hasOwnTableDatabaseField($fieldName)) {
break;
}
}
$candidateClass = get_parent_class($candidateClass);
$exists = class_exists($candidateClass);
$exists = $candidateClass && self::exists($candidateClass);
}
if(!$candidateClass || !$exists) {

View File

@ -5,16 +5,16 @@
* <h2>Extensions</h2>
*
* See {@link Extension} and {@link DataExtension}.
*
*
* <h2>Permission Control</h2>
*
*
* Object-level access control by {@link Permission}. Permission codes are arbitrary
* strings which can be selected on a group-by-group basis.
*
*
* <code>
* class Article extends DataObject implements PermissionProvider {
* static $api_access = true;
*
*
* function canView($member = false) {
* return Permission::check('ARTICLE_VIEW');
* }
@ -36,13 +36,13 @@
* );
* }
* }
* </code>
* </code>
*
* Object-level access control by {@link Group} membership:
* Object-level access control by {@link Group} membership:
* <code>
* class Article extends DataObject {
* static $api_access = true;
*
*
* function canView($member = false) {
* if(!$member) $member = Member::currentUser();
* return $member->inGroup('Subscribers');
@ -51,18 +51,18 @@
* if(!$member) $member = Member::currentUser();
* return $member->inGroup('Editors');
* }
*
*
* // ...
* }
* </code>
*
* If any public method on this class is prefixed with an underscore,
*
* If any public method on this class is prefixed with an underscore,
* the results are cached in memory through {@link cachedCall()}.
*
*
*
*
* @todo Add instance specific removeExtension() which undos loadExtraStatics()
* and defineMethods()
*
*
* @package framework
* @subpackage model
*
@ -72,21 +72,21 @@
* @property string Created Date and time of DataObject creation.
*/
class DataObject extends ViewableData implements DataObjectInterface, i18nEntityProvider {
/**
* Human-readable singular name.
* @var string
* @config
*/
private static $singular_name = null;
/**
* Human-readable plural name
* @var string
* @config
*/
private static $plural_name = null;
/**
* Allow API access to this object?
* @todo Define the options that can be set here
@ -99,18 +99,18 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* @var boolean
*/
public $destroyed = false;
/**
* The DataModel from this this object comes
*/
protected $model;
/**
* Data stored in this objects database record. An array indexed by fieldname.
*
* Data stored in this objects database record. An array indexed by fieldname.
*
* Use {@link toMap()} if you want an array representation
* of this object, as the $record array might contain lazy loaded field aliases.
*
*
* @var array
*/
protected $record;
@ -119,7 +119,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* An array indexed by fieldname, true if the field has been changed.
* Use {@link getChangedFields()} and {@link isChanged()} to inspect
* the changed state.
*
*
* @var array
*/
private $changed;
@ -136,13 +136,13 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* @var boolean
*/
protected $brokenOnDelete = false;
/**
* Used by onBeforeWrite() to ensure child classes call parent::onBeforeWrite()
* @var boolean
*/
protected $brokenOnWrite = false;
/**
* @config
* @var boolean Should dataobjects be validated before they are written?
@ -185,7 +185,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
/**
* Returns when validation on DataObjects is enabled.
*
*
* @deprecated 3.2 Use the "DataObject.validation_enabled" config setting instead
* @return bool
*/
@ -193,14 +193,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
Deprecation::notice('3.2', 'Use the "DataObject.validation_enabled" config setting instead');
return Config::inst()->get('DataObject', 'validation_enabled');
}
/**
* Set whether DataObjects should be validated before they are written.
*
*
* Caution: Validation can contain safeguards against invalid/malicious data,
* and check permission levels (e.g. on {@link Group}). Therefore it is recommended
* to only disable validation for very specific use cases.
*
*
* @param $enable bool
* @see DataObject::validate()
* @deprecated 3.2 Use the "DataObject.validation_enabled" config setting instead
@ -214,7 +214,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* @var [string] - class => ClassName field definition cache for self::database_fields
*/
private static $classname_spec_cache = array();
/**
* Clear all cached classname specs. It's necessary to clear all cached subclassed names
* for any classes if a new class manifest is generated.
@ -260,14 +260,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
}
/**
* Get all database columns explicitly defined on a class in {@link DataObject::$db}
* and {@link DataObject::$has_one}. Resolves instances of {@link CompositeDBField}
* into the actual database fields, rather than the name of the field which
* Get all database columns explicitly defined on a class in {@link DataObject::$db}
* and {@link DataObject::$has_one}. Resolves instances of {@link CompositeDBField}
* into the actual database fields, rather than the name of the field which
* might not equate a database column.
*
*
* Does not include "base fields" like "ID", "ClassName", "Created", "LastEdited",
* see {@link database_fields()}.
*
*
* @uses CompositeDBField->compositeDatabaseFields()
*
* @param string $class
@ -283,14 +283,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
foreach(self::composite_fields($class, false) as $fieldName => $fieldClass) {
// Remove the original fieldname, it's not an actual database column
unset($fields[$fieldName]);
// Add all composite columns
$compositeFields = singleton($fieldClass)->compositeDatabaseFields();
if($compositeFields) foreach($compositeFields as $compositeName => $spec) {
$fields["{$fieldName}{$compositeName}"] = $spec;
}
}
// Add has_one relationships
$hasOne = Config::inst()->get($class, 'has_one', Config::UNINHERITED);
if($hasOne) foreach(array_keys($hasOne) as $field) {
@ -303,7 +303,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
return $output;
}
/**
* Returns the field class if the given db field on the class is a composite field.
* Will check all applicable ancestor classes and aggregate results.
@ -317,7 +317,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
if(!isset(DataObject::$_cache_composite_fields[$class])) {
self::cache_composite_fields($class);
}
if(isset(DataObject::$_cache_composite_fields[$class][$name])) {
$isComposite = DataObject::$_cache_composite_fields[$class][$name];
} elseif($aggregated && $class != 'DataObject' && ($parentClass=get_parent_class($class)) != 'DataObject') {
@ -336,14 +336,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
*/
public static function composite_fields($class, $aggregated = true) {
if(!isset(DataObject::$_cache_composite_fields[$class])) self::cache_composite_fields($class);
$compositeFields = DataObject::$_cache_composite_fields[$class];
if($aggregated && $class != 'DataObject' && ($parentClass=get_parent_class($class)) != 'DataObject') {
$compositeFields = array_merge($compositeFields,
$compositeFields = array_merge($compositeFields,
self::composite_fields($parentClass));
}
return $compositeFields;
}
@ -352,7 +352,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
*/
private static function cache_composite_fields($class) {
$compositeFields = array();
$fields = Config::inst()->get($class, 'db', Config::UNINHERITED);
if($fields) foreach($fields as $fieldName => $fieldClass) {
if(!is_string($fieldClass)) continue;
@ -360,16 +360,16 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
// Strip off any parameters
$bPos = strpos($fieldClass, '(');
if($bPos !== FALSE) $fieldClass = substr($fieldClass, 0, $bPos);
// Test to see if it implements CompositeDBField
if(ClassInfo::classImplements($fieldClass, 'CompositeDBField')) {
$compositeFields[$fieldName] = $fieldClass;
}
}
DataObject::$_cache_composite_fields[$class] = $compositeFields;
}
/**
* Construct a new DataObject.
*
@ -448,7 +448,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
// prevent populateDefaults() and setField() from marking overwritten defaults as changed
$this->changed = array();
}
/**
* Set the DataModel
* @param DataModel $model
@ -481,14 +481,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
$className = $this->class;
$clone = new $className( $this->toMap(), false, $this->model );
$clone->ID = 0;
$clone->invokeWithExtensions('onBeforeDuplicate', $this, $doWrite);
if($doWrite) {
$clone->write();
$this->duplicateManyManyRelations($this, $clone);
}
$clone->invokeWithExtensions('onAfterDuplicate', $this, $doWrite);
return $clone;
}
@ -526,7 +526,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* Helper function to duplicate relations from one object to another
* @param $sourceObject the source object to duplicate from
* @param $destinationObject the destination object to populate with the duplicated relations
* @param $name the name of the relation to duplicate (e.g. members)
* @param $name the name of the relation to duplicate (e.g. members)
*/
private function duplicateRelations($sourceObject, $destinationObject, $name) {
$relations = $sourceObject->$name();
@ -553,7 +553,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
if (!ClassInfo::exists($className)) return get_class($this);
return $className;
}
/**
* Set the ClassName attribute. {@link $class} is also updated.
* Warning: This will produce an inconsistent record, as the object
@ -579,7 +579,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* it ensures that the instance of the class is a match for the className of the
* record. Don't set the {@link DataObject->class} or {@link DataObject->ClassName}
* property manually before calling this method, as it will confuse change detection.
*
*
* If the new class is different to the original class, defaults are populated again
* because this will only occur automatically on instantiation of a DataObject if
* there is no record, or the record has no ID. In this case, we do have an ID but
@ -598,7 +598,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
'RecordClassName' => $originalClass,
)
), false, $this->model);
if($newClassName != $originalClass) {
$newInstance->setClassName($newClassName);
$newInstance->populateDefaults();
@ -667,9 +667,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* Returns TRUE if all values (other than "ID") are
* considered empty (by weak boolean comparison).
* Only checks for fields listed in {@link custom_database_fields()}
*
*
* @todo Use DBField->hasValue()
*
*
* @return boolean
*/
public function isEmpty(){
@ -679,7 +679,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
foreach($map as $k=>$v){
// only look at custom fields
if(!array_key_exists($k, $customFields)) continue;
$dbObj = ($v instanceof DBField) ? $v : $this->dbObject($k);
$isEmpty = ($isEmpty && !$dbObj->exists());
}
@ -698,7 +698,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
if(!$name = $this->stat('singular_name')) {
$name = ucwords(trim(strtolower(preg_replace('/_?([A-Z])/', ' $1', $this->class))));
}
return $name;
}
@ -749,14 +749,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
$name = $this->plural_name();
return _t($this->class.'.PLURALNAME', $name);
}
/**
* Standard implementation of a title/label for a specific
* record. Tries to find properties 'Title' or 'Name',
* and falls back to the 'ID'. Useful to provide
* user-friendly identification of a record, e.g. in errormessages
* or UI-selections.
*
*
* Overload this method to have a more specialized implementation,
* e.g. for an Address record this could be:
* <code>
@ -770,7 +770,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
public function getTitle() {
if($this->hasDatabaseField('Title')) return $this->getField('Title');
if($this->hasDatabaseField('Name')) return $this->getField('Name');
return "#{$this->ID}";
}
@ -808,11 +808,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
/**
* Update a number of fields on this object, given a map of the desired changes.
*
*
* The field names can be simple names, or you can use a dot syntax to access $has_one relations.
* For example, array("Author.FirstName" => "Jim") will set $this->Author()->FirstName to "Jim".
*
* update() doesn't write the main object, but if you use the dot syntax, it will write()
*
* update() doesn't write the main object, but if you use the dot syntax, it will write()
* the related objects that it alters.
*
* @param array $data A map of field name to data values to update.
@ -840,8 +840,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
}
} else {
user_error(
"DataObject::update(): Can't traverse relationship '$relation'," .
"it has to be a has_one relationship or return a single DataObject",
"DataObject::update(): Can't traverse relationship '$relation'," .
"it has to be a has_one relationship or return a single DataObject",
E_USER_NOTICE
);
// unset relation object so we don't write properties to the wrong object
@ -865,7 +865,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
}
return $this;
}
/**
* Pass changes as a map, and try to
* get automatic casting for these fields.
@ -977,7 +977,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* Forces the record to think that all its data has changed.
* Doesn't write to the database. Only sets fields as changed
* if they are not already marked as changed.
*
*
* @return DataObject $this
*/
public function forceChange() {
@ -986,32 +986,32 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
// $this->record might not contain the blank values so we loop on $this->inheritedDatabaseFields() as well
$fieldNames = array_unique(array_merge(
array_keys($this->record),
array_keys($this->record),
array_keys($this->inheritedDatabaseFields())));
foreach($fieldNames as $fieldName) {
if(!isset($this->changed[$fieldName])) $this->changed[$fieldName] = 1;
// Populate the null values in record so that they actually get written
if(!isset($this->record[$fieldName])) $this->record[$fieldName] = null;
}
// @todo Find better way to allow versioned to write a new version after forceChange
if($this->isChanged('Version')) unset($this->changed['Version']);
return $this;
}
/**
* Validate the current object.
*
* By default, there is no validation - objects are always valid! However, you can overload this method in your
* DataObject sub-classes to specify custom validation, or use the hook through DataExtension.
*
*
* Invalid objects won't be able to be written - a warning will be thrown and no write will occur. onBeforeWrite()
* and onAfterWrite() won't get called either.
*
*
* It is expected that you call validate() in your own application to test that an object is valid before
* attempting a write, and respond appropriately if it isn't.
*
*
* @see {@link ValidationResult}
* @return ValidationResult
*/
@ -1027,12 +1027,12 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* database. Don't forget to call parent::onBeforeWrite(), though!
*
* This called after {@link $this->validate()}, so you can be sure that your data is valid.
*
*
* @uses DataExtension->onBeforeWrite()
*/
protected function onBeforeWrite() {
$this->brokenOnWrite = false;
$dummy = null;
$this->extend('onBeforeWrite', $dummy);
}
@ -1059,11 +1059,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
*/
protected function onBeforeDelete() {
$this->brokenOnDelete = false;
$dummy = null;
$this->extend('onBeforeDelete', $dummy);
}
protected function onAfterDelete() {
$this->extend('onAfterDelete');
}
@ -1072,22 +1072,22 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* Load the default values in from the self::$defaults array.
* Will traverse the defaults of the current class and all its parent classes.
* Called by the constructor when creating new records.
*
*
* @uses DataExtension->populateDefaults()
* @return DataObject $this
*/
public function populateDefaults() {
$classes = array_reverse(ClassInfo::ancestry($this));
foreach($classes as $class) {
$defaults = Config::inst()->get($class, 'defaults', Config::UNINHERITED);
if($defaults && !is_array($defaults)) {
user_error("Bad '$this->class' defaults given: " . var_export($defaults, true),
E_USER_WARNING);
$defaults = null;
}
if($defaults) foreach($defaults as $fieldName => $fieldValue) {
// SRM 2007-03-06: Stricter check
if(!isset($this->$fieldName) || $this->$fieldName === null) {
@ -1103,7 +1103,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
break;
}
}
$this->extend('populateDefaults');
return $this;
}
@ -1114,7 +1114,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* - All relevant tables will be updated.
* - $this->onBeforeWrite() gets called beforehand.
* - Extensions such as Versioned will ammend the database-write to ensure that a version is saved.
*
*
* @uses DataExtension->augmentWrite()
*
* @param boolean $showDebug Show debugging information
@ -1183,7 +1183,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
foreach($this->record as $k => $v) {
$this->changed[$k] = 2;
}
$firstWrite = true;
}
@ -1207,12 +1207,12 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
}
}
if($hasChanges || $forceWrite || !$this->record['ID']) {
// New records have their insert into the base data table done first, so that they can pass the
// generated primary key on to the rest of the manipulation
$baseTable = $ancestry[0];
if((!isset($this->record['ID']) || !$this->record['ID']) && isset($ancestry[0])) {
if((!isset($this->record['ID']) || !$this->record['ID']) && isset($ancestry[0])) {
DB::query("INSERT INTO \"{$baseTable}\" (\"Created\") VALUES (" . DB::getConn()->now() . ")");
$this->record['ID'] = DB::getGeneratedID($baseTable);
@ -1226,9 +1226,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
if(isset($ancestry) && is_array($ancestry)) {
foreach($ancestry as $idx => $class) {
$classSingleton = singleton($class);
foreach($this->record as $fieldName => $fieldValue) {
if(isset($this->changed[$fieldName]) && $this->changed[$fieldName]
if(isset($this->changed[$fieldName]) && $this->changed[$fieldName]
&& $fieldType = $classSingleton->hasOwnTableDatabaseField($fieldName)) {
$fieldObj = $this->dbObject($fieldName);
@ -1274,13 +1274,13 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
}
}
$this->extend('augmentWrite', $manipulation);
// New records have their insert into the base data table done first, so that they can pass the
// generated ID on to the rest of the manipulation
if(isset($isNewRecord) && $isNewRecord && isset($manipulation[$baseTable])) {
$manipulation[$baseTable]['command'] = 'update';
}
DB::manipulate($manipulation);
// If there's any relations that couldn't be saved before, save them now (we have an ID here)
@ -1328,13 +1328,13 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
*/
public function writeComponents($recursive = false) {
if(!$this->components) return $this;
foreach($this->components as $component) {
$component->write(false, false, false, $recursive);
}
return $this;
}
/**
* Delete this data object.
* $this->onBeforeDelete() gets called.
@ -1348,7 +1348,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
user_error("$this->class has a broken onBeforeDelete() function."
. " Make sure that you call parent::onBeforeDelete().", E_USER_ERROR);
}
// Deleting a record without an ID shouldn't do anything
if(!$this->ID) throw new LogicException("DataObject::delete() called on a DataObject without an ID");
@ -1365,7 +1365,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
}
// Remove this item out of any caches
$this->flushCache();
$this->onAfterDelete();
$this->OldID = $this->ID;
@ -1417,11 +1417,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
if(isset($this->components[$componentName])) {
return $this->components[$componentName];
}
if($class = $this->has_one($componentName)) {
$joinField = $componentName . 'ID';
$joinID = $this->getField($joinField);
if($joinID) {
$component = $this->model->$class->byID($joinID);
}
@ -1432,11 +1432,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
} elseif($class = $this->belongs_to($componentName)) {
$joinField = $this->getRemoteJoinField($componentName, 'belongs_to');
$joinID = $this->ID;
if($joinID) {
$component = DataObject::get_one($class, "\"$joinField\" = $joinID");
}
if(!isset($component) || !$component) {
$component = $this->model->$class->newObject();
$component->$joinField = $this->ID;
@ -1444,7 +1444,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
} else {
throw new Exception("DataObject->getComponent(): Could not find component '$componentName'.");
}
$this->components[$componentName] = $component;
return $component;
}
@ -1479,13 +1479,13 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
if(!$this->ID) {
if(!isset($this->unsavedRelations[$componentName])) {
$this->unsavedRelations[$componentName] =
new UnsavedRelationList($this->class, $componentName, $componentClass);
new UnsavedRelationList($this->class, $componentName, $componentClass);
}
return $this->unsavedRelations[$componentName];
}
$joinField = $this->getRemoteJoinField($componentName, 'has_many');
$result = HasManyList::create($componentClass, $joinField);
if($this->model) $result->setDataModel($this->model);
$result = $result->forForeignID($this->ID);
@ -1544,7 +1544,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
*/
public function getRemoteJoinField($component, $type = 'has_many') {
$remoteClass = $this->$type($component, false);
if(!$remoteClass) {
throw new Exception("Unknown $type component '$component' on class '$this->class'");
}
@ -1553,7 +1553,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
"Class '$remoteClass' not found, but used in $type component '$component' on class '$this->class'"
);
}
if($fieldPos = strpos($remoteClass, '.')) {
return substr($remoteClass, $fieldPos + 1) . 'ID';
}
@ -1563,7 +1563,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
$remoteRelations = array();
}
$remoteRelations = array_flip($remoteRelations);
// look for remote has_one joins on this class or any parent classes
foreach(array_reverse(ClassInfo::ancestry($this)) as $class) {
if(array_key_exists($class, $remoteRelations)) return $remoteRelations[$class] . 'ID';
@ -1577,7 +1577,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
}
throw new Exception($message);
}
/**
* Returns a many-to-many component, as a ManyManyList.
* @param string $componentName Name of the many-many component
@ -1592,11 +1592,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
if(!$this->ID) {
if(!isset($this->unsavedRelations[$componentName])) {
$this->unsavedRelations[$componentName] =
new UnsavedRelationList($parentClass, $componentName, $componentClass);
new UnsavedRelationList($parentClass, $componentName, $componentClass);
}
return $this->unsavedRelations[$componentName];
}
$result = ManyManyList::create($componentClass, $table, $componentField, $parentField,
$this->many_many_extraFields($componentName));
if($this->model) $result->setDataModel($this->model);
@ -1604,10 +1604,10 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
// If this is called on a singleton, then we return an 'orphaned relation' that can have the
// foreignID set elsewhere.
$result = $result->forForeignID($this->ID);
return $result->where($filter)->sort($sort)->limit($limit);
}
/**
* Return the class of a one-to-one component. If $component is null, return all of the one-to-one components and
* their classes.
@ -1626,7 +1626,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
if($component) {
$hasOne = Config::inst()->get($class, 'has_one', Config::UNINHERITED);
if(isset($hasOne[$component])) {
return $hasOne[$component];
}
@ -1635,7 +1635,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
// Validate the data
foreach($newItems as $k => $v) {
if(!is_string($k) || is_numeric($k) || !is_string($v)) {
user_error("$class::\$has_one has a bad entry: "
user_error("$class::\$has_one has a bad entry: "
. var_export($k,true). " => " . var_export($v,true) . ". Each map key should be a"
. " relationship name, and the map value should be the data class to join to.", E_USER_ERROR);
}
@ -1645,7 +1645,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
}
return isset($items) ? $items : null;
}
/**
* Returns the class of a remote belongs_to relationship. If no component is specified a map of all components and
* their class name will be returned.
@ -1657,7 +1657,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
*/
public function belongs_to($component = null, $classOnly = true) {
$belongsTo = $this->config()->belongs_to;
if($component) {
if($belongsTo && array_key_exists($component, $belongsTo)) {
$belongsTo = $belongsTo[$component];
@ -1665,14 +1665,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
return false;
}
}
if($belongsTo && $classOnly) {
return preg_replace('/(.+)?\..+/', '$1', $belongsTo);
} else {
return $belongsTo ? $belongsTo : array();
}
}
/**
* Return all of the database fields defined in self::$db and all the parent classes.
* Doesn't include any fields specified by self::$has_one. Use $this->has_one() to get these fields
@ -1705,7 +1705,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
// Validate the data
foreach($dbItems as $k => $v) {
if(!is_string($k) || is_numeric($k) || !is_string($v)) {
user_error("$class::\$db has a bad entry: "
user_error("$class::\$db has a bad entry: "
. var_export($k,true). " => " . var_export($v,true) . ". Each map key should be a"
. " property name, and the map value should be the property type.", E_USER_ERROR);
}
@ -1729,7 +1729,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
*/
public function has_many($component = null, $classOnly = true) {
$hasMany = $this->config()->has_many;
if($component) {
if($hasMany && array_key_exists($component, $hasMany)) {
$hasMany = $hasMany[$component];
@ -1737,7 +1737,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
return false;
}
}
if($hasMany && $classOnly) {
return preg_replace('/(.+)?\..+/', '$1', $hasMany);
} else {
@ -1747,10 +1747,10 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
/**
* Return the many-to-many extra fields specification.
*
*
* If you don't specify a component name, it returns all
* extra fields for all components available.
*
*
* @param string $component Name of component
* @return array
*/
@ -1770,13 +1770,13 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
if(isset($extraFields[$component])) {
return $extraFields[$component];
}
$manyMany = $SNG_class->stat('many_many');
$candidate = (isset($manyMany[$component])) ? $manyMany[$component] : null;
if($candidate) {
$SNG_candidate = singleton($candidate);
$candidateManyMany = $SNG_candidate->stat('belongs_many_many');
// Find the relation given the class
if($candidateManyMany) foreach($candidateManyMany as $relation => $relatedClass) {
if($relatedClass == $class) {
@ -1784,7 +1784,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
break;
}
}
if($relationName) {
$extraFields = $SNG_candidate->stat('many_many_extraFields');
if(isset($extraFields[$relationName])) {
@ -1792,30 +1792,30 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
}
}
}
$manyMany = $SNG_class->stat('belongs_many_many');
$candidate = (isset($manyMany[$component])) ? $manyMany[$component] : null;
if($candidate) {
$SNG_candidate = singleton($candidate);
$candidateManyMany = $SNG_candidate->stat('many_many');
// Find the relation given the class
if($candidateManyMany) foreach($candidateManyMany as $relation => $relatedClass) {
if($relatedClass == $class) {
$relationName = $relation;
}
}
$extraFields = $SNG_candidate->stat('many_many_extraFields');
if(isset($extraFields[$relationName])) {
return $extraFields[$relationName];
}
}
} else {
} else {
// Find all the extra fields for all components
$newItems = (array)Config::inst()->get($class, 'many_many_extraFields', Config::UNINHERITED);
foreach($newItems as $k => $v) {
if(!is_array($v)) {
user_error(
@ -1826,14 +1826,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
);
}
}
$items = isset($items) ? array_merge($newItems, $items) : $newItems;
}
}
return isset($items) ? $items : null;
}
/**
* Return information about a many-to-many component.
* The return value is an array of (parentclass, childclass). If $component is null, then all many-many
@ -1887,18 +1887,18 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
// Validate the data
foreach($newItems as $k => $v) {
if(!is_string($k) || is_numeric($k) || !is_string($v)) {
user_error("$class::\$many_many has a bad entry: "
user_error("$class::\$many_many has a bad entry: "
. var_export($k,true). " => " . var_export($v,true) . ". Each map key should be a"
. " relationship name, and the map value should be the data class to join to.", E_USER_ERROR);
}
}
$items = isset($items) ? array_merge($newItems, $items) : $newItems;
$newItems = (array)Config::inst()->get($class, 'belongs_many_many', Config::UNINHERITED);
// Validate the data
foreach($newItems as $k => $v) {
if(!is_string($k) || is_numeric($k) || !is_string($v)) {
user_error("$class::\$belongs_many_many has a bad entry: "
user_error("$class::\$belongs_many_many has a bad entry: "
. var_export($k,true). " => " . var_export($v,true) . ". Each map key should be a"
. " relationship name, and the map value should be the data class to join to.", E_USER_ERROR);
}
@ -1907,20 +1907,20 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
$items = isset($items) ? array_merge($newItems, $items) : $newItems;
}
}
return isset($items) ? $items : null;
}
/**
* This returns an array (if it exists) describing the database extensions that are required, or false if none
*
*
* This is experimental, and is currently only a Postgres-specific enhancement.
*
*
* @return array or false
*/
public function database_extensions($class){
$extensions = Config::inst()->get($class, 'database_extensions', Config::UNINHERITED);
if($extensions)
return $extensions;
else
@ -1935,12 +1935,12 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
*/
public function getDefaultSearchContext() {
return new SearchContext(
$this->class,
$this->scaffoldSearchFields(),
$this->class,
$this->scaffoldSearchFields(),
$this->defaultSearchFilters()
);
}
/**
* Determine which properties on the DataObject are
* searchable, and map them to their default {@link FormField}
@ -1950,7 +1950,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* how generic or specific the field type is.
*
* Used by {@link SearchContext}.
*
*
* @param array $_params
* 'fieldClasses': Associative array of field names as keys and FormField classes as values
* 'restrictFields': Numeric array of a field name whitelist
@ -1967,7 +1967,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
$fields = new FieldList();
foreach($this->searchableFields() as $fieldName => $spec) {
if($params['restrictFields'] && !in_array($fieldName, $params['restrictFields'])) continue;
// If a custom fieldclass is provided as a string, use it
if($params['fieldClasses'] && isset($params['fieldClasses'][$fieldName])) {
$fieldClass = $params['fieldClasses'][$fieldName];
@ -1978,17 +1978,17 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
if(is_string($spec['field'])) {
$fieldClass = $spec['field'];
$field = new $fieldClass($fieldName);
// If it's a FormField object, then just use that object directly.
} else if($spec['field'] instanceof FormField) {
$field = $spec['field'];
// Otherwise we have a bug
} else {
user_error("Bad value for searchable_fields, 'field' value: "
. var_export($spec['field'], true), E_USER_WARNING);
}
// Otherwise, use the database field's scaffolder
} else {
$field = $this->relObject($fieldName)->scaffoldSearchField();
@ -2010,7 +2010,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* Field labels/titles will be auto generated from {@link DataObject::fieldLabels()}.
*
* @uses FormScaffolder
*
*
* @param array $_params Associative array passing through properties to {@link FormScaffolder}.
* @return FieldList
*/
@ -2025,27 +2025,27 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
),
(array)$_params
);
$fs = new FormScaffolder($this);
$fs->tabbed = $params['tabbed'];
$fs->includeRelations = $params['includeRelations'];
$fs->restrictFields = $params['restrictFields'];
$fs->fieldClasses = $params['fieldClasses'];
$fs->ajaxSafe = $params['ajaxSafe'];
return $fs->getFieldList();
}
/**
* Allows user code to hook into DataObject::getCMSFields prior to updateCMSFields
* being called on extensions
*
*
* @param callable $callback The callback to execute
*/
protected function beforeUpdateCMSFields($callback) {
$this->beforeExtending('updateCMSFields', $callback);
}
/**
* Centerpiece of every data administration interface in Silverstripe,
* which returns a {@link FieldList} suitable for a {@link Form} object.
@ -2076,16 +2076,16 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
'tabbed' => true,
'ajaxSafe' => true
));
$this->extend('updateCMSFields', $tabbedFields);
return $tabbedFields;
}
/**
* need to be overload by solid dataobject, so that the customised actions of that dataobject,
* including that dataobject's extensions customised actions could be added to the EditForm.
*
*
* @return an Empty FieldList(); need to be overload by solid subclass
*/
public function getCMSActions() {
@ -2093,14 +2093,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
$this->extend('updateCMSActions', $actions);
return $actions;
}
/**
* Used for simple frontend forms without relation editing
* or {@link TabSet} behaviour. Uses {@link scaffoldFormFields()}
* by default. To customize, either overload this method in your
* subclass, or extend it by {@link DataExtension->updateFrontEndFields()}.
*
*
* @todo Decide on naming for "website|frontend|site|page" and stick with it in the API
*
* @param array $params See {@link scaffoldFormFields()}
@ -2109,7 +2109,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
public function getFrontEndFields($params = null) {
$untabbedFields = $this->scaffoldFormFields($params);
$this->extend('updateFrontEndFields', $untabbedFields);
return $untabbedFields;
}
@ -2148,7 +2148,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
// or a valid record has been loaded from the database
$value = (isset($this->record[$field])) ? $this->record[$field] : null;
if($value || $this->exists()) $fieldObj->setValue($value, $this->record, false);
$this->record[$field] = $fieldObj;
return $this->record[$field];
@ -2178,7 +2178,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
}
$dataQuery = new DataQuery($tableClass);
// Reset query parameter context to that of this DataObject
if($params = $this->getSourceQueryParams()) {
foreach($params as $key => $value) $dataQuery->setQueryParam($key, $value);
@ -2235,7 +2235,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
/**
* Return the fields that have changed.
*
*
* The change level affects what the functions defines as "changed":
* - Level 1 will return strict changes, even !== ones.
* - Level 2 is more lenient, it will only return real data changes, for example a change from 0 to null
@ -2254,14 +2254,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
*/
public function getChangedFields($databaseFieldsOnly = false, $changeLevel = 1) {
$changedFields = array();
// Update the changed array with references to changed obj-fields
foreach($this->record as $k => $v) {
if(is_object($v) && method_exists($v, 'isChanged') && $v->isChanged()) {
$this->changed[$k] = 2;
}
}
if($databaseFieldsOnly) {
$databaseFields = $this->inheritedDatabaseFields();
$databaseFields['ID'] = true;
@ -2281,7 +2281,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
}
}
}
if($fields) foreach($fields as $name => $level) {
$changedFields[$name] = array(
'before' => array_key_exists($name, $this->original) ? $this->original[$name] : null,
@ -2292,11 +2292,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
return $changedFields;
}
/**
* Uses {@link getChangedFields()} to determine if fields have been changed
* since loading them from the database.
*
*
* @param string $fieldName Name of the database field to check, will check for any if not given
* @param int $changeLevel See {@link getChangedFields()}
* @return boolean
@ -2305,7 +2305,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
$changed = $this->getChangedFields(false, $changeLevel);
if(!isset($fieldName)) {
return !empty($changed);
}
}
else {
return array_key_exists($fieldName, $changed);
}
@ -2340,14 +2340,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
if(is_object($val) && $this->db($fieldName)) {
user_error('DataObject::setField: passed an object that is not a DBField', E_USER_WARNING);
}
// if a field is not existing or has strictly changed
if(!isset($this->record[$fieldName]) || $this->record[$fieldName] !== $val) {
// TODO Add check for php-level defaults which are not set in the db
// TODO Add check for hidden input-fields (readonly) which are not set in the db
// At the very least, the type has changed
$this->changed[$fieldName] = 1;
if((!isset($this->record[$fieldName]) && $val) || (isset($this->record[$fieldName])
&& $this->record[$fieldName] != $val)) {
@ -2393,8 +2393,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
}
/**
* Returns true if the given field exists in a database column on any of
* the objects tables and optionally look up a dynamic getter with
* Returns true if the given field exists in a database column on any of
* the objects tables and optionally look up a dynamic getter with
* get<fieldName>().
*
* @param string $field Name of the field
@ -2421,7 +2421,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
return array_key_exists($field, $this->inheritedDatabaseFields());
}
/**
* Returns the field type of the given field, if it belongs to this class, and not a parent.
* Note that the field type will not include constructor arguments in round brackets, only the classname.
@ -2437,17 +2437,18 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
if($field == "Created" && get_parent_class($this) == "DataObject") return "SS_Datetime";
// Add fields from Versioned extension
if($field == 'Version' && $this->hasExtension('Versioned')) {
if($field == 'Version' && $this->hasExtension('Versioned')) {
return 'Int';
}
// get cached fieldmap
$fieldMap = isset(DataObject::$cache_has_own_table_field[$this->class])
? DataObject::$cache_has_own_table_field[$this->class] : null;
$lClass = strtolower($this->class);
$fieldMap = isset(DataObject::$cache_has_own_table_field[$lClass])
? DataObject::$cache_has_own_table_field[$lClass] : null;
// if no fieldmap is cached, get all fields
if(!$fieldMap) {
$fieldMap = Config::inst()->get($this->class, 'db', Config::UNINHERITED);
// all $db fields on this specific class (no parents)
foreach(self::composite_fields($this->class, false) as $fieldname => $fieldtype) {
$combined_db = singleton($fieldtype)->compositeDatabaseFields();
@ -2455,7 +2456,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
$fieldMap[$fieldname.$name] = $type;
}
}
// all has_one relations on this specific class,
// add foreign key
$hasOne = Config::inst()->get($this->class, 'has_one', Config::UNINHERITED);
@ -2464,7 +2465,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
}
// set cached fieldmap
DataObject::$cache_has_own_table_field[$this->class] = $fieldMap;
DataObject::$cache_has_own_table_field[$lClass] = $fieldMap;
}
// Remove string-based "constructor-arguments" from the DBField definition
@ -2473,7 +2474,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
else return $fieldMap[$field]['type'];
}
}
/**
* Returns true if given class has its own table. Uses the rules for whether the table should exist rather than
* actually looking in the database.
@ -2482,20 +2483,20 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* @return bool
*/
public static function has_own_table($dataClass) {
if(!is_subclass_of($dataClass,'DataObject')) return false;
if(!isset(DataObject::$cache_has_own_table[$dataClass])) {
if(get_parent_class($dataClass) == 'DataObject') {
DataObject::$cache_has_own_table[$dataClass] = true;
if(!is_subclass_of($dataClass, 'DataObject')) return false;
$lDataClass = strtolower($dataClass);
if(!isset(DataObject::$cache_has_own_table[$lDataClass])) {
if(get_parent_class($dataClass) == 'DataObject' || Config::inst()->get($dataClass, 'db', Config::UNINHERITED)
|| Config::inst()->get($dataClass, 'has_one', Config::UNINHERITED)) {
DataObject::$cache_has_own_table[$lDataClass] = $dataClass;
} else {
DataObject::$cache_has_own_table[$dataClass]
= Config::inst()->get($dataClass, 'db', Config::UNINHERITED)
|| Config::inst()->get($dataClass, 'has_one', Config::UNINHERITED);
DataObject::$cache_has_own_table[$lDataClass] = false;
}
}
return DataObject::$cache_has_own_table[$dataClass];
return (bool)DataObject::$cache_has_own_table[$lDataClass];
}
/**
* Returns true if the member is allowed to do the given action.
* See {@link extendedCan()} for a more versatile tri-state permission control.
@ -2569,11 +2570,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
/**
* Process tri-state responses from permission-alterting extensions. The extensions are
* expected to return one of three values:
*
*
* - false: Disallow this permission, regardless of what other extensions say
* - true: Allow this permission, as long as no other extensions return false
* - NULL: Don't affect the outcome
*
*
* This method itself returns a tri-state value, and is designed to be used like this:
*
* <code>
@ -2581,7 +2582,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* if($extended !== null) return $extended;
* else return $normalValue;
* </code>
*
*
* @param String $methodName Method on the same object, e.g. {@link canEdit()}
* @param Member|int $member
* @return boolean|null
@ -2592,12 +2593,12 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
// Remove NULLs
$results = array_filter($results, function($v) {return !is_null($v);});
// If there are any non-NULL responses, then return the lowest one of them.
// If any explicitly deny the permission, then we don't get access
// If any explicitly deny the permission, then we don't get access
if($results) return min($results);
}
return null;
}
/**
* @param Member $member
* @return boolean
@ -2676,7 +2677,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
// If we have a CompositeDBField object in $this->record, then return that
if(isset($this->record[$fieldName]) && is_object($this->record[$fieldName])) {
return $this->record[$fieldName];
// Special case for ID field
} else if($fieldName == 'ID') {
return new PrimaryKey($fieldName, $this);
@ -2694,7 +2695,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
$obj = Object::create_from_string($helper, $fieldName);
$obj->setValue($this->$fieldName, $this->record, false);
return $obj;
// Special case for has_one relationships
} else if(preg_match('/ID$/', $fieldName) && $this->has_one(substr($fieldName,0,-2))) {
$val = $this->$fieldName;
@ -2705,7 +2706,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
/**
* Traverses to a DBField referenced by relationships between data objects.
*
* The path to the related field is specified with dot separated syntax
* The path to the related field is specified with dot separated syntax
* (eg: Parent.Child.Child.FieldName).
*
* @param string $fieldPath
@ -2767,7 +2768,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
$component = $component->relation($relation);
} elseif($component instanceof DataObject
&& ($dbObject = $component->dbObject($relation))
) {
) {
// Select db object
$component = $dbObject;
} else {
@ -2775,7 +2776,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
}
}
}
// Bail if the component is null
if(!$component) {
return null;
@ -2789,7 +2790,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
/**
* Temporary hack to return an association name, based on class, to get around the mangle
* of having to deal with reverse lookup of relationships to determine autogenerated foreign keys.
*
*
* @return String
*/
public function getReverseAssociation($className) {
@ -2805,7 +2806,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
$has_one = array_flip($this->has_one());
if (array_key_exists($className, $has_one)) return $has_one[$className];
}
return false;
}
@ -2831,12 +2832,12 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
if($callerClass == 'DataObject') {
throw new \InvalidArgumentException('Call <classname>::get() instead of DataObject::get()');
}
if($filter || $sort || $join || $limit || ($containerClass != 'DataList')) {
throw new \InvalidArgumentException('If calling <classname>::get() then you shouldn\'t pass any other'
. ' arguments');
}
$result = DataList::create(get_called_class());
$result->setDataModel(DataModel::inst());
return $result;
@ -2847,7 +2848,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
'The $join argument has been removed. Use leftJoin($table, $joinClause) instead.'
);
}
$result = DataList::create($callerClass)->where($filter)->sort($sort);
if($limit && strpos($limit, ',') !== false) {
@ -2860,7 +2861,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
$result->setDataModel(DataModel::inst());
return $result;
}
/**
* @deprecated 3.1 Use DataList::create and DataList to do your querying
*/
@ -2910,10 +2911,10 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
$cacheKey .= '-' . implode("-", $extra);
}
$cacheKey = md5($cacheKey);
// Flush destroyed items out of the cache
if($cache && isset(DataObject::$_cache_get_one[$callerClass][$cacheKey])
&& DataObject::$_cache_get_one[$callerClass][$cacheKey] instanceof DataObject
if($cache && isset(DataObject::$_cache_get_one[$callerClass][$cacheKey])
&& DataObject::$_cache_get_one[$callerClass][$cacheKey] instanceof DataObject
&& DataObject::$_cache_get_one[$callerClass][$cacheKey]->destroyed) {
DataObject::$_cache_get_one[$callerClass][$cacheKey] = false;
@ -2937,12 +2938,12 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* Also clears any cached aggregate data.
*
* @param boolean $persistent When true will also clear persistent data stored in the Cache system.
* When false will just clear session-local cached data
* When false will just clear session-local cached data
* @return DataObject $this
*/
public function flushCache($persistent = true) {
if($persistent) Aggregate::flushCache($this->class);
if($this->class == 'DataObject') {
DataObject::$_cache_get_one = array();
return $this;
@ -2952,9 +2953,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
foreach($classes as $class) {
if(isset(DataObject::$_cache_get_one[$class])) unset(DataObject::$_cache_get_one[$class]);
}
$this->extend('flushCache');
$this->components = array();
return $this;
}
@ -2970,7 +2971,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
}
DataObject::$_cache_get_one = array();
}
/**
* Reset all global caches associated with DataObject.
*/
@ -3020,7 +3021,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
/**
* @var Array Parameters used in the query that built this object.
* This can be used by decorators (e.g. lazy loading) to
* This can be used by decorators (e.g. lazy loading) to
* run additional queries using the same context.
*/
protected $sourceQueryParams;
@ -3093,7 +3094,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
/**
* Check the database schema and update it as necessary.
*
*
* @uses DataExtension->augmentDatabase()
*/
public function requireTable() {
@ -3129,7 +3130,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
"{$this->class}ID" => true,
(($this->class == $childClass) ? "ChildID" : "{$childClass}ID") => true,
);
DB::requireTable("{$this->class}_$relationship", $manymanyFields, $manymanyIndexes, true, null,
$extensions);
}
@ -3144,7 +3145,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* database is built, after the database tables have all been created. Overload
* this to add default records when the database is built, but make sure you
* call parent::requireDefaultRecords().
*
*
* @uses DataExtension->requireDefaultRecords()
*/
public function requireDefaultRecords() {
@ -3161,11 +3162,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
DB::alteration_message("Added default records to $className table","created");
}
}
// Let any extentions make their own database default data
$this->extend('requireDefaultRecords', $dummy);
}
/**
* Returns fields bu traversing the class heirachy in a bottom-up direction.
*
@ -3179,18 +3180,18 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
public function inheritedDatabaseFields() {
$fields = array();
$currentObj = $this->class;
while($currentObj != 'DataObject') {
$fields = array_merge($fields, self::custom_database_fields($currentObj));
$currentObj = get_parent_class($currentObj);
}
return (array) $fields;
}
/**
* Get the default searchable fields for this object, as defined in the
* $searchable_fields list. If searchable fields are not defined on the
* Get the default searchable fields for this object, as defined in the
* $searchable_fields list. If searchable fields are not defined on the
* data object, uses a default selection of summary fields.
*
* @return array
@ -3199,7 +3200,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
// can have mixed format, need to make consistent in most verbose form
$fields = $this->stat('searchable_fields');
$labels = $this->fieldLabels();
// fallback to summary fields (unless empty array is explicitly specified)
if( ! $fields && ! is_array($fields)) {
$summaryFields = array_keys($this->summaryFields());
@ -3223,7 +3224,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
}
}
}
// we need to make sure the format is unified before
// augmenting fields, so extensions can apply consistent checks
// but also after augmenting fields, because the extension
@ -3263,13 +3264,13 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
}
$fields = $rewrite;
// apply DataExtensions if present
$this->extend('updateSearchableFields', $fields);
return $fields;
}
/**
* Get any user defined searchable fields labels that
* exist. Allows overriding of default field names in the form
@ -3282,23 +3283,23 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* would generally only be set in the case of more complex relationships
* between data object being required in the search interface.
*
* Generates labels based on name of the field itself, if no static property
* Generates labels based on name of the field itself, if no static property
* {@link self::field_labels} exists.
*
* @uses $field_labels
* @uses FormField::name_to_label()
*
* @param boolean $includerelations a boolean value to indicate if the labels returned include relation fields
*
*
* @return array|string Array of all element labels if no argument given, otherwise the label of the field
*/
public function fieldLabels($includerelations = true) {
$cacheKey = $this->class . '_' . $includerelations;
if(!isset(self::$_cache_field_labels[$cacheKey])) {
$customLabels = $this->stat('field_labels');
$autoLabels = array();
// get all translated static properties as defined in i18nCollectStatics()
$ancestry = ClassInfo::ancestry($this->class);
$ancestry = array_reverse($ancestry);
@ -3321,20 +3322,20 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
}
$labels = array_merge((array)$autoLabels, (array)$customLabels);
$this->extend('updateFieldLabels', $labels);
$this->extend('updateFieldLabels', $labels);
self::$_cache_field_labels[$cacheKey] = $labels;
}
return self::$_cache_field_labels[$cacheKey];
}
/**
* Get a human-readable label for a single field,
* see {@link fieldLabels()} for more details.
*
*
* @uses fieldLabels()
* @uses FormField::name_to_label()
*
*
* @param string $name Name of the field
* @return string Label of the field
*/
@ -3368,7 +3369,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
if ($this->hasField('FirstName')) $fields['FirstName'] = 'First Name';
}
$this->extend("updateSummaryFields", $fields);
// Final fail-over, just list ID field
if(!$fields) $fields['ID'] = 'ID';
@ -3400,7 +3401,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
foreach($this->searchableFields() as $name => $spec) {
$filterClass = $spec['filter'];
if($spec['filter'] instanceof SearchFilter) {
$filters[$name] = $spec['filter'];
} else {
@ -3427,8 +3428,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
/*
* @ignore
*/
private static $subclass_access = true;
private static $subclass_access = true;
/**
* Temporarily disable subclass access in data object qeur
*/
@ -3438,7 +3439,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
public static function enable_subclass_access() {
self::$subclass_access = true;
}
//-------------------------------------------------------------------------------------------//
/**
@ -3462,13 +3463,13 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
"Created" => "SS_Datetime",
"Title" => 'Text',
);
/**
* Specify custom options for a CREATE TABLE call.
* Can be used to specify a custom storage engine for specific database table.
* All options have to be keyed for a specific database implementation,
* identified by their class name (extending from {@link SS_Database}).
*
*
* <code>
* array(
* 'MySQLDatabase' => 'ENGINE=MyISAM'
@ -3477,7 +3478,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
*
* Caution: This API is experimental, and might not be
* included in the next major release. Please use with care.
*
*
* @var array
* @config
*/
@ -3489,7 +3490,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* If a field is in this array, then create a database index
* on that field. This is a map from fieldname to index type.
* See {@link SS_Database->requireIndex()} and custom subclasses for details on the array notation.
*
*
* @var array
* @config
*/
@ -3499,11 +3500,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* Inserts standard column-values when a DataObject
* is instanciated. Does not insert default records {@see $default_records}.
* This is a map from fieldname to default value.
*
*
* - If you would like to change a default value in a sub-class, just specify it.
* - If you would like to disable the default value given by a parent class, set the default value to 0,'',
* or false in your subclass. Setting it to null won't work.
*
*
* @var array
* @config
*/
@ -3536,7 +3537,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* @config
*/
private static $has_one = null;
/**
* A meta-relationship that allows you to define the reverse side of a {@link DataObject::$has_one}.
*
@ -3550,7 +3551,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* @config
*/
private static $belongs_to;
/**
* This defines a one-to-many relationship. It is a map of component name to the remote data class.
*
@ -3575,7 +3576,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
/**
* Extra fields to include on the connecting many-many table.
* This is a map from field name to field type.
*
*
* Example code:
* <code>
* public static $many_many_extraFields = array(
@ -3584,7 +3585,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* )
* );
* </code>
*
*
* @var array
* @config
*/
@ -3616,7 +3617,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* "Name" => "PartialMatchFilter"
* );
* </code>
*
*
* Overriding the default form fields, with a custom defined field.
* The 'filter' parameter will be generated from {@link DBField::$default_search_filter_class}.
* The 'title' parameter will be generated from {@link DataObject->fieldLabels()}.
@ -3632,7 +3633,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* <code>
* static $searchable_fields = array(
* "Organisation.ZipCode" => array(
* "field" => "TextField",
* "field" => "TextField",
* "filter" => "PartialMatchFilter",
* "title" => 'Organisation ZIP'
* )
@ -3655,44 +3656,44 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* @config
*/
private static $summary_fields = null;
/**
* Provides a list of allowed methods that can be called via RESTful api.
*/
public static $allowed_actions = null;
/**
* Collect all static properties on the object
* which contain natural language, and need to be translated.
* The full entity name is composed from the class name and a custom identifier.
*
*
* @return array A numerical array which contains one or more entities in array-form.
* Each numeric entity array contains the "arguments" for a _t() call as array values:
* $entity, $string, $priority, $context.
*/
public function provideI18nEntities() {
$entities = array();
$entities["{$this->class}.SINGULARNAME"] = array(
$this->singular_name(),
'Singular name of the object, used in dropdowns and to generally identify a single object in the interface'
);
$entities["{$this->class}.PLURALNAME"] = array(
$this->plural_name(),
'Pural name of the object, used in dropdowns and to generally identify a collection of this object in the'
. ' interface'
);
return $entities;
}
/**
* Returns true if the given method/parameter has a value
* (Uses the DBField::hasValue if the parameter is a database field)
*
*
* @param string $field The field name
* @param array $arguments
* @param bool $cache

View File

@ -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;

View File

@ -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 {
}
/**

View File

@ -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');
}