Version: current

       1 <?php
       2 /**
       3  * A single database record & abstract class for the data-access-model.
       4  *
       5  * <h2>Extensions and Decorators</h2>
       6  *
       7  * See {@link Extension} and {@link DataObjectDecorator}.
       8  *
       9  * <h2>Permission Control</h2>
      10  *
      11  * Object-level access control by {@link Permission}. Permission codes are arbitrary
      12  * strings which can be selected on a group-by-group basis.
      13  *
      14  * <code>
      15  * class Article extends DataObject implements PermissionProvider {
      16  *  static $api_access = true;
      17  *
      18  *  public function canView($member = false) {
      19  *      return Permission::check('ARTICLE_VIEW');
      20  *  }
      21  *  public function canEdit($member = false) {
      22  *      return Permission::check('ARTICLE_EDIT');
      23  *  }
      24  *  public function canDelete() {
      25  *      return Permission::check('ARTICLE_DELETE');
      26  *  }
      27  *  public function canCreate() {
      28  *      return Permission::check('ARTICLE_CREATE');
      29  *  }
      30  *  public function providePermissions() {
      31  *      return array(
      32  *          'ARTICLE_VIEW' => 'Read an article object',
      33  *          'ARTICLE_EDIT' => 'Edit an article object',
      34  *          'ARTICLE_DELETE' => 'Delete an article object',
      35  *          'ARTICLE_CREATE' => 'Create an article object',
      36  *      );
      37  *  }
      38  * }
      39  * </code>
      40  *
      41  * Object-level access control by {@link Group} membership:
      42  * <code>
      43  * class Article extends DataObject {
      44  *  static $api_access = true;
      45  *
      46  *  public function canView($member = false) {
      47  *      if(!$member) $member = Member::currentUser();
      48  *      return $member->inGroup('Subscribers');
      49  *  }
      50  *  public function canEdit($member = false) {
      51  *      if(!$member) $member = Member::currentUser();
      52  *      return $member->inGroup('Editors');
      53  *  }
      54  *
      55  *  // ...
      56  * }
      57  * </code>
      58  *
      59  * If any public method on this class is prefixed with an underscore,
      60  * the results are cached in memory through {@link cachedCall()}.
      61  *
      62  *
      63  * @todo Add instance specific removeExtension() which undos loadExtraStatics()
      64  *  and defineMethods()
      65  *
      66  * @package sapphire
      67  * @subpackage model
      68  */
      69 class DataObject extends ViewableData implements DataObjectInterface, i18nEntityProvider {
      70
      71     /**
      72      * Human-readable singular name.
      73      * @var string
      74      */
      75     public static $singular_name = null;
      76
      77     /**
      78      * Human-readable pluaral name
      79      * @var string
      80      */
      81     public static $plural_name = null;
      82
      83     /**
      84      * Allow API access to this object?
      85      * @todo Define the options that can be set here
      86      */
      87     public static $api_access = false;
      88
      89     public static
      90         $cache_has_own_table       = array(),
      91         $cache_has_own_table_field = array();
      92
      93     /**
      94      * True if this DataObject has been destroyed.
      95      * @var boolean
      96      */
      97     public $destroyed = false;
      98
      99     /**
     100      * Data stored in this objects database record. An array indexed
     101      * by fieldname.
     102      * @var array
     103      */
     104     protected $record;
     105
     106     /**
     107      * An array indexed by fieldname, true if the field has been changed.
     108      * Use {@link getChangedFields()} and {@link isChanged()} to inspect
     109      * the changed state.
     110      *
     111      * @var array
     112      */
     113     private $changed;
     114
     115     /**
     116      * The database record (in the same format as $record), before
     117      * any changes.
     118      * @var array
     119      */
     120     protected $original;
     121
     122     /**
     123      * The one-to-one, one-to-many and many-to-one components
     124      * indexed by component name.
     125      * @var array
     126      */
     127     protected $components;
     128
     129     /**
     130      * Used by onBeforeDelete() to ensure child classes call parent::onBeforeDelete()
     131      * @var boolean
     132      */
     133     protected $brokenOnDelete = false;
     134
     135     /**
     136      * Used by onBeforeWrite() to ensure child classes call parent::onBeforeWrite()
     137      * @var boolean
     138      */
     139     protected $brokenOnWrite = false;
     140
     141     /**
     142      * Should dataobjects be validated before they are written?
     143      */
     144     private static $validation_enabled = true;
     145
     146     /**
     147      * Returns when validation on DataObjects is enabled.
     148      * @return bool
     149      */
     150     static function get_validation_enabled() {
     151         return self::$validation_enabled;
     152     }
     153
     154     /**
     155      * Set whether DataObjects should be validated before they are written.
     156      * @param $enable bool
     157      * @see DataObject::validate()
     158      */
     159     static function set_validation_enabled($enable) {
     160         self::$validation_enabled = (bool) $enable;
     161     }
     162
     163     /**
     164      * Return the complete map of fields on this object, including Created, LastEdited and ClassName
     165      *
     166      * @param string $class
     167      * @return array
     168      */
     169     public static function database_fields($class) {
     170         if(get_parent_class($class) == 'DataObject') {
     171             return array_merge (
     172                 array (
     173                     'ClassName'  => "Enum('" . implode(', ', ClassInfo::subclassesFor($class)) . "')",
     174                     'Created'    => 'SS_Datetime',
     175                     'LastEdited' => 'SS_Datetime'
     176                 ),
     177                 self::custom_database_fields($class)
     178             );
     179         }
     180
     181         return self::custom_database_fields($class);
     182     }
     183
     184     /**
     185      * Get all database columns explicitly defined on a class in {@link DataObject::$db}
     186      * and {@link DataObject::$has_one}. Resolves instances of {@link CompositeDBField}
     187      * into the actual database fields, rather than the name of the field which
     188      * might not equate a database column.
     189      *
     190      * @uses CompositeDBField->compositeDatabaseFields()
     191      *
     192      * @param string $class
     193      * @return array Map of fieldname to specification, similiar to {@link DataObject::$db}.
     194      */
     195     public static function custom_database_fields($class) {
     196         $fields = Object::uninherited_static($class, 'db');
     197
     198         foreach(self::composite_fields($class, false) as $fieldName => $fieldClass) {
     199             // Remove the original fieldname, its not an actual database column
     200             unset($fields[$fieldName]);
     201
     202             // Add all composite columns
     203             $compositeFields = singleton($fieldClass)->compositeDatabaseFields();
     204             if($compositeFields) foreach($compositeFields as $compositeName => $spec) {
     205                 $fields["{$fieldName}{$compositeName}"] = $spec;
     206             }
     207         }
     208
     209         // Add has_one relationships
     210         $hasOne = Object::uninherited_static($class, 'has_one');
     211         if($hasOne) foreach(array_keys($hasOne) as $field) {
     212             $fields[$field . 'ID'] = 'ForeignKey';
     213         }
     214
     215         return (array)$fields;
     216     }
     217
     218     private static $_cache_custom_database_fields = array();
     219
     220
     221     /**
     222      * Returns the field class if the given db field on the class is a composite field.
     223      * Will check all applicable ancestor classes and aggregate results.
     224      */
     225     static function is_composite_field($class, $name, $aggregated = true) {
     226         if(!isset(self::$_cache_composite_fields[$class])) self::cache_composite_fields($class);
     227
     228         if(isset(self::$_cache_composite_fields[$class][$name])) {
     229             return self::$_cache_composite_fields[$class][$name];
     230
     231         } else if($aggregated && $class != 'DataObject' && ($parentClass=get_parent_class($class)) != 'DataObject') {
     232             return self::is_composite_field($parentClass, $name);
     233         }
     234     }
     235
     236     /**
     237      * Returns a list of all the composite if the given db field on the class is a composite field.
     238      * Will check all applicable ancestor classes and aggregate results.
     239      */
     240     static function composite_fields($class, $aggregated = true) {
     241         if(!isset(self::$_cache_composite_fields[$class])) self::cache_composite_fields($class);
     242
     243         $compositeFields = self::$_cache_composite_fields[$class];
     244
     245         if($aggregated && $class != 'DataObject' && ($parentClass=get_parent_class($class)) != 'DataObject') {
     246             $compositeFields = array_merge($compositeFields,
     247                 self::composite_fields($parentClass));
     248         }
     249
     250         return $compositeFields;
     251     }
     252
     253     /**
     254      * Internal cacher for the composite field information
     255      */
     256     private static function cache_composite_fields($class) {
     257         $compositeFields = array();
     258
     259         $fields = Object::uninherited_static($class, 'db');
     260         if($fields) foreach($fields as $fieldName => $fieldClass) {
     261             // Strip off any parameters
     262             $bPos = strpos('(', $fieldClass);
     263             if($bPos !== FALSE) $fieldClass = substr(0,$bPos, $fieldClass);
     264
     265             // Test to see if it implements CompositeDBField
     266             if(ClassInfo::classImplements($fieldClass, 'CompositeDBField')) {
     267                 $compositeFields[$fieldName] = $fieldClass;
     268             }
     269         }
     270
     271         self::$_cache_composite_fields[$class] = $compositeFields;
     272     }
     273
     274     private static $_cache_composite_fields = array();
     275
     276
     277     /**
     278      * This controls whether of not extendCMSFields() is called by getCMSFields.
     279      */
     280     private static $runCMSFieldsExtensions = true;
     281
     282     /**
     283      * Stops extendCMSFields() being called on getCMSFields().
     284      * This is useful when you need access to fields added by subclasses
     285      * of SiteTree in a decorator. Call before calling parent::getCMSFields(),
     286      * and reenable afterwards.
     287      */
     288     public static function disableCMSFieldsExtensions() {
     289         self::$runCMSFieldsExtensions = false;
     290     }
     291
     292     /**
     293      * Reenables extendCMSFields() being called on getCMSFields() after
     294      * it has been disabled by disableCMSFieldsExtensions().
     295      */
     296     public static function enableCMSFieldsExtensions() {
     297         self::$runCMSFieldsExtensions = true;
     298     }
     299
     300     /**
     301      * Construct a new DataObject.
     302      *
     303      * @param array|null $record This will be null for a new database record.  Alternatively, you can pass an array of
     304      * field values.  Normally this contructor is only used by the internal systems that get objects from the database.
     305      * @param boolean $isSingleton This this to true if this is a singleton() object, a stub for calling methods.  Singletons
     306      * don't have their defaults set.
     307      */
     308     function __construct($record = null, $isSingleton = false) {
     309         // Set the fields data.
     310         if(!$record) {
     311             $record = array(
     312                 'ID' => 0,
     313                 'ClassName' => get_class($this),
     314                 'RecordClassName' => get_class($this)
     315             );
     316         }
     317
     318         if(!is_array($record)) {
     319             if(is_object($record)) $passed = "an object of type '$record->class'";
     320             else $passed = "The value '$record'";
     321
     322             user_error("DataObject::__construct passed $passed.  It's supposed to be passed an array,
     323             taken straight from the database.  Perhaps you should use DataObject::get_one instead?", E_USER_WARNING);
     324             $record = null;
     325         }
     326
     327         // Convert PostgreSQL boolean values
     328         // TODO: Implement this more elegantly, for example by writing a more intelligent SQL SELECT query prior to object construction
     329         if(DB::getConn() instanceof PostgreSQLDatabase) {
     330             $this->class = get_class($this);
     331             foreach($record as $k => $v) {
     332                 if($this->db($k) == 'Boolean' && $v == 'f') $record[$k] = '0';
     333             }
     334         }
     335
     336         // TODO: MSSQL has a convert function that can do this on the SQL end. We just need a
     337         // nice way of telling the database how we want to get the value out on a per-fieldtype basis
     338         if(DB::getConn() instanceof MSSQLDatabase) {
     339             $this->class = get_class($this);
     340             foreach($record as $k => $v) {
     341                 if($v) {
     342                     if($k == 'Created' || $k == 'LastEdited') {
     343                         $fieldtype = 'SS_Datetime';
     344                     } else {
     345                         $fieldtype = $this->db($k);
     346                     }
     347
     348                     // MSSQLDatabase::date() uses datetime for the data type for "Date" and "SS_Datetime"
     349                     switch($fieldtype) {
     350                         case "Date":
     351                             $v = preg_replace('/:[0-9][0-9][0-9]([ap]m)$/i', ' \\1', $v);
     352                             $record[$k] = date('Y-m-d', strtotime($v));
     353                             break;
     354
     355                         case "Datetime":
     356                         case "SS_Datetime":
     357                             $v = preg_replace('/:[0-9][0-9][0-9]([ap]m)$/i', ' \\1', $v);
     358                             $record[$k] = date('Y-m-d H:i:s', strtotime($v));
     359                             break;
     360                     }
     361                 }
     362             }
     363         }
     364
     365         // Set $this->record to $record, but ignore NULLs
     366         $this->record = array();
     367         foreach($record as $k => $v) {
     368             // Ensure that ID is stored as a number and not a string
     369             // To do: this kind of clean-up should be done on all numeric fields, in some relatively
     370             // performant manner
     371             if($v !== null) {
     372                 if($k == 'ID' && is_numeric($v)) $this->record[$k] = (int)$v;
     373                 else $this->record[$k] = $v;
     374             }
     375         }
     376         $this->original = $this->record;
     377
     378         // Keep track of the modification date of all the data sourced to make this page
     379         // From this we create a Last-Modified HTTP header
     380         if(isset($record['LastEdited'])) {
     381             HTTP::register_modification_date($record['LastEdited']);
     382         }
     383
     384         parent::__construct();
     385
     386         // Must be called after parent constructor
     387         if(!$isSingleton && (!isset($this->record['ID']) || !$this->record['ID'])) {
     388             $this->populateDefaults();
     389         }
     390
     391         // prevent populateDefaults() and setField() from marking overwritten defaults as changed
     392         $this->changed = array();
     393     }
     394
     395     /**
     396      * Destroy all of this objects dependant objects.
     397      * You'll need to call this to get the memory of an object that has components or extensions freed.
     398      */
     399     function destroy() {
     400         $this->extension_instances = null;
     401         $this->components = null;
     402         $this->destroyed = true;
     403         $this->record = null;
     404         $this->original = null;
     405         $this->changed = null;
     406         $this->flushCache(false);
     407     }
     408
     409     /**
     410      * Create a duplicate of this node.
     411      * Caution: Doesn't duplicate relations.
     412      *
     413      * @param $doWrite Perform a write() operation before returning the object.  If this is true, it will create the duplicate in the database.
     414      * @return DataObject A duplicate of this node. The exact type will be the type of this node.
     415      */
     416     function duplicate($doWrite = true) {
     417         $className = $this->class;
     418         $clone = new $className( $this->record );
     419         $clone->ID = 0;
     420         if($doWrite) $clone->write();
     421         return $clone;
     422     }
     423
     424     /**
     425      * Set the ClassName attribute. {@link $class} is also updated.
     426      * Warning: This will produce an inconsistent record, as the object
     427      * instance will not automatically switch to the new subclass.
     428      * Please use {@link newClassInstance()} for this purpose,
     429      * or destroy and reinstanciate the record.
     430      *
     431      * @param string $className The new ClassName attribute (a subclass of {@link DataObject})
     432      */
     433     function setClassName($className) {
     434         $className = trim($className);
     435         if(!$className || !is_subclass_of($className, 'DataObject')) return;
     436
     437         $this->class = $className;
     438         $this->setField("ClassName", $className);
     439     }
     440
     441     /**
     442      * Create a new instance of a different class from this object's record.
     443      * This is useful when dynamically changing the type of an instance. Specifically,
     444      * it ensures that the instance of the class is a match for the className of the
     445      * record. Don't set the {@link DataObject->class} or {@link DataObject->ClassName}
     446      * property manually before calling this method, as it will confuse change detection.
     447      *
     448      * If the new class is different to the original class, defaults are populated again
     449      * because this will only occur automatically on instantiation of a DataObject if
     450      * there is no record, or the record has no ID. In this case, we do have an ID but
     451      * we still need to repopulate the defaults.
     452      *
     453      * @param string $newClassName The name of the new class
     454      *
     455      * @return DataObject The new instance of the new class, The exact type will be of the class name provided.
     456      */
     457     function newClassInstance($newClassName) {
     458         $originalClass = $this->ClassName;
     459         $newInstance = new $newClassName(array_merge(
     460             $this->record,
     461             array(
     462                 'ClassName' => $originalClass,
     463                 'RecordClassName' => $originalClass,
     464             )
     465         ));
     466
     467         if($newClassName != $originalClass) {
     468             $newInstance->setClassName($newClassName);
     469             $newInstance->populateDefaults();
     470             $newInstance->forceChange();
     471         }
     472
     473         return $newInstance;
     474     }
     475
     476     /**
     477      * Adds methods from the extensions.
     478      * Called by Object::__construct() once per class.
     479      */
     480     function defineMethods() {
     481         parent::defineMethods();
     482
     483         // Define the extra db fields - this is only necessary for extensions added in the
     484         // class definition.  Object::add_extension() will call this at definition time for
     485         // those objects, which is a better mechanism.  Perhaps extensions defined inside the
     486         // class def can somehow be applied at definiton time also?
     487         if($this->extension_instances) foreach($this->extension_instances as $i => $instance) {
     488             if(!$instance->class) {
     489                 $class = get_class($instance);
     490                 user_error("DataObject::defineMethods(): Please ensure {$class}::__construct() calls parent::__construct()", E_USER_ERROR);
     491             }
     492         }
     493
     494         if($this->class == 'DataObject') return;
     495
     496         // Set up accessors for joined items
     497         if($manyMany = $this->many_many()) {
     498             foreach($manyMany as $relationship => $class) {
     499                 $this->addWrapperMethod($relationship, 'getManyManyComponents');
     500             }
     501         }
     502         if($hasMany = $this->has_many()) {
     503
     504             foreach($hasMany as $relationship => $class) {
     505                 $this->addWrapperMethod($relationship, 'getComponents');
     506             }
     507
     508         }
     509         if($hasOne = $this->has_one()) {
     510             foreach($hasOne as $relationship => $class) {
     511                 $this->addWrapperMethod($relationship, 'getComponent');
     512             }
     513         }
     514         if($belongsTo = $this->belongs_to()) foreach(array_keys($belongsTo) as $relationship) {
     515             $this->addWrapperMethod($relationship, 'getComponent');
     516         }
     517     }
     518
     519     /**
     520      * Returns true if this object "exists", i.e., has a sensible value.
     521      * The default behaviour for a DataObject is to return true if
     522      * the object exists in the database, you can override this in subclasses.
     523      *
     524      * @return boolean true if this object exists
     525      */
     526     public function exists() {
     527         return ($this->record && $this->record['ID'] > 0);
     528     }
     529
     530     public function isEmpty(){
     531         $isEmpty = true;
     532         if($this->record){
     533             foreach($this->record as $k=>$v){
     534                 if($k != "ID"){
     535                     $isEmpty = $isEmpty && !$v;
     536                 }
     537             }
     538         }
     539         return $isEmpty;
     540     }
     541
     542     /**
     543      * Get the user friendly singular name of this DataObject.
     544      * If the name is not defined (by redefining $singular_name in the subclass),
     545      * this returns the class name.
     546      *
     547      * @return string User friendly singular name of this DataObject
     548      */
     549     function singular_name() {
     550         if(!$name = $this->stat('singular_name')) {
     551             $name = ucwords(trim(strtolower(preg_replace('/_?([A-Z])/', ' $1', $this->class))));
     552         }
     553
     554         return $name;
     555     }
     556
     557     /**
     558      * Get the translated user friendly singular name of this DataObject
     559      * same as singular_name() but runs it through the translating function
     560      *
     561      * Translating string is in the form:
     562      *     $this->class.SINGULARNAME
     563      * Example:
     564      *     Page.SINGULARNAME
     565      *
     566      * @return string User friendly translated singular name of this DataObject
     567      */
     568     function i18n_singular_name() {
     569         return _t($this->class.'.SINGULARNAME', $this->singular_name());
     570     }
     571
     572     /**
     573      * Get the user friendly plural name of this DataObject
     574      * If the name is not defined (by renaming $plural_name in the subclass),
     575      * this returns a pluralised version of the class name.
     576      *
     577      * @return string User friendly plural name of this DataObject
     578      */
     579     function plural_name() {
     580         if($name = $this->stat('plural_name')) {
     581             return $name;
     582         } else {
     583             $name = $this->singular_name();
     584             if(substr($name,-1) == 'e') $name = substr($name,0,-1);
     585             else if(substr($name,-1) == 'y') $name = substr($name,0,-1) . 'ie';
     586
     587             return ucfirst($name . 's');
     588         }
     589     }
     590
     591     /**
     592      * Get the translated user friendly plural name of this DataObject
     593      * Same as plural_name but runs it through the translation function
     594      * Translation string is in the form:
     595      *      $this->class.PLURALNAME
     596      * Example:
     597      *      Page.PLURALNAME
     598      *
     599      * @return string User friendly translated plural name of this DataObject
     600      */
     601     function i18n_plural_name()
     602     {
     603         $name = $this->plural_name();
     604         return _t($this->class.'.PLURALNAME', $name);
     605     }
     606
     607     /**
     608      * Standard implementation of a title/label for a specific
     609      * record. Tries to find properties 'Title' or 'Name',
     610      * and falls back to the 'ID'. Useful to provide
     611      * user-friendly identification of a record, e.g. in errormessages
     612      * or UI-selections.
     613      *
     614      * Overload this method to have a more specialized implementation,
     615      * e.g. for an Address record this could be:
     616      * <code>
     617      * public function getTitle() {
     618      *   return "{$this->StreetNumber} {$this->StreetName} {$this->City}";
     619      * }
     620      * </code>
     621      *
     622      * @return string
     623      */
     624     public function getTitle() {
     625         if($this->hasDatabaseField('Title')) return $this->getField('Title');
     626         if($this->hasDatabaseField('Name')) return $this->getField('Name');
     627
     628         return "#{$this->ID}";
     629     }
     630
     631     /**
     632      * Returns the associated database record - in this case, the object itself.
     633      * This is included so that you can call $dataOrController->data() and get a DataObject all the time.
     634      *
     635      * @return DataObject Associated database record
     636      */
     637     public function data() {
     638         return $this;
     639     }
     640
     641     /**
     642      * Convert this object to a map.
     643      *
     644      * @return array The data as a map.
     645      */
     646     public function toMap() {
     647         return $this->record;
     648     }
     649
     650     /**
     651      * Update a number of fields on this object, given a map of the desired changes.
     652      *
     653      * The field names can be simple names, or you can use a dot syntax to access $has_one relations.
     654      * For example, array("Author.FirstName" => "Jim") will set $this->Author()->FirstName to "Jim".
     655      *
     656      * update() doesn't write the main object, but if you use the dot syntax, it will write()
     657      * the related objects that it alters.
     658      *
     659      * @param array $data A map of field name to data values to update.
     660      */
     661     public function update($data) {
     662         foreach($data as $k => $v) {
     663             // Implement dot syntax for updates
     664             if(strpos($k,'.') !== false) {
     665                 $relations = explode('.', $k);
     666                 $fieldName = array_pop($relations);
     667                 $relObj = $this;
     668                 foreach($relations as $i=>$relation) {
     669                     // no support for has_many or many_many relationships,
     670                     // as the updater wouldn't know which object to write to (or create)
     671                     if($relObj->$relation() instanceof DataObject) {
     672                         $relObj = $relObj->$relation();
     673
     674                         // If the intermediate relationship objects have been created, then write them
     675                         if($i<sizeof($relation)-1 && !$relObj->ID) $relObj->write();
     676                     } else {
     677                         user_error(
     678                             "DataObject::update(): Can't traverse relationship '$relation'," .
     679                             "it has to be a has_one relationship or return a single DataObject",
     680                             E_USER_NOTICE
     681                         );
     682                         // unset relation object so we don't write properties to the wrong object
     683                         unset($relObj);
     684                         break;
     685                     }
     686                 }
     687
     688                 if($relObj) {
     689                     $relObj->$fieldName = $v;
     690                     $relObj->write();
     691                     $relObj->flushCache();
     692                 } else {
     693                     user_error("Couldn't follow dot syntax '$k' on '$this->class' object", E_USER_WARNING);
     694                 }
     695             } else {
     696                 $this->$k = $v;
     697             }
     698         }
     699     }
     700
     701     /**
     702      * Pass changes as a map, and try to
     703      * get automatic casting for these fields.
     704      * Doesn't write to the database. To write the data,
     705      * use the write() method.
     706      *
     707      * @param array $data A map of field name to data values to update.
     708      */
     709     public function castedUpdate($data) {
     710         foreach($data as $k => $v) {
     711             $this->setCastedField($k,$v);
     712         }
     713     }
     714
     715     /**
     716      * Merges data and relations from another object of same class,
     717      * without conflict resolution. Allows to specify which
     718      * dataset takes priority in case its not empty.
     719      * has_one-relations are just transferred with priority 'right'.
     720      * has_many and many_many-relations are added regardless of priority.
     721      *
     722      * Caution: has_many/many_many relations are moved rather than duplicated,
     723      * meaning they are not connected to the merged object any longer.
     724      * Caution: Just saves updated has_many/many_many relations to the database,
     725      * doesn't write the updated object itself (just writes the object-properties).
     726      * Caution: Does not delete the merged object.
     727      * Caution: Does now overwrite Created date on the original object.
     728      *
     729      * @param $obj DataObject
     730      * @param $priority String left|right Determines who wins in case of a conflict (optional)
     731      * @param $includeRelations Boolean Merge any existing relations (optional)
     732      * @param $overwriteWithEmpty Boolean Overwrite existing left values with empty right values.
     733      *  Only applicable with $priority='right'. (optional)
     734      * @return Boolean
     735      */
     736     public function merge($rightObj, $priority = 'right', $includeRelations = true, $overwriteWithEmpty = false) {
     737         $leftObj = $this;
     738
     739         if($leftObj->ClassName != $rightObj->ClassName) {
     740             // we can't merge similiar subclasses because they might have additional relations
     741             user_error("DataObject->merge(): Invalid object class '{$rightObj->ClassName}'
     742             (expected '{$leftObj->ClassName}').", E_USER_WARNING);
     743             return false;
     744         }
     745
     746         if(!$rightObj->ID) {
     747             user_error("DataObject->merge(): Please write your merged-in object to the database before merging,
     748                 to make sure all relations are transferred properly.').", E_USER_WARNING);
     749             return false;
     750         }
     751
     752         // makes sure we don't merge data like ID or ClassName
     753         $leftData = $leftObj->inheritedDatabaseFields();
     754         $rightData = $rightObj->inheritedDatabaseFields();
     755
     756         foreach($rightData as $key=>$rightVal) {
     757             // don't merge conflicting values if priority is 'left'
     758             if($priority == 'left' && $leftObj->{$key} !== $rightObj->{$key}) continue;
     759
     760             // don't overwrite existing left values with empty right values (if $overwriteWithEmpty is set)
     761             if($priority == 'right' && !$overwriteWithEmpty && empty($rightObj->{$key})) continue;
     762
     763             // TODO remove redundant merge of has_one fields
     764             $leftObj->{$key} = $rightObj->{$key};
     765         }
     766
     767         // merge relations
     768         if($includeRelations) {
     769             if($manyMany = $this->many_many()) {
     770                 foreach($manyMany as $relationship => $class) {
     771                     $leftComponents = $leftObj->getManyManyComponents($relationship);
     772                     $rightComponents = $rightObj->getManyManyComponents($relationship);
     773                     if($rightComponents && $rightComponents->exists()) $leftComponents->addMany($rightComponents->column('ID'));
     774                     $leftComponents->write();
     775                 }
     776             }
     777
     778             if($hasMany = $this->has_many()) {
     779                 foreach($hasMany as $relationship => $class) {
     780                     $leftComponents = $leftObj->getComponents($relationship);
     781                     $rightComponents = $rightObj->getComponents($relationship);
     782                     if($rightComponents && $rightComponents->exists()) $leftComponents->addMany($rightComponents->column('ID'));
     783                     $leftComponents->write();
     784                 }
     785
     786             }
     787
     788             if($hasOne = $this->has_one()) {
     789                 foreach($hasOne as $relationship => $class) {
     790                     $leftComponent = $leftObj->getComponent($relationship);
     791                     $rightComponent = $rightObj->getComponent($relationship);
     792                     if($leftComponent->exists() && $rightComponent->exists() && $priority == 'right') {
     793                         $leftObj->{$relationship . 'ID'} = $rightObj->{$relationship . 'ID'};
     794                     }
     795                 }
     796             }
     797         }
     798
     799         return true;
     800     }
     801
     802     /**
     803      * Forces the record to think that all its data has changed.
     804      * Doesn't write to the database. Only sets fields as changed
     805      * if they are not already marked as changed.
     806      */
     807     public function forceChange() {
     808         // $this->record might not contain the blank values so we loop on $this->inheritedDatabaseFields() as well
     809         $fieldNames = array_unique(array_merge(array_keys($this->record), array_keys($this->inheritedDatabaseFields())));
     810
     811         foreach($fieldNames as $fieldName) {
     812             if(!isset($this->changed[$fieldName])) $this->changed[$fieldName] = 1;
     813             // Populate the null values in record so that they actually get written
     814             if(!isset($this->record[$fieldName])) $this->record[$fieldName] = null;
     815         }
     816
     817         // @todo Find better way to allow versioned to write a new version after forceChange
     818         if($this->isChanged('Version')) unset($this->changed['Version']);
     819     }
     820
     821     /**
     822      * Validate the current object.
     823      *
     824      * By default, there is no validation - objects are always valid!  However, you can overload this method in your
     825      * DataObject sub-classes to specify custom validation.
     826      *
     827      * Invalid objects won't be able to be written - a warning will be thrown and no write will occur.  onBeforeWrite()
     828      * and onAfterWrite() won't get called either.
     829      *
     830      * It is expected that you call validate() in your own application to test that an object is valid before attempting
     831      * a write, and respond appropriately if it isnt'.
     832      *
     833      * @return A {@link ValidationResult} object
     834      */
     835     protected function validate() {
     836         return new ValidationResult();
     837     }
     838
     839     /**
     840      * Event handler called before writing to the database.
     841      * You can overload this to clean up or otherwise process data before writing it to the
     842      * database.  Don't forget to call parent::onBeforeWrite(), though!
     843      *
     844      * This called after {@link $this->validate()}, so you can be sure that your data is valid.
     845      *
     846      * @uses DataObjectDecorator->onBeforeWrite()
     847      */
     848     protected function onBeforeWrite() {
     849         $this->brokenOnWrite = false;
     850
     851         $dummy = null;
     852         $this->extend('onBeforeWrite', $dummy);
     853     }
     854
     855     /**
     856      * Event handler called after writing to the database.
     857      * You can overload this to act upon changes made to the data after it is written.
     858      * $this->changed will have a record
     859      * database.  Don't forget to call parent::onAfterWrite(), though!
     860      *
     861      * @uses DataObjectDecorator->onAfterWrite()
     862      */
     863     protected function onAfterWrite() {
     864         $dummy = null;
     865         $this->extend('onAfterWrite', $dummy);
     866     }
     867
     868     /**
     869      * Event handler called before deleting from the database.
     870      * You can overload this to clean up or otherwise process data before delete this
     871      * record.  Don't forget to call parent::onBeforeDelete(), though!
     872      *
     873      * @uses DataObjectDecorator->onBeforeDelete()
     874      */
     875     protected function onBeforeDelete() {
     876         $this->brokenOnDelete = false;
     877
     878         $dummy = null;
     879         $this->extend('onBeforeDelete', $dummy);
     880     }
     881
     882     protected function onAfterDelete() {
     883         $this->extend('onAfterDelete');
     884     }
     885
     886     /**
     887      * Load the default values in from the self::$defaults array.
     888      * Will traverse the defaults of the current class and all its parent classes.
     889      * Called by the constructor when creating new records.
     890      *
     891      *  @uses DataObjectDecorator->populateDefaults()
     892      */
     893     public function populateDefaults() {
     894         $classes = array_reverse(ClassInfo::ancestry($this));
     895
     896         foreach($classes as $class) {
     897             $defaults = Object::uninherited_static($class, 'defaults');
     898
     899             if($defaults && !is_array($defaults)) {
     900                 user_error("Bad '$this->class' defaults given: " . var_export($defaults, true),
     901                     E_USER_WARNING);
     902                 $defaults = null;
     903             }
     904
     905             if($defaults) foreach($defaults as $fieldName => $fieldValue) {
     906                 // SRM 2007-03-06: Stricter check
     907                 if(!isset($this->$fieldName) || $this->$fieldName === null) {
     908                     $this->$fieldName = $fieldValue;
     909                 }
     910                 // Set many-many defaults with an array of ids
     911                 if(is_array($fieldValue) && $this->many_many($fieldName)) {
     912                     $manyManyJoin = $this->$fieldName();
     913                     $manyManyJoin->setByIdList($fieldValue);
     914                 }
     915             }
     916             if($class == 'DataObject') {
     917                 break;
     918             }
     919         }
     920
     921         $this->extend('populateDefaults');
     922     }
     923
     924     /**
     925      * Writes all changes to this object to the database.
     926      *  - It will insert a record whenever ID isn't set, otherwise update.
     927      *  - All relevant tables will be updated.
     928      *  - $this->onBeforeWrite() gets called beforehand.
     929      *  - Extensions such as Versioned will ammend the database-write to ensure that a version is saved.
     930      *  - Calls to {@link DataObjectLog} can be used to see everything that's been changed.
     931      *
     932      *  @uses DataObjectDecorator->augmentWrite()
     933      *
     934      * @param boolean $showDebug Show debugging information
     935      * @param boolean $forceInsert Run INSERT command rather than UPDATE, even if record already exists
     936      * @param boolean $forceWrite Write to database even if there are no changes
     937      * @param boolean $writeComponents Call write() on all associated component instances which were previously
     938      *                  retrieved through {@link getComponent()}, {@link getComponents()} or {@link getManyManyComponents()}
     939      *                  (Default: false)
     940      *
     941      * @return int The ID of the record
     942      * @throws ValidationException Exception that can be caught and handled by the calling function
     943      */
     944     public function write($showDebug = false, $forceInsert = false, $forceWrite = false, $writeComponents = false) {
     945         $firstWrite = false;
     946         $this->brokenOnWrite = true;
     947         $isNewRecord = false;
     948
     949         if(self::get_validation_enabled()) {
     950             $valid = $this->validate();
     951             if(!$valid->valid()) {
     952                 // Used by DODs to clean up after themselves, eg, Versioned
     953                 $this->extend('onAfterSkippedWrite');
     954                 throw new ValidationException($valid, "Validation error writing a $this->class object: " . $valid->message() . ".  Object not written.", E_USER_WARNING);
     955                 return false;
     956             }
     957         }
     958
     959         $this->onBeforeWrite();
     960         if($this->brokenOnWrite) {
     961             user_error("$this->class has a broken onBeforeWrite() function.  Make sure that you call parent::onBeforeWrite().", E_USER_ERROR);
     962         }
     963
     964         // New record = everything has changed
     965
     966         if(($this->ID && is_numeric($this->ID)) && !$forceInsert) {
     967             $dbCommand = 'update';
     968
     969             // Update the changed array with references to changed obj-fields
     970             foreach($this->record as $k => $v) {
     971                 if(is_object($v) && method_exists($v, 'isChanged') && $v->isChanged()) {
     972                     $this->changed[$k] = true;
     973                 }
     974             }
     975
     976         } else{
     977             $dbCommand = 'insert';
     978
     979             $this->changed = array();
     980             foreach($this->record as $k => $v) {
     981                 $this->changed[$k] = 2;
     982             }
     983
     984             $firstWrite = true;
     985         }
     986
     987         // No changes made
     988         if($this->changed) {
     989             foreach($this->getClassAncestry() as $ancestor) {
     990                 if(self::has_own_table($ancestor))
     991                 $ancestry[] = $ancestor;
     992             }
     993
     994             // Look for some changes to make
     995             if(!$forceInsert) unset($this->changed['ID']);
     996
     997             $hasChanges = false;
     998             foreach($this->changed as $fieldName => $changed) {
     999                 if($changed) {
    1000                     $hasChanges = true;
    1001                     break;
    1002                 }
    1003             }
    1004
    1005             if($hasChanges || $forceWrite || !$this->record['ID']) {
    1006
    1007                 // New records have their insert into the base data table done first, so that they can pass the
    1008                 // generated primary key on to the rest of the manipulation
    1009                 $baseTable = $ancestry[0];
    1010
    1011                 if((!isset($this->record['ID']) || !$this->record['ID']) && isset($ancestry[0])) {
    1012
    1013                     DB::query("INSERT INTO \"{$baseTable}\" (\"Created\") VALUES (" . DB::getConn()->now() . ")");
    1014                     $this->record['ID'] = DB::getGeneratedID($baseTable);
    1015                     $this->changed['ID'] = 2;
    1016
    1017                     $isNewRecord = true;
    1018                 }
    1019
    1020                 // Divvy up field saving into a number of database manipulations
    1021                 $manipulation = array();
    1022                 if(isset($ancestry) && is_array($ancestry)) {
    1023                     foreach($ancestry as $idx => $class) {
    1024                         $classSingleton = singleton($class);
    1025
    1026                         foreach($this->record as $fieldName => $fieldValue) {
    1027                             if(isset($this->changed[$fieldName]) && $this->changed[$fieldName] && $fieldType = $classSingleton->hasOwnTableDatabaseField($fieldName)) {
    1028                                 $fieldObj = $this->dbObject($fieldName);
    1029                                 if(!isset($manipulation[$class])) $manipulation[$class] = array();
    1030
    1031                                 // if database column doesn't correlate to a DBField instance...
    1032                                 if(!$fieldObj) {
    1033                                     $fieldObj = DBField::create('Varchar', $this->record[$fieldName], $fieldName);
    1034                                 }
    1035
    1036                                 // Both CompositeDBFields and regular fields need to be repopulated
    1037                                 $fieldObj->setValue($this->record[$fieldName], $this->record);
    1038
    1039                                 if($class != $baseTable || $fieldName!='ID')
    1040                                     $fieldObj->writeToManipulation($manipulation[$class]);
    1041                             }
    1042                         }
    1043
    1044                         // Add the class name to the base object
    1045                         if($idx == 0) {
    1046                             $manipulation[$class]['fields']["LastEdited"] = "'".SS_Datetime::now()->Rfc2822()."'";
    1047                             if($dbCommand == 'insert') {
    1048                                 $manipulation[$class]['fields']["Created"] = "'".SS_Datetime::now()->Rfc2822()."'";
    1049                                 //echo "<li>$this->class - " .get_class($this);
    1050                                 $manipulation[$class]['fields']["ClassName"] = "'$this->class'";
    1051                             }
    1052                         }
    1053
    1054                         // In cases where there are no fields, this 'stub' will get picked up on
    1055                         if(self::has_own_table($class)) {
    1056                             $manipulation[$class]['command'] = $dbCommand;
    1057                             $manipulation[$class]['id'] = $this->record['ID'];
    1058                         } else {
    1059                             unset($manipulation[$class]);
    1060                         }
    1061                     }
    1062                 }
    1063                 $this->extend('augmentWrite', $manipulation);
    1064
    1065                 // New records have their insert into the base data table done first, so that they can pass the
    1066                 // generated ID on to the rest of the manipulation
    1067                 if(isset($isNewRecord) && $isNewRecord && isset($manipulation[$baseTable])) {
    1068                     $manipulation[$baseTable]['command'] = 'update';
    1069                 }
    1070
    1071                 DB::manipulate($manipulation);
    1072
    1073                 if(isset($isNewRecord) && $isNewRecord) {
    1074                     DataObjectLog::addedObject($this);
    1075                 } else {
    1076                     DataObjectLog::changedObject($this);
    1077                 }
    1078
    1079                 $this->onAfterWrite();
    1080
    1081                 $this->changed = null;
    1082             } elseif ( $showDebug ) {
    1083                 echo "<b>Debug:</b> no changes for DataObject<br />";
    1084                 // Used by DODs to clean up after themselves, eg, Versioned
    1085                 $this->extend('onAfterSkippedWrite');
    1086             }
    1087
    1088             // Clears the cache for this object so get_one returns the correct object.
    1089             $this->flushCache();
    1090
    1091             if(!isset($this->record['Created'])) {
    1092                 $this->record['Created'] = SS_Datetime::now()->Rfc2822();
    1093             }
    1094             $this->record['LastEdited'] = SS_Datetime::now()->Rfc2822();
    1095         } else {
    1096             // Used by DODs to clean up after themselves, eg, Versioned
    1097             $this->extend('onAfterSkippedWrite');
    1098         }
    1099
    1100         // Write ComponentSets as necessary
    1101         if($writeComponents) {
    1102             $this->writeComponents(true);
    1103         }
    1104         return $this->record['ID'];
    1105     }
    1106
    1107
    1108     /**
    1109      * Write the cached components to the database. Cached components could refer to two different instances of the same record.
    1110      *
    1111      * @param $recursive Recursively write components
    1112      */
    1113     public function writeComponents($recursive = false) {
    1114         if(!$this->components) return;
    1115
    1116         foreach($this->components as $component) {
    1117             $component->write(false, false, false, $recursive);
    1118         }
    1119     }
    1120
    1121     /**
    1122      * Perform a write without affecting the version table.
    1123      * On objects without versioning.
    1124      *
    1125      * @return int The ID of the record
    1126      */
    1127     public function writeWithoutVersion() {
    1128         $this->changed['Version'] = 1;
    1129         if(!isset($this->record['Version'])) {
    1130             $this->record['Version'] = -1;
    1131         }
    1132         return $this->write();
    1133     }
    1134
    1135     /**
    1136      * Delete this data object.
    1137      * $this->onBeforeDelete() gets called.
    1138      * Note that in Versioned objects, both Stage and Live will be deleted.
    1139      *  @uses DataObjectDecorator->augmentSQL()
    1140      */
    1141     public function delete() {
    1142         $this->brokenOnDelete = true;
    1143         $this->onBeforeDelete();
    1144         if($this->brokenOnDelete) {
    1145             user_error("$this->class has a broken onBeforeDelete() function.  Make sure that you call parent::onBeforeDelete().", E_USER_ERROR);
    1146         }
    1147
    1148         foreach($this->getClassAncestry() as $ancestor) {
    1149             if(self::has_own_table($ancestor)) {
    1150                 $sql = new SQLQuery();
    1151                 $sql->delete = true;
    1152                 $sql->from[$ancestor] = "\"$ancestor\"";
    1153                 $sql->where[] = "\"ID\" = $this->ID";
    1154                 $this->extend('augmentSQL', $sql);
    1155                 $sql->execute();
    1156             }
    1157         }
    1158         // Remove this item out of any caches
    1159         $this->flushCache();
    1160
    1161         $this->onAfterDelete();
    1162
    1163         $this->OldID = $this->ID;
    1164         $this->ID = 0;
    1165
    1166         DataObjectLog::deletedObject($this);
    1167     }
    1168
    1169     /**
    1170      * Delete the record with the given ID.
    1171      *
    1172      * @param string $className The class name of the record to be deleted
    1173      * @param int $id ID of record to be deleted
    1174      */
    1175     public static function delete_by_id($className, $id) {
    1176         $obj = DataObject::get_by_id($className, $id);
    1177         if($obj) {
    1178             $obj->delete();
    1179         } else {
    1180             user_error("$className object #$id wasn't found when calling DataObject::delete_by_id", E_USER_WARNING);
    1181         }
    1182     }
    1183
    1184     /**
    1185      * A cache used by getClassAncestry()
    1186      * @var array
    1187      */
    1188     protected static $ancestry;
    1189
    1190     /**
    1191      * Get the class ancestry, including the current class name.
    1192      * The ancestry will be returned as an array of class names, where the 0th element
    1193      * will be the class that inherits directly from DataObject, and the last element
    1194      * will be the current class.
    1195      *
    1196      * @return array Class ancestry
    1197      */
    1198     public function getClassAncestry() {
    1199         if(!isset(DataObject::$ancestry[$this->class])) {
    1200             DataObject::$ancestry[$this->class] = array($this->class);
    1201             while(($class = get_parent_class(DataObject::$ancestry[$this->class][0])) != "DataObject") {
    1202                 array_unshift(DataObject::$ancestry[$this->class], $class);
    1203             }
    1204         }
    1205         return DataObject::$ancestry[$this->class];
    1206     }
    1207
    1208     /**
    1209      * Return a component object from a one to one relationship, as a DataObject.
    1210      * If no component is available, an 'empty component' will be returned.
    1211      *
    1212      * @param string $componentName Name of the component
    1213      *
    1214      * @return DataObject The component object. It's exact type will be that of the component.
    1215      */
    1216     public function getComponent($componentName) {
    1217         if(isset($this->components[$componentName])) {
    1218             return $this->components[$componentName];
    1219         }
    1220
    1221         if($class = $this->has_one($componentName)) {
    1222             $joinField = $componentName . 'ID';
    1223             $joinID    = $this->getField($joinField);
    1224
    1225             if($joinID) {
    1226                 $component = DataObject::get_by_id($class, $joinID);
    1227             }
    1228
    1229             if(!isset($component) || !$component) {
    1230                 $component = new $class();
    1231             }
    1232         } elseif($class = $this->belongs_to($componentName)) {
    1233             $joinField = $this->getRemoteJoinField($componentName, 'belongs_to');
    1234             $joinID    = $this->ID;
    1235
    1236             if($joinID) {
    1237                 $component = DataObject::get_one($class, "\"$joinField\" = $joinID");
    1238             }
    1239
    1240             if(!isset($component) || !$component) {
    1241                 $component = new $class();
    1242                 $component->$joinField = $this->ID;
    1243             }
    1244         } else {
    1245             throw new Exception("DataObject->getComponent(): Could not find component '$componentName'.");
    1246         }
    1247
    1248         $this->components[$componentName] = $component;
    1249         return $component;
    1250     }
    1251
    1252     /**
    1253      * A cache used by component getting classes
    1254      * @var array
    1255      */
    1256     protected $componentCache;
    1257
    1258     /**
    1259      * Returns a one-to-many component, as a ComponentSet.
    1260      *
    1261      * @param string $componentName Name of the component
    1262      * @param string $filter A filter to be inserted into the WHERE clause
    1263      * @param string|array $sort A sort expression to be inserted into the ORDER BY clause. If omitted, the static field $default_sort on the component class will be used.
    1264      * @param string $join A single join clause. This can be used for filtering, only 1 instance of each DataObject will be returned.
    1265      * @param string|array $limit A limit expression to be inserted into the LIMIT clause
    1266      *
    1267      * @return ComponentSet The components of the one-to-many relationship.
    1268      */
    1269     public function getComponents($componentName, $filter = "", $sort = "", $join = "", $limit = "") {
    1270         $result = null;
    1271
    1272         $sum = md5("{$filter}_{$sort}_{$join}_{$limit}");
    1273         if(isset($this->componentCache[$componentName . '_' . $sum]) && false != $this->componentCache[$componentName . '_' . $sum]) {
    1274             return $this->componentCache[$componentName . '_' . $sum];
    1275         }
    1276
    1277         if(!$componentClass = $this->has_many($componentName)) {
    1278             user_error("DataObject::getComponents(): Unknown 1-to-many component '$componentName' on class '$this->class'", E_USER_ERROR);
    1279         }
    1280
    1281         $joinField = $this->getRemoteJoinField($componentName, 'has_many');
    1282
    1283         if($this->isInDB()) { //Check to see whether we should query the db
    1284             $query = $this->getComponentsQuery($componentName, $filter, $sort, $join, $limit);
    1285             $result = $this->buildDataObjectSet($query->execute(), 'ComponentSet', $query, $componentClass);
    1286             if($result) $result->parseQueryLimit($query);
    1287         }
    1288
    1289         if(!$result) {
    1290             // If this record isn't in the database, then we want to hold onto this specific ComponentSet,
    1291             // because it's the only copy of the data that we have.
    1292             $result = new ComponentSet();
    1293             $this->setComponent($componentName . '_' . $sum, $result);
    1294         }
    1295
    1296         $result->setComponentInfo("1-to-many", $this, null, null, $componentClass, $joinField);
    1297
    1298         return $result;
    1299     }
    1300
    1301     /**
    1302      * Get the query object for a $has_many Component.
    1303      *
    1304      * Use {@link DataObjectSet->setComponentInfo()} to attach metadata to the
    1305      * resultset you're building with this query.
    1306      * Use {@link DataObject->buildDataObjectSet()} to build a set out of the {@link SQLQuery}
    1307      * object, and pass "ComponentSet" as a $containerClass.
    1308      *
    1309      * @param string $componentName
    1310      * @param string $filter
    1311      * @param string|array $sort
    1312      * @param string $join
    1313      * @param string|array $limit
    1314      * @return SQLQuery
    1315      */
    1316     public function getComponentsQuery($componentName, $filter = "", $sort = "", $join = "", $limit = "") {
    1317         if(!$componentClass = $this->has_many($componentName)) {
    1318             user_error("DataObject::getComponentsQuery(): Unknown 1-to-many component '$componentName' on class '$this->class'", E_USER_ERROR);
    1319         }
    1320
    1321         $joinField = $this->getRemoteJoinField($componentName, 'has_many');
    1322
    1323         $id = $this->getField("ID");
    1324
    1325         // get filter
    1326         $combinedFilter = "\"$joinField\" = '$id'";
    1327         if($filter) $combinedFilter .= " AND {$filter}";
    1328
    1329         return singleton($componentClass)->extendedSQL($combinedFilter, $sort, $limit, $join);
    1330     }
    1331
    1332     /**
    1333      * Tries to find the database key on another object that is used to store a relationship to this class. If no join
    1334      * field can be found it defaults to 'ParentID'.
    1335      *
    1336      * @param string $component
    1337      * @param string $type the join type - either 'has_many' or 'belongs_to'
    1338      * @return string
    1339      */
    1340     public function getRemoteJoinField($component, $type = 'has_many') {
    1341         $remoteClass = $this->$type($component, false);
    1342
    1343         if(!$remoteClass) {
    1344             throw new Exception("Unknown $type component '$component' on class '$this->class'");
    1345         }
    1346
    1347         if($fieldPos = strpos($remoteClass, '.')) {
    1348             return substr($remoteClass, $fieldPos + 1) . 'ID';
    1349         }
    1350
    1351         $remoteRelations = array_flip(Object::combined_static($remoteClass, 'has_one', 'DataObject'));
    1352
    1353         // look for remote has_one joins on this class or any parent classes
    1354         foreach(array_reverse(ClassInfo::ancestry($this)) as $class) {
    1355             if(array_key_exists($class, $remoteRelations)) return $remoteRelations[$class] . 'ID';
    1356         }
    1357
    1358         return 'ParentID';
    1359     }
    1360
    1361     /**
    1362      * Sets the component of a relationship.
    1363      *
    1364      * @param string $componentName Name of the component
    1365      * @param DataObject|ComponentSet $componentValue Value of the component
    1366      */
    1367     public function setComponent($componentName, $componentValue) {
    1368         $this->componentCache[$componentName] = $componentValue;
    1369     }
    1370
    1371     /**
    1372      * Returns a many-to-many component, as a ComponentSet.
    1373      * @param string $componentName Name of the many-many component
    1374      * @return ComponentSet The set of components
    1375      *
    1376      * @todo Implement query-params
    1377      */
    1378     public function getManyManyComponents($componentName, $filter = "", $sort = "", $join = "", $limit = "") {
    1379         $sum = md5("{$filter}_{$sort}_{$join}_{$limit}");
    1380         if(isset($this->componentCache[$componentName . '_' . $sum]) && false != $this->componentCache[$componentName . '_' . $sum]) {
    1381             return $this->componentCache[$componentName . '_' . $sum];
    1382         }
    1383
    1384         list($parentClass, $componentClass, $parentField, $componentField, $table) = $this->many_many($componentName);
    1385
    1386         // Join expression is done on SiteTree.ID even if we link to Page; it helps work around
    1387         // database inconsistencies
    1388         $componentBaseClass = ClassInfo::baseDataClass($componentClass);
    1389
    1390         if($this->ID && is_numeric($this->ID)) {
    1391
    1392             if($componentClass) {
    1393                 $query = $this->getManyManyComponentsQuery($componentName, $filter, $sort, $join, $limit);
    1394                 $records = $query->execute();
    1395                 $result = $this->buildDataObjectSet($records, "ComponentSet", $query, $componentBaseClass);
    1396                 if($result) $result->parseQueryLimit($query); // for pagination support
    1397                 if(!$result) {
    1398                     $result = new ComponentSet();
    1399                 }
    1400             }
    1401         } else {
    1402             $result = new ComponentSet();
    1403         }
    1404         $result->setComponentInfo("many-to-many", $this, $parentClass, $table, $componentClass);
    1405
    1406         // If this record isn't in the database, then we want to hold onto this specific ComponentSet,
    1407         // because it's the only copy of the data that we have.
    1408         if(!$this->isInDB()) {
    1409             $this->setComponent($componentName . '_' . $sum, $result);
    1410         }
    1411
    1412         return $result;
    1413     }
    1414
    1415     /**
    1416      * Get the query object for a $many_many Component.
    1417      * Use {@link DataObjectSet->setComponentInfo()} to attach metadata to the
    1418      * resultset you're building with this query.
    1419      * Use {@link DataObject->buildDataObjectSet()} to build a set out of the {@link SQLQuery}
    1420      * object, and pass "ComponentSet" as a $containerClass.
    1421      *
    1422      * @param string $componentName
    1423      * @param string $filter
    1424      * @param string|array $sort
    1425      * @param string $join
    1426      * @param string|array $limit
    1427      * @return SQLQuery
    1428      */
    1429     public function getManyManyComponentsQuery($componentName, $filter = "", $sort = "", $join = "", $limit = "") {
    1430         list($parentClass, $componentClass, $parentField, $componentField, $table) = $this->many_many($componentName);
    1431
    1432         $componentObj = singleton($componentClass);
    1433
    1434         // Join expression is done on SiteTree.ID even if we link to Page; it helps work around
    1435         // database inconsistencies
    1436         $componentBaseClass = ClassInfo::baseDataClass($componentClass);
    1437
    1438
    1439         $query = $componentObj->extendedSQL(
    1440             "\"$table\".\"$parentField\" = $this->ID", // filter
    1441             $sort,
    1442             $limit,
    1443             "INNER JOIN \"$table\" ON \"$table\".\"$componentField\" = \"$componentBaseClass\".\"ID\"" // join
    1444         );
    1445
    1446         foreach((array)$this->many_many_extraFields($componentName) as $extraField => $extraFieldType) {
    1447             $query->select[] = "\"$table\".\"$extraField\"";
    1448             $query->groupby[] = "\"$table\".\"$extraField\"";
    1449         }
    1450
    1451         if($filter) $query->where[] = $filter;
    1452         if($join) $query->from[] = $join;
    1453
    1454         return $query;
    1455     }
    1456
    1457     /**
    1458      * Pull out a join clause for a many-many relationship.
    1459      *
    1460      * @param string $componentName The many_many or belongs_many_many relation to join to.
    1461      * @param string $baseTable The classtable that will already be included in the SQL query to which this join will be added.
    1462      * @return string SQL join clause
    1463      */
    1464     function getManyManyJoin($componentName, $baseTable) {
    1465         if(!$componentClass = $this->many_many($componentName)) {
    1466             user_error("DataObject::getComponents(): Unknown many-to-many component '$componentName' on class '$this->class'", E_USER_ERROR);
    1467         }
    1468         $classes = array_reverse(ClassInfo::ancestry($this->class));
    1469
    1470         list($parentClass, $componentClass, $parentField, $componentField, $table) = $this->many_many($componentName);
    1471
    1472         $baseComponentClass = ClassInfo::baseDataClass($componentClass);
    1473         if($baseTable == $parentClass) {
    1474             return "LEFT JOIN \"$table\" ON (\"$table\".\"$parentField\" = \"$parentClass\".\"ID\" AND \"$table\".\"$componentField\" = '{$this->ID}')";
    1475         } else {
    1476             return "LEFT JOIN \"$table\" ON (\"$table\".\"$componentField\" = \"$baseComponentClass\".\"ID\" AND \"$table\".\"$parentField\" = '{$this->ID}')";
    1477         }
    1478     }
    1479
    1480     function getManyManyFilter($componentName, $baseTable) {
    1481         list($parentClass, $componentClass, $parentField, $componentField, $table) = $this->many_many($componentName);
    1482
    1483         return "\"$table\".\"$parentField\" = '{$this->ID}'";
    1484     }
    1485
    1486     /**
    1487      * Return an aggregate object. An aggregate object returns the result of running some SQL aggregate function on a field of
    1488      * this dataobject type.
    1489      *
    1490      * It can be called with no arguments, in which case it returns an object that calculates aggregates on this object's type,
    1491      * or with an argument (possibly statically), in which case it returns an object for that type
    1492      */
    1493     function Aggregate($type = null, $filter = '') {
    1494         return new Aggregate($type ? $type : $this->class, $filter);
    1495     }
    1496
    1497     /**
    1498      * Return an relationship aggregate object. A relationship aggregate does the same thing as an aggregate object, but operates
    1499      * on a has_many rather than directly on the type specified
    1500      */
    1501     function RelationshipAggregate($object = null, $relationship = '', $filter = '') {
    1502         if (is_string($object)) { $filter = $relationship; $relationship = $object; $object = $this; }
    1503         return new Aggregate_Relationship($object ? $object : $this->owner, $relationship, $filter);
    1504     }
    1505
    1506     /**
    1507      * Return the class of a one-to-one component.  If $component is null, return all of the one-to-one components and their classes.
    1508      *
    1509      * @param string $component Name of component
    1510      *
    1511      * @return string|array The class of the one-to-one component, or an array of all one-to-one components and their classes.
    1512      */
    1513     public function has_one($component = null) {
    1514         $classes = ClassInfo::ancestry($this);
    1515
    1516         foreach($classes as $class) {
    1517             // Wait until after we reach DataObject
    1518             if(in_array($class, array('Object', 'ViewableData', 'DataObject'))) continue;
    1519
    1520             if($component) {
    1521                 $hasOne = Object::uninherited_static($class, 'has_one');
    1522
    1523                 if(isset($hasOne[$component])) {
    1524                     return $hasOne[$component];
    1525                 }
    1526             } else {
    1527                 $newItems = (array) Object::uninherited_static($class, 'has_one');
    1528                 // Validate the data
    1529                 foreach($newItems as $k => $v) {
    1530                     if(!is_string($k) || is_numeric($k) || !is_string($v)) user_error("$class::\$has_one has a bad entry: "
    1531                         . 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);
    1532                 }
    1533                 $items = isset($items) ? array_merge($newItems, (array)$items) : $newItems;
    1534             }
    1535         }
    1536         return isset($items) ? $items : null;
    1537     }
    1538
    1539     /**
    1540      * Returns the class of a remote belongs_to relationship. If no component is specified a map of all components and
    1541      * their class name will be returned.
    1542      *
    1543      * @param string $component
    1544      * @param bool $classOnly If this is TRUE, than any has_many relationships in the form "ClassName.Field" will have
    1545      *        the field data stripped off. It defaults to TRUE.
    1546      * @return string|array
    1547      */
    1548     public function belongs_to($component = null, $classOnly = true) {
    1549         $belongsTo = Object::combined_static($this->class, 'belongs_to', 'DataObject');
    1550
    1551         if($component) {
    1552             if($belongsTo && array_key_exists($component, $belongsTo)) {
    1553                 $belongsTo = $belongsTo[$component];
    1554             } else {
    1555                 return false;
    1556             }
    1557         }
    1558
    1559         if($belongsTo && $classOnly) {
    1560             return preg_replace('/(.+)?\..+/', '$1', $belongsTo);
    1561         } else {
    1562             return $belongsTo ? $belongsTo : array();
    1563         }
    1564     }
    1565
    1566     /**
    1567      * Return all of the database fields defined in self::$db and all the parent classes.
    1568      * Doesn't include any fields specified by self::$has_one.  Use $this->has_one() to get these fields
    1569      *
    1570      * @param string $fieldName Limit the output to a specific field name
    1571      * @return array The database fields
    1572      */
    1573     public function db($fieldName = null) {
    1574         $classes = ClassInfo::ancestry($this);
    1575         $good = false;
    1576         $items = array();
    1577
    1578         foreach($classes as $class) {
    1579             // Wait until after we reach DataObject
    1580             if(!$good) {
    1581                 if($class == 'DataObject') {
    1582                     $good = true;
    1583                 }
    1584                 continue;
    1585             }
    1586
    1587             if($fieldName) {
    1588                 $db = Object::uninherited_static($class, 'db');
    1589
    1590                 if(isset($db[$fieldName])) {
    1591                     return $db[$fieldName];
    1592                 }
    1593             } else {
    1594                 $newItems = (array) Object::uninherited_static($class, 'db');
    1595                 // Validate the data
    1596                 foreach($newItems as $k => $v) {
    1597                     if(!is_string($k) || is_numeric($k) || !is_string($v)) user_error("$class::\$db has a bad entry: "
    1598                         . 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);
    1599                 }
    1600                 $items = isset($items) ? array_merge((array)$items, $newItems) : $newItems;
    1601             }
    1602         }
    1603
    1604         return $items;
    1605     }
    1606
    1607     /**
    1608      * Gets the class of a one-to-many relationship. If no $component is specified then an array of all the one-to-many
    1609      * relationships and their classes will be returned.
    1610      *
    1611      * @param string $component Name of component
    1612      * @param bool $classOnly If this is TRUE, than any has_many relationships in the form "ClassName.Field" will have
    1613      *        the field data stripped off. It defaults to TRUE.
    1614      * @return string|array
    1615      */
    1616     public function has_many($component = null, $classOnly = true) {
    1617         $hasMany = Object::combined_static($this->class, 'has_many', 'DataObject');
    1618
    1619         if($component) {
    1620             if($hasMany && array_key_exists($component, $hasMany)) {
    1621                 $hasMany = $hasMany[$component];
    1622             } else {
    1623                 return false;
    1624             }
    1625         }
    1626
    1627         if($hasMany && $classOnly) {
    1628             return preg_replace('/(.+)?\..+/', '$1', $hasMany);
    1629         } else {
    1630             return $hasMany ? $hasMany : array();
    1631         }
    1632     }
    1633
    1634     /**
    1635      * Return the many-to-many extra fields specification.
    1636      *
    1637      * If you don't specify a component name, it returns all
    1638      * extra fields for all components available.
    1639      *
    1640      * @param string $component Name of component
    1641      * @return array
    1642      */
    1643     public function many_many_extraFields($component = null) {
    1644         $classes = ClassInfo::ancestry($this);
    1645
    1646         foreach($classes as $class) {
    1647             if(in_array($class, array('ViewableData', 'Object', 'DataObject'))) continue;
    1648
    1649             // Find extra fields for one component
    1650             if($component) {
    1651                 $SNG_class = singleton($class);
    1652                 $extraFields = $SNG_class->stat('many_many_extraFields');
    1653
    1654                 // Extra fields are immediately available on this class
    1655                 if(isset($extraFields[$component])) {
    1656                     return $extraFields[$component];
    1657                 }
    1658
    1659                 $manyMany = $SNG_class->stat('many_many');
    1660                 $candidate = (isset($manyMany[$component])) ? $manyMany[$component] : null;
    1661                 if($candidate) {
    1662                     $SNG_candidate = singleton($candidate);
    1663                     $candidateManyMany = $SNG_candidate->stat('belongs_many_many');
    1664
    1665                     // Find the relation given the class
    1666                     $relationName = null;
    1667                     if($candidateManyMany) foreach($candidateManyMany as $relation => $relatedClass) {
    1668                         if($relatedClass == $class) {
    1669                             $relationName = $relation;
    1670                             break;
    1671                         }
    1672                     }
    1673
    1674                     if($relationName) {
    1675                         $extraFields = $SNG_candidate->stat('many_many_extraFields');
    1676                         if(isset($extraFields[$relationName])) {
    1677                             return $extraFields[$relationName];
    1678                         }
    1679                     }
    1680                 }
    1681
    1682                 $manyMany = $SNG_class->stat('belongs_many_many');
    1683                 $candidate = (isset($manyMany[$component])) ? $manyMany[$component] : null;
    1684                 if($candidate) {
    1685                     $SNG_candidate = singleton($candidate);
    1686                     $candidateManyMany = $SNG_candidate->stat('many_many');
    1687
    1688                     // Find the relation given the class
    1689                     if($candidateManyMany) foreach($candidateManyMany as $relation => $relatedClass) {
    1690                         if($relatedClass == $class) {
    1691                             $relationName = $relation;
    1692                         }
    1693                     }
    1694
    1695                     $extraFields = $SNG_candidate->stat('many_many_extraFields');
    1696                     if(isset($extraFields[$relationName])) {
    1697                         return $extraFields[$relationName];
    1698                     }
    1699                 }
    1700
    1701             } else {
    1702
    1703                 // Find all the extra fields for all components
    1704                 $newItems = eval("return (array){$class}::\$many_many_extraFields;");
    1705
    1706                 foreach($newItems as $k => $v) {
    1707                     if(!is_array($v)) {
    1708                         user_error(
    1709                             "$class::\$many_many_extraFields has a bad entry: "
    1710                             . var_export($k, true) . " => " . var_export($v, true)
    1711                             . ". Each many_many_extraFields entry should map to a field specification array.",
    1712                             E_USER_ERROR
    1713                         );
    1714                     }
    1715                 }
    1716
    1717                 return isset($items) ? array_merge($newItems, $items) : $newItems;
    1718             }
    1719         }
    1720     }
    1721
    1722     /**
    1723      * Return information about a many-to-many component.
    1724      * The return value is an array of (parentclass, childclass).  If $component is null, then all many-many
    1725      * components are returned.
    1726      *
    1727      * @param string $component Name of component
    1728      *
    1729      * @return array  An array of (parentclass, childclass), or an array of all many-many components
    1730      */
    1731     public function many_many($component = null) {
    1732         $classes = ClassInfo::ancestry($this);
    1733
    1734         foreach($classes as $class) {
    1735             // Wait until after we reach DataObject
    1736             if(in_array($class, array('ViewableData', 'Object', 'DataObject'))) continue;
    1737
    1738             if($component) {
    1739                 $manyMany = Object::uninherited_static($class, 'many_many');
    1740                 // Try many_many
    1741                 $candidate = (isset($manyMany[$component])) ? $manyMany[$component] : null;
    1742                 if($candidate) {
    1743                     $parentField = $class . "ID";
    1744                     $childField = ($class == $candidate) ? "ChildID" : $candidate . "ID";
    1745                     return array($class, $candidate, $parentField, $childField, "{$class}_$component");
    1746                 }
    1747
    1748                 // Try belongs_many_many
    1749                 $belongsManyMany = Object::uninherited_static($class, 'belongs_many_many');
    1750                 $candidate = (isset($belongsManyMany[$component])) ? $belongsManyMany[$component] : null;
    1751                 if($candidate) {
    1752                     $childField = $candidate . "ID";
    1753
    1754                     // We need to find the inverse component name
    1755                     $otherManyMany = Object::uninherited_static($candidate, 'many_many');
    1756                     if(!$otherManyMany) {
    1757                         user_error("Inverse component of $candidate not found ({$this->class})", E_USER_ERROR);
    1758                     }
    1759
    1760                     foreach($otherManyMany as $inverseComponentName => $candidateClass) {
    1761                         if($candidateClass == $class || is_subclass_of($class, $candidateClass)) {
    1762                             $parentField = ($class == $candidate) ? "ChildID" : $candidateClass . "ID";
    1763                             // HACK HACK HACK!
    1764                             if($component == 'NestedProducts') {
    1765                                 $parentField = $candidateClass . "ID";
    1766                             }
    1767
    1768                             return array($class, $candidate, $parentField, $childField, "{$candidate}_$inverseComponentName");
    1769                         }
    1770                     }
    1771                     user_error("Orphaned \$belongs_many_many value for $this->class.$component", E_USER_ERROR);
    1772                 }
    1773             } else {
    1774                 $newItems = (array) Object::uninherited_static($class, 'many_many');
    1775                 // Validate the data
    1776                 foreach($newItems as $k => $v) {
    1777                     if(!is_string($k) || is_numeric($k) || !is_string($v)) user_error("$class::\$many_many has a bad entry: "
    1778                         . 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);
    1779                 }
    1780                 $items = isset($items) ? array_merge($newItems, $items) : $newItems;
    1781
    1782                 $newItems = (array) Object::uninherited_static($class, 'belongs_many_many');
    1783                 // Validate the data
    1784                 foreach($newItems as $k => $v) {
    1785                     if(!is_string($k) || is_numeric($k) || !is_string($v)) user_error("$class::\$belongs_many_many has a bad entry: "
    1786                         . 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);
    1787                 }
    1788
    1789                 $items = isset($items) ? array_merge($newItems, $items) : $newItems;
    1790             }
    1791         }
    1792
    1793         return isset($items) ? $items : null;
    1794     }
    1795
    1796     /**
    1797      * This returns an array (if it exists) describing the database extensions that are required, or false if none
    1798      *
    1799      * This is experimental, and is currently only a Postgres-specific enhancement.
    1800      *
    1801      * @return array or false
    1802      */
    1803     function database_extensions($class){
    1804
    1805         $extensions = Object::uninherited_static($class, 'database_extensions');
    1806
    1807         if($extensions)
    1808             return $extensions;
    1809         else
    1810             return false;
    1811     }
    1812
    1813     /**
    1814      * Generates a SearchContext to be used for building and processing
    1815      * a generic search form for properties on this object.
    1816      *
    1817      * @return SearchContext
    1818      */
    1819     public function getDefaultSearchContext() {
    1820         return new SearchContext(
    1821             $this->class,
    1822             $this->scaffoldSearchFields(),
    1823             $this->defaultSearchFilters()
    1824         );
    1825     }
    1826
    1827     /**
    1828      * Determine which properties on the DataObject are
    1829      * searchable, and map them to their default {@link FormField}
    1830      * representations. Used for scaffolding a searchform for {@link ModelAdmin}.
    1831      *
    1832      * Some additional logic is included for switching field labels, based on
    1833      * how generic or specific the field type is.
    1834      *
    1835      * Used by {@link SearchContext}.
    1836      *
    1837      * @param array $_params
    1838      *  'fieldClasses': Associative array of field names as keys and FormField classes as values
    1839      *  'restrictFields': Numeric array of a field name whitelist
    1840      * @return FieldSet
    1841      */
    1842     public function scaffoldSearchFields($_params = null) {
    1843         $params = array_merge(
    1844             array(
    1845                 'fieldClasses' => false,
    1846                 'restrictFields' => false
    1847             ),
    1848             (array)$_params
    1849         );
    1850         $fields = new FieldSet();
    1851         foreach($this->searchableFields() as $fieldName => $spec) {
    1852             if($params['restrictFields'] && !in_array($fieldName, $params['restrictFields'])) continue;
    1853
    1854             // If a custom fieldclass is provided as a string, use it
    1855             if($params['fieldClasses'] && isset($params['fieldClasses'][$fieldName])) {
    1856                 $fieldClass = $params['fieldClasses'][$fieldName];
    1857                 $field = new $fieldClass($fieldName);
    1858             // If we explicitly set a field, then construct that
    1859             } else if(isset($spec['field'])) {
    1860                 // If it's a string, use it as a class name and construct
    1861                 if(is_string($spec['field'])) {
    1862                     $fieldClass = $spec['field'];
    1863                     $field = new $fieldClass($fieldName);
    1864
    1865                 // If it's a FormField object, then just use that object directly.
    1866                 } else if($spec['field'] instanceof FormField) {
    1867                     $field = $spec['field'];
    1868
    1869                 // Otherwise we have a bug
    1870                 } else {
    1871                     user_error("Bad value for searchable_fields, 'field' value: " . var_export($spec['field'], true), E_USER_WARNING);
    1872                 }
    1873
    1874             // Otherwise, use the database field's scaffolder
    1875             } else {
    1876                 $field = $this->relObject($fieldName)->scaffoldSearchField();
    1877             }
    1878
    1879             if (strstr($fieldName, '.')) {
    1880                 $field->setName(str_replace('.', '__', $fieldName));
    1881             }
    1882             $field->setTitle($spec['title']);
    1883
    1884             $fields->push($field);
    1885         }
    1886         return $fields;
    1887     }
    1888
    1889     /**
    1890      * Scaffold a simple edit form for all properties on this dataobject,
    1891      * based on default {@link FormField} mapping in {@link DBField::scaffoldFormField()}.
    1892      * Field labels/titles will be auto generated from {@link DataObject::fieldLabels()}.
    1893      *
    1894      * @uses FormScaffolder
    1895      *
    1896      * @param array $_params Associative array passing through properties to {@link FormScaffolder}.
    1897      * @return FieldSet
    1898      */
    1899     public function scaffoldFormFields($_params = null) {
    1900         $params = array_merge(
    1901             array(
    1902                 'tabbed' => false,
    1903                 'includeRelations' => false,
    1904                 'restrictFields' => false,
    1905                 'fieldClasses' => false,
    1906                 'ajaxSafe' => false
    1907             ),
    1908             (array)$_params
    1909         );
    1910
    1911         $fs = new FormScaffolder($this);
    1912         $fs->tabbed = $params['tabbed'];
    1913         $fs->includeRelations = $params['includeRelations'];
    1914         $fs->restrictFields = $params['restrictFields'];
    1915         $fs->fieldClasses = $params['fieldClasses'];
    1916         $fs->ajaxSafe = $params['ajaxSafe'];
    1917
    1918         return $fs->getFieldSet();
    1919     }
    1920
    1921     /**
    1922      * Centerpiece of every data administration interface in Silverstripe,
    1923      * which returns a {@link FieldSet} suitable for a {@link Form} object.
    1924      * If not overloaded, we're using {@link scaffoldFormFields()} to automatically
    1925      * generate this set. To customize, overload this method in a subclass
    1926      * or decorate onto it by using {@link DataObjectDecorator->updateCMSFields()}.
    1927      *
    1928      * <code>
    1929      * klass MyCustomClass extends DataObject {
    1930      *  static $db = array('CustomProperty'=>'Boolean');
    1931      *
    1932      *  public function getCMSFields() {
    1933      *      $fields = parent::getCMSFields();
    1934      *      $fields->addFieldToTab('Root.Content',new CheckboxField('CustomProperty'));
    1935      *      return $fields;
    1936      *  }
    1937      * }
    1938      * </code>
    1939      *
    1940      * @see Good example of complex FormField building: SiteTree::getCMSFields()
    1941      *
    1942      * @param array $params See {@link scaffoldFormFields()}
    1943      * @return FieldSet Returns a TabSet for usage within the CMS - don't use for frontend forms.
    1944      */
    1945     public function getCMSFields($params = null) {
    1946         $tabbedFields = $this->scaffoldFormFields(array_merge(
    1947             array(
    1948                 'includeRelations' => true,
    1949                 'tabbed' => true,
    1950                 'ajaxSafe' => true
    1951             ),
    1952             (array)$params
    1953         ));
    1954
    1955         if(self::$runCMSFieldsExtensions) {
    1956             $this->extend('updateCMSFields', $tabbedFields);
    1957         }
    1958
    1959         return $tabbedFields;
    1960     }
    1961
    1962     /**
    1963      * need to be overload by solid dataobject, so that the customised actions of that dataobject,
    1964      * including that dataobject's decorator customised actions could be added to the EditForm.
    1965      *
    1966      * @return an Empty FieldSet(); need to be overload by solid subclass
    1967      */
    1968     public function getCMSActions() {
    1969         $actions = new FieldSet();
    1970         $this->extend('updateCMSActions', $actions);
    1971         return $actions;
    1972     }
    1973
    1974
    1975     /**
    1976      * Used for simple frontend forms without relation editing
    1977      * or {@link TabSet} behaviour. Uses {@link scaffoldFormFields()}
    1978      * by default. To customize, either overload this method in your
    1979      * subclass, or decorate it by {@link DataObjectDecorator->updateFrontEndFields()}.
    1980      *
    1981      * @todo Decide on naming for "website|frontend|site|page" and stick with it in the API
    1982      *
    1983      * @param array $params See {@link scaffoldFormFields()}
    1984      * @return FieldSet Always returns a simple field collection without TabSet.
    1985      */
    1986     public function getFrontEndFields($params = null) {
    1987         $untabbedFields = $this->scaffoldFormFields($params);
    1988         $this->extend('updateFrontEndFields', $untabbedFields);
    1989
    1990         return $untabbedFields;
    1991     }
    1992
    1993     /**
    1994      * Gets the value of a field.
    1995      * Called by {@link __get()} and any getFieldName() methods you might create.
    1996      *
    1997      * @param string $field The name of the field
    1998      *
    1999      * @return mixed The field value
    2000      */
    2001     public function getField($field) {
    2002         // If we already have an object in $this->record, then we should just return that
    2003         if(isset($this->record[$field]) && is_object($this->record[$field]))  return $this->record[$field];
    2004
    2005         // Otherwise, we need to determine if this is a complex field
    2006         if(self::is_composite_field($this->class, $field)) {
    2007             $helper = $this->castingHelper($field);
    2008             $fieldObj = Object::create_from_string($helper, $field);
    2009
    2010             // write value only if either the field value exists,
    2011             // or a valid record has been loaded from the database
    2012             $value = (isset($this->record[$field])) ? $this->record[$field] : null;
    2013             if($value || $this->exists()) $fieldObj->setValue($value, $this->record, false);
    2014
    2015             $this->record[$field] = $fieldObj;
    2016
    2017             return $this->record[$field];
    2018         }
    2019
    2020         return isset($this->record[$field]) ? $this->record[$field] : null;
    2021     }
    2022
    2023     /**
    2024      * Return a map of all the fields for this record.
    2025      *
    2026      * @return array A map of field names to field values.
    2027      */
    2028     public function getAllFields() {
    2029         return $this->record;
    2030     }
    2031
    2032     /**
    2033      * Return the fields that have changed.
    2034      *
    2035      * The change level affects what the functions defines as "changed":
    2036      * - Level 1 will return strict changes, even !== ones.
    2037      * - Level 2 is more lenient, it will only return real data changes, for example a change from 0 to null
    2038      * would not be included.
    2039      *
    2040      * Example return:
    2041      * <code>
    2042      * array(
    2043      *   'Title' = array('before' => 'Home', 'after' => 'Home-Changed', 'level' => 2)
    2044      * )
    2045      * </code>
    2046      *
    2047      * @param boolean $databaseFieldsOnly Get only database fields that have changed
    2048      * @param int $changeLevel The strictness of what is defined as change
    2049      * @return array
    2050      */
    2051     public function getChangedFields($databaseFieldsOnly = false, $changeLevel = 1) {
    2052         $changedFields = array();
    2053
    2054         // Update the changed array with references to changed obj-fields
    2055         foreach($this->record as $k => $v) {
    2056             if(is_object($v) && method_exists($v, 'isChanged') && $v->isChanged()) {
    2057                 $this->changed[$k] = 1;
    2058             }
    2059         }
    2060
    2061         if($databaseFieldsOnly) {
    2062             $databaseFields = $this->inheritedDatabaseFields();
    2063             $databaseFields['ID'] = true;
    2064             $databaseFields['LastEdited'] = true;
    2065             $databaseFields['Created'] = true;
    2066             $databaseFields['ClassName'] = true;
    2067             $fields = array_intersect_key((array)$this->changed, $databaseFields);
    2068         } else {
    2069             $fields = $this->changed;
    2070         }
    2071
    2072         // Filter the list to those of a certain change level
    2073         if($changeLevel > 1) {
    2074             if($fields) foreach($fields as $name => $level) {
    2075                 if($level < $changeLevel) {
    2076                     unset($fields[$name]);
    2077                 }
    2078             }
    2079         }
    2080
    2081         if($fields) foreach($fields as $name => $level) {
    2082             $changedFields[$name] = array(
    2083                 'before' => array_key_exists($name, $this->original) ? $this->original[$name] : null,
    2084                 'after' => array_key_exists($name, $this->record) ? $this->record[$name] : null,
    2085                 'level' => $level
    2086             );
    2087         }
    2088
    2089         return $changedFields;
    2090     }
    2091
    2092     /**
    2093      * Uses {@link getChangedFields()} to determine if fields have been changed
    2094      * since loading them from the database.
    2095      *
    2096      * @param string $fieldName Name of the database field to check, will check for any if not given
    2097      * @param int $changeLevel See {@link getChangedFields()}
    2098      * @return boolean
    2099      */
    2100     function isChanged($fieldName = null, $changeLevel = 1) {
    2101         $changed = $this->getChangedFields(false, $changeLevel);
    2102         if(!isset($fieldName)) {
    2103             return !empty($changed);
    2104         }
    2105         else {
    2106             return array_key_exists($fieldName, $changed);
    2107         }
    2108     }
    2109
    2110     /**
    2111      * Set the value of the field
    2112      * Called by {@link __set()} and any setFieldName() methods you might create.
    2113      *
    2114      * @param string $fieldName Name of the field
    2115      * @param mixed $val New field value
    2116      */
    2117     function setField($fieldName, $val) {
    2118         // Situation 1: Passing an DBField
    2119         if($val instanceof DBField) {
    2120             $val->Name = $fieldName;
    2121             $this->record[$fieldName] = $val;
    2122         // Situation 2: Passing a literal or non-DBField object
    2123         } else {
    2124             // If this is a proper database field, we shouldn't be getting non-DBField objects
    2125             if(is_object($val) && $this->db($fieldName)) {
    2126                 user_error('DataObject::setField: passed an object that is not a DBField', E_USER_WARNING);
    2127             }
    2128
    2129             $defaults = $this->stat('defaults');
    2130             // if a field is not existing or has strictly changed
    2131             if(!isset($this->record[$fieldName]) || $this->record[$fieldName] !== $val) {
    2132                 // TODO Add check for php-level defaults which are not set in the db
    2133                 // TODO Add check for hidden input-fields (readonly) which are not set in the db
    2134                 // At the very least, the type has changed
    2135                 $this->changed[$fieldName] = 1;
    2136
    2137                 if((!isset($this->record[$fieldName]) && $val) || (isset($this->record[$fieldName]) && $this->record[$fieldName] != $val)) {
    2138                     // Value has changed as well, not just the type
    2139                     $this->changed[$fieldName] = 2;
    2140                 }
    2141
    2142                 // value is always saved back when strict check succeeds
    2143                 $this->record[$fieldName] = $val;
    2144             }
    2145         }
    2146     }
    2147
    2148     /**
    2149      * Set the value of the field, using a casting object.
    2150      * This is useful when you aren't sure that a date is in SQL format, for example.
    2151      * setCastedField() can also be used, by forms, to set related data.  For example, uploaded images
    2152      * can be saved into the Image table.
    2153      *
    2154      * @param string $fieldName Name of the field
    2155      * @param mixed $value New field value
    2156      */
    2157     public function setCastedField($fieldName, $val) {
    2158         if(!$fieldName) {
    2159             user_error("DataObject::setCastedField: Called without a fieldName", E_USER_ERROR);
    2160         }
    2161         $castingHelper = $this->castingHelper($fieldName);
    2162         if($castingHelper) {
    2163             $fieldObj = Object::create_from_string($castingHelper, $fieldName);
    2164             $fieldObj->setValue($val);
    2165             $fieldObj->saveInto($this);
    2166         } else {
    2167             $this->$fieldName = $val;
    2168         }
    2169     }
    2170
    2171     /**
    2172      * Returns true if the given field exists
    2173      * in a database column on any of the objects tables,
    2174      * or as a dynamic getter with get<fieldName>().
    2175      *
    2176      * @param string $field Name of the field
    2177      * @return boolean True if the given field exists
    2178      */
    2179     public function hasField($field) {
    2180         return (
    2181             array_key_exists($field, $this->record)
    2182             || $this->db($field)
    2183             || $this->hasMethod("get{$field}")
    2184         );
    2185     }
    2186
    2187     /**
    2188      * Returns true if the given field exists as a database column
    2189      *
    2190      * @param string $field Name of the field
    2191      *
    2192      * @return boolean
    2193      */
    2194     public function hasDatabaseField($field) {
    2195         // Add base fields which are not defined in static $db
    2196         static $fixedFields = array(
    2197             'ID' => 'Int',
    2198             'ClassName' => 'Enum',
    2199             'LastEdited' => 'SS_Datetime',
    2200             'Created' => 'SS_Datetime',
    2201         );
    2202
    2203         if(isset($fixedFields[$field])) return true;
    2204
    2205         return array_key_exists($field, $this->inheritedDatabaseFields());
    2206     }
    2207
    2208     /**
    2209      * Returns the field type of the given field, if it belongs to this class, and not a parent.
    2210      * Note that the field type will not include constructor arguments in round brackets, only the classname.
    2211      *
    2212      * @param string $field Name of the field
    2213      * @return string The field type of the given field
    2214      */
    2215     public function hasOwnTableDatabaseField($field) {
    2216         // Add base fields which are not defined in static $db
    2217         if($field == "ID") return "Int";
    2218         if($field == "ClassName" && get_parent_class($this) == "DataObject") return "Enum";
    2219         if($field == "LastEdited" && get_parent_class($this) == "DataObject") return "SS_Datetime";
    2220         if($field == "Created" && get_parent_class($this) == "DataObject") return "SS_Datetime";
    2221
    2222         // Add fields from Versioned decorator
    2223         if($field == "Version") return $this->hasExtension('Versioned') ? "Int" : false;
    2224
    2225         // get cached fieldmap
    2226         $fieldMap = isset(self::$cache_has_own_table_field[$this->class]) ? self::$cache_has_own_table_field[$this->class] : null;
    2227
    2228         // if no fieldmap is cached, get all fields
    2229         if(!$fieldMap) {
    2230             $fieldMap = Object::uninherited_static($this->class, 'db');
    2231
    2232             // all $db fields on this specific class (no parents)
    2233             foreach(self::composite_fields($this->class, false) as $fieldname => $fieldtype) {
    2234                 $combined_db = singleton($fieldtype)->compositeDatabaseFields();
    2235                 foreach($combined_db as $name => $type){
    2236                     $fieldMap[$fieldname.$name] = $type;
    2237                 }
    2238             }
    2239
    2240             // all has_one relations on this specific class,
    2241             // add foreign key
    2242             $hasOne = Object::uninherited_static($this->class, 'has_one');
    2243             if($hasOne) foreach($hasOne as $fieldName => $fieldSchema) {
    2244                 $fieldMap[$fieldName . 'ID'] = "ForeignKey";
    2245             }
    2246
    2247             // set cached fieldmap
    2248             self::$cache_has_own_table_field[$this->class] = $fieldMap;
    2249         }
    2250
    2251         // Remove string-based "constructor-arguments" from the DBField definition
    2252         if(isset($fieldMap[$field])) {
    2253             if(is_string($fieldMap[$field])) return strtok($fieldMap[$field],'(');
    2254             else return $fieldMap[$field]['type'];
    2255         }
    2256     }
    2257
    2258     /**
    2259      * Returns true if given class has its own table. Uses the rules for whether the table should exist rather than
    2260      * actually looking in the database.
    2261      *
    2262      * @param string $dataClass
    2263      * @return bool
    2264      */
    2265     public static function has_own_table($dataClass) {
    2266
    2267         // The condition below has the same effect as !is_subclass_of($dataClass,'DataObject'),
    2268         // which causes PHP < 5.3 to segfault in rare circumstances, see PHP bug #46753
    2269         if($dataClass == 'DataObject' || !in_array('DataObject', ClassInfo::ancestry($dataClass))) return false;
    2270
    2271         if(!isset(self::$cache_has_own_table[$dataClass])) {
    2272             if(get_parent_class($dataClass) == 'DataObject') {
    2273                 self::$cache_has_own_table[$dataClass] = true;
    2274             } else {
    2275                 self::$cache_has_own_table[$dataClass] = Object::uninherited_static($dataClass, 'db') || Object::uninherited_static($dataClass, 'has_one');
    2276             }
    2277         }
    2278         return self::$cache_has_own_table[$dataClass];
    2279     }
    2280
    2281     /**
    2282      * Returns true if the member is allowed to do the given action.
    2283      *
    2284      * @param string $perm The permission to be checked, such as 'View'.
    2285      * @param Member $member The member whose permissions need checking.  Defaults to the currently logged
    2286      * in user.
    2287      *
    2288      * @return boolean True if the the member is allowed to do the given action
    2289      */
    2290     function can($perm, $member = null) {
    2291         if(!isset($member)) {
    2292             $member = Member::currentUser();
    2293         }
    2294         if(Permission::checkMember($member, "ADMIN")) return true;
    2295
    2296         if($this->many_many('Can' . $perm)) {
    2297             if($this->ParentID && $this->SecurityType == 'Inherit') {
    2298                 if(!($p = $this->Parent)) {
    2299                     return false;
    2300                 }
    2301                 return $this->Parent->can($perm, $member);
    2302
    2303             } else {
    2304                 $permissionCache = $this->uninherited('permissionCache');
    2305                 $memberID = $member ? $member->ID : 'none';
    2306
    2307                 if(!isset($permissionCache[$memberID][$perm])) {
    2308                     if($member->ID) {
    2309                         $groups = $member->Groups();
    2310                     }
    2311
    2312                     $groupList = implode(', ', $groups->column("ID"));
    2313
    2314                     $query = new SQLQuery(
    2315                         "\"Page_Can$perm\".PageID",
    2316                     array("\"Page_Can$perm\""),
    2317                         "GroupID IN ($groupList)");
    2318
    2319                     $permissionCache[$memberID][$perm] = $query->execute()->column();
    2320
    2321                     if($perm == "View") {
    2322                         $query = new SQLQuery("\"SiteTree\".\"ID\"", array(
    2323                             "\"SiteTree\"",
    2324                             "LEFT JOIN \"Page_CanView\" ON \"Page_CanView\".\"PageID\" = \"SiteTree\".\"ID\""
    2325                             ), "\"Page_CanView\".\"PageID\" IS NULL");
    2326
    2327                             $unsecuredPages = $query->execute()->column();
    2328                             if($permissionCache[$memberID][$perm]) {
    2329                                 $permissionCache[$memberID][$perm] = array_merge($permissionCache[$memberID][$perm], $unsecuredPages);
    2330                             } else {
    2331                                 $permissionCache[$memberID][$perm] = $unsecuredPages;
    2332                             }
    2333                     }
    2334
    2335                     $this->set_uninherited('permissionCache', $permissionCache);
    2336                 }
    2337
    2338
    2339                 if($permissionCache[$memberID][$perm]) {
    2340                     return in_array($this->ID, $permissionCache[$memberID][$perm]);
    2341                 }
    2342             }
    2343         } else {
    2344             return parent::can($perm, $member);
    2345         }
    2346     }
    2347
    2348     /**
    2349      * @param Member $member
    2350      * @return boolean
    2351      */
    2352     public function canView($member = null) {
    2353         return Permission::check('ADMIN', 'any', $member);
    2354     }
    2355
    2356     /**
    2357      * @param Member $member
    2358      * @return boolean
    2359      */
    2360     public function canEdit($member = null) {
    2361         return Permission::check('ADMIN', 'any', $member);
    2362     }
    2363
    2364     /**
    2365      * @param Member $member
    2366      * @return boolean
    2367      */
    2368     public function canDelete($member = null) {
    2369         return Permission::check('ADMIN', 'any', $member);
    2370     }
    2371
    2372     /**
    2373      * @todo Should canCreate be a static method?
    2374      *
    2375      * @param Member $member
    2376      * @return boolean
    2377      */
    2378     public function canCreate($member = null) {
    2379         return Permission::check('ADMIN', 'any', $member);
    2380     }
    2381
    2382     /**
    2383      * Debugging used by Debug::show()
    2384      *
    2385      * @return string HTML data representing this object
    2386      */
    2387     public function debug() {
    2388         $val = "<h3>Database record: $this->class</h3>\n<ul>\n";
    2389         if($this->record) foreach($this->record as $fieldName => $fieldVal) {
    2390             $val .= "\t<li>$fieldName: " . Debug::text($fieldVal) . "</li>\n";
    2391         }
    2392         $val .= "</ul>\n";
    2393         return $val;
    2394     }
    2395
    2396     /**
    2397      * Return the DBField object that represents the given field.
    2398      * This works similarly to obj() with 2 key differences:
    2399      *   - it still returns an object even when the field has no value.
    2400      *   - it only matches fields and not methods
    2401      *   - it matches foreign keys generated by has_one relationships, eg, "ParentID"
    2402      *
    2403      * @param string $fieldName Name of the field
    2404      * @return DBField The field as a DBField object
    2405      */
    2406     public function dbObject($fieldName) {
    2407         // If we have a CompositeDBField object in $this->record, then return that
    2408         if(isset($this->record[$fieldName]) && is_object($this->record[$fieldName])) {
    2409             return $this->record[$fieldName];
    2410
    2411         // Special case for ID field
    2412         } else if($fieldName == 'ID') {
    2413             return new PrimaryKey($fieldName, $this);
    2414
    2415         // General casting information for items in $db or $casting
    2416         } else if($helper = $this->castingHelper($fieldName)) {
    2417             $obj = Object::create_from_string($helper, $fieldName);
    2418             $obj->setValue($this->$fieldName, $this->record, false);
    2419             return $obj;
    2420
    2421         // Special case for has_one relationships
    2422         } else if(preg_match('/ID$/', $fieldName) && $this->has_one(substr($fieldName,0,-2))) {
    2423             $val = (isset($this->record[$fieldName])) ? $this->record[$fieldName] : null;
    2424             return DBField::create('ForeignKey', $val, $fieldName, $this);
    2425
    2426         // Special case for ClassName
    2427         } else if($fieldName == 'ClassName') {
    2428             $val = get_class($this);
    2429             return DBField::create('Varchar', $val, $fieldName, $this);
    2430         }
    2431     }
    2432
    2433     /**
    2434      * Traverses to a DBField referenced by relationships between data objects.
    2435      * The path to the related field is specified with dot separated syntax (eg: Parent.Child.Child.FieldName)
    2436      *
    2437      * @param $fieldPath string
    2438      * @return DBField
    2439      */
    2440     public function relObject($fieldPath) {
    2441         $parts = explode('.', $fieldPath);
    2442         $fieldName = array_pop($parts);
    2443         $component = $this;
    2444         foreach($parts as $relation) {
    2445             if ($rel = $component->has_one($relation)) {
    2446                 $component = singleton($rel);
    2447             } elseif ($rel = $component->has_many($relation)) {
    2448                 $component = singleton($rel);
    2449             } elseif ($rel = $component->many_many($relation)) {
    2450                 $component = singleton($rel[1]);
    2451             } elseif($className = $this->castingClass($relation)) {
    2452                 $component = $className;
    2453             }
    2454         }
    2455
    2456         $object = $component->dbObject($fieldName);
    2457
    2458         if (!($object instanceof DBField) && !($object instanceof ComponentSet)) {
    2459             // Todo: come up with a broader range of exception objects to describe differnet kinds of errors programatically
    2460             throw new Exception("Unable to traverse to related object field [$fieldPath] on [$this->class]");
    2461         }
    2462         return $object;
    2463     }
    2464
    2465     /**
    2466      * Temporary hack to return an association name, based on class, to get around the mangle
    2467      * of having to deal with reverse lookup of relationships to determine autogenerated foreign keys.
    2468      *
    2469      * @return String
    2470      */
    2471     public function getReverseAssociation($className) {
    2472         if (is_array($this->many_many())) {
    2473             $many_many = array_flip($this->many_many());
    2474             if (array_key_exists($className, $many_many)) return $many_many[$className];
    2475         }
    2476         if (is_array($this->has_many())) {
    2477             $has_many = array_flip($this->has_many());
    2478             if (array_key_exists($className, $has_many)) return $has_many[$className];
    2479         }
    2480         if (is_array($this->has_one())) {
    2481             $has_one = array_flip($this->has_one());
    2482             if (array_key_exists($className, $has_one)) return $has_one[$className];
    2483         }
    2484
    2485         return false;
    2486     }
    2487
    2488     /**
    2489      * Build a {@link SQLQuery} object to perform the given query.
    2490      *
    2491      * @param string $filter A filter to be inserted into the WHERE clause.
    2492      * @param string|array $sort A sort expression to be inserted into the ORDER BY clause. If omitted, self::$default_sort will be used.
    2493      * @param string|array $limit A limit expression to be inserted into the LIMIT clause.
    2494      * @param string $join A single join clause. This can be used for filtering, only 1 instance of each DataObject will be returned.
    2495      * @param boolean $restictClasses Restrict results to only objects of either this class of a subclass of this class
    2496      * @param string $having A filter to be inserted into the HAVING clause.
    2497      *
    2498      * @return SQLQuery Query built.
    2499      */
    2500     public function buildSQL($filter = "", $sort = "", $limit = "", $join = "", $restrictClasses = true, $having = "") {
    2501         // Cache the big hairy part of buildSQL
    2502         if(!isset(self::$cache_buildSQL_query[$this->class])) {
    2503             // Get the tables to join to
    2504             $tableClasses = ClassInfo::dataClassesFor($this->class);
    2505             if(!$tableClasses) {
    2506                 if(!ManifestBuilder::has_been_included()) {
    2507                     user_error("DataObjects have been requested before the manifest is loaded. Please ensure you are not querying the database in _config.php.", E_USER_ERROR);
    2508                 } else {
    2509                     user_error("DataObject::buildSQL: Can't find data classes (classes linked to tables) for $this->class. Please ensure you run dev/build after creating a new DataObject.", E_USER_ERROR);
    2510                 }
    2511             }
    2512
    2513             $baseClass = array_shift($tableClasses);
    2514
    2515
    2516             // $collidingFields will keep a list fields that appear in mulitple places in the class
    2517             // heirarchy for this table.  They will be dealt with more explicitly in the SQL query
    2518             // to ensure that junk data from other tables doesn't corrupt data objects
    2519             $collidingFields = array();
    2520
    2521             // Build our intial query
    2522             $query = new SQLQuery(array());
    2523             $query->from("\"$baseClass\"");
    2524
    2525             // Add SQL for multi-value fields on the base table
    2526             $databaseFields = self::database_fields($baseClass);
    2527             if($databaseFields) foreach($databaseFields as $k => $v) {
    2528                 if(!in_array($k, array('ClassName', 'LastEdited', 'Created')) && ClassInfo::classImplements($v, 'CompositeDBField')) {
    2529                     $this->dbObject($k)->addToQuery($query);
    2530                 } else {
    2531                     $query->select[$k] = "\"$baseClass\".\"$k\"";
    2532                 }
    2533             }
    2534             // Join all the tables
    2535             if($tableClasses && self::$subclass_access) {
    2536                 foreach($tableClasses as $tableClass) {
    2537                     $query->from[$tableClass] = "LEFT JOIN \"$tableClass\" ON \"$tableClass\".\"ID\" = \"$baseClass\".\"ID\"";
    2538
    2539                     // Add SQL for multi-value fields
    2540                     $databaseFields = self::database_fields($tableClass);
    2541                     $compositeFields = self::composite_fields($tableClass, false);
    2542                     if($databaseFields) foreach($databaseFields as $k => $v) {
    2543                         if(!isset($compositeFields[$k])) {
    2544                             // Update $collidingFields if necessary
    2545                             if(isset($query->select[$k])) {
    2546                                 if(!isset($collidingFields[$k])) $collidingFields[$k] = array($query->select[$k]);
    2547                                 $collidingFields[$k][] = "\"$tableClass\".\"$k\"";
    2548
    2549                             } else {
    2550                                 $query->select[$k] = "\"$tableClass\".\"$k\"";
    2551                             }
    2552                         }
    2553                     }
    2554                     if($compositeFields) foreach($compositeFields as $k => $v) {
    2555                         $dbO = $this->dbObject($k);
    2556                         if($dbO) $dbO->addToQuery($query);
    2557                     }
    2558                 }
    2559             }
    2560
    2561             // Resolve colliding fields
    2562             if($collidingFields) {
    2563                 foreach($collidingFields as $k => $collisions) {
    2564                     $caseClauses = array();
    2565                     foreach($collisions as $collision) {
    2566                         if(preg_match('/^"([^"]+)"/', $collision, $matches)) {
    2567                             $collisionBase = $matches[1];
    2568                             $collisionClasses = ClassInfo::subclassesFor($collisionBase);
    2569                             $caseClauses[] = "WHEN \"$baseClass\".\"ClassName\" IN ('"
    2570                                 . implode("', '", $collisionClasses) . "') THEN $collision";
    2571                         } else {
    2572                             user_error("Bad collision item '$collision'", E_USER_WARNING);
    2573                         }
    2574                     }
    2575                     $query->select[$k] = "CASE " . implode( " ", $caseClauses) . " ELSE NULL END"
    2576                         .  " AS \"$k\"";
    2577                 }
    2578             }
    2579
    2580
    2581             $query->select[] = "\"$baseClass\".\"ID\"";
    2582             $query->select[] = "CASE WHEN \"$baseClass\".\"ClassName\" IS NOT NULL THEN \"$baseClass\".\"ClassName\" ELSE '$baseClass' END AS \"RecordClassName\"";
    2583
    2584             // Get the ClassName values to filter to
    2585             $classNames = ClassInfo::subclassesFor($this->class);
    2586
    2587             if(!$classNames) {
    2588                 user_error("DataObject::get() Can't find data sub-classes for '$callerClass'");
    2589             }
    2590
    2591             // If querying the base class, don't bother filtering on class name
    2592             if($restrictClasses && $this->class != $baseClass) {
    2593                 // Get the ClassName values to filter to
    2594                 $classNames = ClassInfo::subclassesFor($this->class);
    2595                 if(!$classNames) {
    2596                     user_error("DataObject::get() Can't find data sub-classes for '$callerClass'");
    2597                 }
    2598
    2599                 $query->where[] = "\"$baseClass\".\"ClassName\" IN ('" . implode("','", $classNames) . "')";
    2600             }
    2601             self::$cache_buildSQL_query[$this->class] = clone $query;
    2602         } else {
    2603             $query = clone self::$cache_buildSQL_query[$this->class];
    2604
    2605         }
    2606
    2607         // Find a default sort
    2608         if(!$sort) {
    2609             $sort = $this->stat('default_sort');
    2610         }
    2611         // Add quoting to sort expression if it's a simple column name
    2612         if(preg_match('/^[A-Z][A-Z0-9_]*$/i', $sort)) $sort = "\"$sort\"";
    2613
    2614         $query->where($filter);
    2615         $query->orderby($sort);
    2616         $query->limit($limit);
    2617
    2618
    2619         if($having) {
    2620             $query->having[] = $having;
    2621         }
    2622
    2623         if($join) {
    2624             $query->from[] = $join;
    2625             // In order to group by unique columns we have to group by everything listed in the select
    2626             foreach($query->select as $field) {
    2627                 // Skip the _SortColumns; these are only going to be aggregate functions
    2628                 if(preg_match('/AS\s+\"?_SortColumn/', $field, $matches)) {
    2629
    2630                 // Identify columns with aliases, and ignore the alias.  Making use of the alias in
    2631                 // group by was causing problems when those queries were subsequently passed into
    2632                 // SQLQuery::unlimitedRowCount.
    2633                 } else if(preg_match('/^(.*)\s+AS\s+(\"[^"]+\")\s*$/', $field, $matches)) {
    2634                     $query->groupby[] = $matches[1];
    2635                 // Otherwise just use the field as is
    2636                 } else {
    2637                     $query->groupby[] = $field;
    2638                 }
    2639             }
    2640         }
    2641
    2642         return $query;
    2643     }
    2644
    2645     /**
    2646      * Cache for the hairy bit of buildSQL
    2647      */
    2648     private static $cache_buildSQL_query;
    2649
    2650     /**
    2651      * Like {@link buildSQL}, but applies the extension modifications.
    2652      *
    2653      * @uses DataObjectDecorator->augmentSQL()
    2654      *
    2655      * @param string $filter A filter to be inserted into the WHERE clause.
    2656      * @param string|array $sort A sort expression to be inserted into the ORDER BY clause. If omitted, self::$default_sort will be used.
    2657      * @param string|array $limit A limit expression to be inserted into the LIMIT clause.
    2658      * @param string $join A single join clause. This can be used for filtering, only 1 instance of each DataObject will be returned.
    2659      * @param string $having A filter to be inserted into the HAVING clause.
    2660      * @return SQLQuery Query built
    2661      */
    2662     public function extendedSQL($filter = "", $sort = "", $limit = "", $join = "", $having = ""){
    2663         $query = $this->buildSQL($filter, $sort, $limit, $join, true, $having);
    2664         $this->extend('augmentSQL', $query);
    2665         return $query;
    2666     }
    2667
    2668     /**
    2669      * Return all objects matching the filter
    2670      * sub-classes are automatically selected and included
    2671      *
    2672      * @param string $callerClass The class of objects to be returned
    2673      * @param string $filter A filter to be inserted into the WHERE clause.
    2674      * @param string|array $sort A sort expression to be inserted into the ORDER BY clause.  If omitted, self::$default_sort will be used.
    2675      * @param string $join A single join clause.  This can be used for filtering, only 1 instance of each DataObject will be returned.
    2676      * @param string|array $limit A limit expression to be inserted into the LIMIT clause.
    2677      * @param string $containerClass The container class to return the results in.
    2678      *
    2679      * @return mixed The objects matching the filter, in the class specified by $containerClass
    2680      */
    2681     public static function get($callerClass, $filter = "", $sort = "", $join = "", $limit = "", $containerClass = "DataObjectSet") {
    2682         return singleton($callerClass)->instance_get($filter, $sort, $join, $limit, $containerClass);
    2683     }
    2684
    2685     /**
    2686      * The internal function that actually performs the querying for get().
    2687      * DataObject::get("Table","filter") is the same as singleton("Table")->instance_get("filter")
    2688      *
    2689      * @param string $filter A filter to be inserted into the WHERE clause.
    2690      * @param string $sort A sort expression to be inserted into the ORDER BY clause.  If omitted, self::$default_sort will be used.
    2691      * @param string $join A single join clause.  This can be used for filtering, only 1 instance of each DataObject will be returned.
    2692      * @param string $limit A limit expression to be inserted into the LIMIT clause.
    2693      * @param string $containerClass The container class to return the results in.
    2694      *
    2695      * @return mixed The objects matching the filter, in the class specified by $containerClass
    2696      */
    2697     public function instance_get($filter = "", $sort = "", $join = "", $limit="", $containerClass = "DataObjectSet") {
    2698         if(!DB::isActive()) {
    2699             user_error("DataObjects have been requested before the database is ready. Please ensure your database connection details are correct, your database has been built, and that you are not trying to query the database in _config.php.", E_USER_ERROR);
    2700         }
    2701
    2702         $query = $this->extendedSQL($filter, $sort, $limit, $join);
    2703
    2704         $records = $query->execute();
    2705
    2706         $ret = $this->buildDataObjectSet($records, $containerClass, $query, $this->class);
    2707         if($ret) $ret->parseQueryLimit($query);
    2708
    2709         return $ret;
    2710     }
    2711
    2712     /**
    2713      * Take a database {@link SS_Query} and instanciate an object for each record.
    2714      *
    2715      * @param SS_Query|array $records The database records, a {@link SS_Query} object or an array of maps.
    2716      * @param string $containerClass The class to place all of the objects into.
    2717      *
    2718      * @return mixed The new objects in an object of type $containerClass
    2719      */
    2720     function buildDataObjectSet($records, $containerClass = "DataObjectSet", $query = null, $baseClass = null) {
    2721         foreach($records as $record) {
    2722             if(empty($record['RecordClassName'])) {
    2723                 $record['RecordClassName'] = $record['ClassName'];
    2724             }
    2725             if(class_exists($record['RecordClassName'])) {
    2726                 $results[] = new $record['RecordClassName']($record);
    2727             } else {
    2728                 if(!$baseClass) {
    2729                     user_error("Bad RecordClassName '{$record['RecordClassName']}' and "
    2730                         . "\$baseClass not set", E_USER_ERROR);
    2731                 } else if(!is_string($baseClass) || !class_exists($baseClass)) {
    2732                     user_error("Bad RecordClassName '{$record['RecordClassName']}' and bad "
    2733                         . "\$baseClass '$baseClass not set", E_USER_ERROR);
    2734                 }
    2735                 $results[] = new $baseClass($record);
    2736             }
    2737         }
    2738
    2739         if(isset($results)) {
    2740             return new $containerClass($results);
    2741         }
    2742     }
    2743
    2744     /**
    2745      * A cache used by get_one.
    2746      * @var array
    2747      */
    2748     protected static $cache_get_one;
    2749
    2750     /**
    2751      * Return the first item matching the given query.
    2752      * All calls to get_one() are cached.
    2753      *
    2754      * @param string $callerClass The class of objects to be returned
    2755      * @param string $filter A filter to be inserted into the WHERE clause
    2756      * @param boolean $cache Use caching
    2757      * @param string $orderby A sort expression to be inserted into the ORDER BY clause.
    2758      *
    2759      * @return DataObject The first item matching the query
    2760      */
    2761     public static function get_one($callerClass, $filter = "", $cache = true, $orderby = "") {
    2762         $SNG = singleton($callerClass);
    2763
    2764         $cacheKey = "{$filter}-{$orderby}";
    2765         if($extra = $SNG->extend('cacheKeyComponent')) {
    2766             $cacheKey .= '-' . implode("-", $extra);
    2767         }
    2768         $cacheKey = md5($cacheKey);
    2769
    2770         // Flush destroyed items out of the cache
    2771         if($cache && isset(DataObject::$cache_get_one[$callerClass][$cacheKey]) && DataObject::$cache_get_one[$callerClass][$cacheKey] instanceof DataObject && DataObject::$cache_get_one[$callerClass][$cacheKey]->destroyed) {
    2772             DataObject::$cache_get_one[$callerClass][$cacheKey
    2773             ] = false;
    2774         }
    2775         if(!$cache || !isset(DataObject::$cache_get_one[$callerClass][$cacheKey])) {
    2776             $item = $SNG->instance_get_one($filter, $orderby);
    2777             if($cache) {
    2778                 DataObject::$cache_get_one[$callerClass][$cacheKey] = $item;
    2779                 if(!DataObject::$cache_get_one[$callerClass][$cacheKey]) {
    2780                     DataObject::$cache_get_one[$callerClass][$cacheKey] = false;
    2781                 }
    2782             }
    2783         }
    2784         return $cache ? DataObject::$cache_get_one[$callerClass][$cacheKey] : $item;
    2785     }
    2786
    2787     /**
    2788      * Flush the cached results for all relations (has_one, has_many, many_many)
    2789      * Also clears any cached aggregate data
    2790      *
    2791      * @param boolean $persistant When true will also clear persistant data stored in the Cache system.
    2792      *                            When false will just clear session-local cached data
    2793      *
    2794      */
    2795     public function flushCache($persistant=true) {
    2796         if($persistant) Aggregate::flushCache($this->class);
    2797
    2798         if($this->class == 'DataObject') {
    2799             DataObject::$cache_get_one = array();
    2800             return;
    2801         }
    2802
    2803         $classes = ClassInfo::ancestry($this->class);
    2804         foreach($classes as $class) {
    2805             if(isset(self::$cache_get_one[$class])) unset(self::$cache_get_one[$class]);
    2806         }
    2807
    2808         $this->extend('flushCache');
    2809
    2810         $this->componentCache = array();
    2811     }
    2812
    2813     static function flush_and_destroy_cache() {
    2814         if(self::$cache_get_one) foreach(self::$cache_get_one as $class => $items) {
    2815             if(is_array($items)) foreach($items as $item) {
    2816                 if($item) $item->destroy();
    2817             }
    2818         }
    2819         self::$cache_get_one = array();
    2820     }
    2821
    2822     /**
    2823      * Reset internal caches, for example after test runs
    2824      */
    2825     static function reset() {
    2826         self::$cache_get_one = array();
    2827         self::$cache_buildSQL_query = array();
    2828     }
    2829
    2830     /**
    2831      * Does the hard work for get_one()
    2832      *
    2833      * @uses DataObjectDecorator->augmentSQL()
    2834      *
    2835      * @param string $filter A filter to be inserted into the WHERE clause
    2836      * @param string $orderby A sort expression to be inserted into the ORDER BY clause.
    2837      * @return DataObject The first item matching the query
    2838      */
    2839     public function instance_get_one($filter, $orderby = null) {
    2840         if(!DB::isActive()) {
    2841             user_error("DataObjects have been requested before the database is ready. Please ensure your database connection details are correct, your database has been built, and that you are not trying to query the database in _config.php.", E_USER_ERROR);
    2842         }
    2843
    2844         $query = $this->buildSQL($filter);
    2845         $query->limit = "1";
    2846         if($orderby) {
    2847             $query->orderby = $orderby;
    2848         }
    2849
    2850         $this->extend('augmentSQL', $query);
    2851
    2852         $records = $query->execute();
    2853         $records->rewind();
    2854         $record = $records->current();
    2855
    2856         if($record) {
    2857             // Mid-upgrade, the database can have invalid RecordClassName values that need to be guarded against.
    2858             if(class_exists($record['RecordClassName'])) {
    2859                 $record = new $record['RecordClassName']($record);
    2860             } else {
    2861                 $record = new $this->class($record);
    2862             }
    2863
    2864             // Rather than restrict classes at the SQL-query level, we now check once the object has been instantiated
    2865             // This lets us check up on weird errors where the class has been incorrectly set, and give warnings to our
    2866             // developers
    2867             return $record;
    2868         }
    2869     }
    2870
    2871     /**
    2872      * Return the given element, searching by ID
    2873      *
    2874      * @param string $callerClass The class of the object to be returned
    2875      * @param int $id The id of the element
    2876      * @param boolean $cache See {@link get_one()}
    2877      *
    2878      * @return DataObject The element
    2879      */
    2880     public static function get_by_id($callerClass, $id, $cache = true) {
    2881         if(is_numeric($id)) {
    2882             if(is_subclass_of(($callerClass), 'DataObject')) {
    2883                 $tableClasses = ClassInfo::dataClassesFor($callerClass);
    2884                 $baseClass = array_shift($tableClasses);
    2885                 return DataObject::get_one($callerClass,"\"$baseClass\".\"ID\" = $id", $cache);
    2886
    2887                 // This simpler code will be used by non-DataObject classes that implement DataObjectInterface
    2888             } else {
    2889                 return DataObject::get_one($callerClass,"\"ID\" = $id", $cache);
    2890             }
    2891         } else {
    2892             user_error("DataObject::get_by_id passed a non-numeric ID #$id", E_USER_WARNING);
    2893         }
    2894     }
    2895
    2896     /**
    2897      * Get the name of the base table for this object
    2898      */
    2899     public function baseTable() {
    2900         $tableClasses = ClassInfo::dataClassesFor($this->class);
    2901         return array_shift($tableClasses);
    2902     }
    2903
    2904     //-------------------------------------------------------------------------------------------//
    2905
    2906     /**
    2907      * Return the database indexes on this table.
    2908      * This array is indexed by the name of the field with the index, and
    2909      * the value is the type of index.
    2910      */
    2911     public function databaseIndexes() {
    2912         $has_one = $this->uninherited('has_one',true);
    2913         $classIndexes = $this->uninherited('indexes',true);
    2914         //$fileIndexes = $this->uninherited('fileIndexes', true);
    2915
    2916         $indexes = array();
    2917
    2918         if($has_one) {
    2919             foreach($has_one as $relationshipName => $fieldType) {
    2920                 $indexes[$relationshipName . 'ID'] = true;
    2921             }
    2922         }
    2923
    2924         if($classIndexes) {
    2925             foreach($classIndexes as $indexName => $indexType) {
    2926                 $indexes[$indexName] = $indexType;
    2927             }
    2928         }
    2929
    2930         if(get_parent_class($this) == "DataObject") {
    2931             $indexes['ClassName'] = true;
    2932         }
    2933
    2934         return $indexes;
    2935     }
    2936
    2937     /**
    2938      * Check the database schema and update it as necessary.
    2939      *
    2940      * @uses DataObjectDecorator->augmentDatabase()
    2941      */
    2942     public function requireTable() {
    2943         // Only build the table if we've actually got fields
    2944         $fields = self::database_fields($this->class);
    2945         $extensions = self::database_extensions($this->class);
    2946
    2947         $indexes = $this->databaseIndexes();
    2948
    2949         if($fields) {
    2950             $hasAutoIncPK = ($this->class == ClassInfo::baseDataClass($this->class));
    2951             DB::requireTable($this->class, $fields, $indexes, $hasAutoIncPK, $this->stat('create_table_options'), $extensions);
    2952         } else {
    2953             DB::dontRequireTable($this->class);
    2954         }
    2955
    2956         // Build any child tables for many_many items
    2957         if($manyMany = $this->uninherited('many_many', true)) {
    2958             $extras = $this->uninherited('many_many_extraFields', true);
    2959             foreach($manyMany as $relationship => $childClass) {
    2960                 // Build field list
    2961                 $manymanyFields = array(
    2962                     "{$this->class}ID" => "Int",
    2963                 (($this->class == $childClass) ? "ChildID" : "{$childClass}ID") => "Int",
    2964                 );
    2965                 if(isset($extras[$relationship])) {
    2966                     $manymanyFields = array_merge($manymanyFields, $extras[$relationship]);
    2967                 }
    2968
    2969                 // Build index list
    2970                 $manymanyIndexes = array(
    2971                     "{$this->class}ID" => true,
    2972                 (($this->class == $childClass) ? "ChildID" : "{$childClass}ID") => true,
    2973                 );
    2974
    2975                 DB::requireTable("{$this->class}_$relationship", $manymanyFields, $manymanyIndexes, true, null, $extensions);
    2976             }
    2977         }
    2978
    2979         // Let any extentions make their own database fields
    2980         $this->extend('augmentDatabase', $dummy);
    2981     }
    2982
    2983     /**
    2984      * Add default records to database. This function is called whenever the
    2985      * database is built, after the database tables have all been created. Overload
    2986      * this to add default records when the database is built, but make sure you
    2987      * call parent::requireDefaultRecords().
    2988      *
    2989      * @uses DataObjectDecorator->requireDefaultRecords()
    2990      */
    2991     public function requireDefaultRecords() {
    2992         $defaultRecords = $this->stat('default_records');
    2993
    2994         if(!empty($defaultRecords)) {
    2995             $hasData = DataObject::get_one($this->class);
    2996             if(!$hasData) {
    2997                 $className = $this->class;
    2998                 foreach($defaultRecords as $record) {
    2999                     $obj = new $className($record);
    3000                     $obj->write();
    3001                 }
    3002                 DB::alteration_message("Added default records to $className table","created");
    3003             }
    3004         }
    3005
    3006         // Let any extentions make their own database default data
    3007         $this->extend('requireDefaultRecords', $dummy);
    3008     }
    3009
    3010     /**
    3011      * @see DataObject::database_fields()
    3012      */
    3013     public function databaseFields() {
    3014         user_error("databaseFields() is deprecated; use self::database_fields() "
    3015             . "instead", E_USER_NOTICE);
    3016         return self::database_fields($this->class);
    3017     }
    3018
    3019     /**
    3020      * @see DataObject::custom_database_fields()
    3021      */
    3022     public function customDatabaseFields() {
    3023         user_error("customDatabaseFields() is deprecated; use self::custom_database_fields() "
    3024             . "instead", E_USER_NOTICE);
    3025         return self::custom_database_fields($this->class);
    3026     }
    3027
    3028     /**
    3029      * Returns fields bu traversing the class heirachy in a bottom-up direction.
    3030      *
    3031      * Needed to avoid getCMSFields being empty when customDatabaseFields overlooks
    3032      * the inheritance chain of the $db array, where a child data object has no $db array,
    3033      * but still needs to know the properties of its parent. This should be merged into databaseFields or
    3034      * customDatabaseFields.
    3035      *
    3036      * @todo review whether this is still needed after recent API changes
    3037      */
    3038     public function inheritedDatabaseFields() {
    3039         $fields     = array();
    3040         $currentObj = $this->class;
    3041
    3042         while($currentObj != 'DataObject') {
    3043             $fields     = array_merge($fields, self::custom_database_fields($currentObj));
    3044             $currentObj = get_parent_class($currentObj);
    3045         }
    3046
    3047         return (array) $fields;
    3048     }
    3049
    3050     /**
    3051      * Get the default searchable fields for this object,
    3052      * as defined in the $searchable_fields list. If searchable
    3053      * fields are not defined on the data object, uses a default
    3054      * selection of summary fields.
    3055      *
    3056      * @return array
    3057      */
    3058     public function searchableFields() {
    3059         // can have mixed format, need to make consistent in most verbose form
    3060         $fields = $this->stat('searchable_fields');
    3061
    3062         $labels = $this->fieldLabels();
    3063
    3064         // fallback to summary fields
    3065         if(!$fields) $fields = array_keys($this->summaryFields());
    3066
    3067         // we need to make sure the format is unified before
    3068         // augmenting fields, so decorators can apply consistent checks
    3069         // but also after augmenting fields, because the decorator
    3070         // might use the shorthand notation as well
    3071
    3072         // rewrite array, if it is using shorthand syntax
    3073         $rewrite = array();
    3074         foreach($fields as $name => $specOrName) {
    3075             $identifer = (is_int($name)) ? $specOrName : $name;
    3076
    3077             if(is_int($name)) {
    3078                 // Format: array('MyFieldName')
    3079                 $rewrite[$identifer] = array();
    3080             } elseif(is_array($specOrName)) {
    3081                 // Format: array('MyFieldName' => array(
    3082                 //   'filter => 'ExactMatchFilter',
    3083                 //   'field' => 'NumericField', // optional
    3084                 //   'title' => 'My Title', // optiona.
    3085                 // ))
    3086                 $rewrite[$identifer] = array_merge(
    3087                     array('filter' => $this->relObject($identifer)->stat('default_search_filter_class')),
    3088                     (array)$specOrName
    3089                 );
    3090             } else {
    3091                 // Format: array('MyFieldName' => 'ExactMatchFilter')
    3092                 $rewrite[$identifer] = array(
    3093                     'filter' => $specOrName,
    3094                 );
    3095             }
    3096             if(!isset($rewrite[$identifer]['title'])) {
    3097                 $rewrite[$identifer]['title'] = (isset($labels[$identifer])) ? $labels[$identifer] : FormField::name_to_label($identifer);
    3098             }
    3099             if(!isset($rewrite[$identifer]['filter'])) {
    3100                 $rewrite[$identifer]['filter'] = 'PartialMatchFilter';
    3101             }
    3102         }
    3103
    3104         $fields = $rewrite;
    3105
    3106         // apply DataObjectDecorators if present
    3107         $this->extend('updateSearchableFields', $fields);
    3108
    3109         return $fields;
    3110     }
    3111
    3112     /**
    3113      * Get any user defined searchable fields labels that
    3114      * exist. Allows overriding of default field names in the form
    3115      * interface actually presented to the user.
    3116      *
    3117      * The reason for keeping this separate from searchable_fields,
    3118      * which would be a logical place for this functionality, is to
    3119      * avoid bloating and complicating the configuration array. Currently
    3120      * much of this system is based on sensible defaults, and this property
    3121      * would generally only be set in the case of more complex relationships
    3122      * between data object being required in the search interface.
    3123      *
    3124      * Generates labels based on name of the field itself, if no static property
    3125      * {@link self::field_labels} exists.
    3126      *
    3127      * @uses $field_labels
    3128      * @uses FormField::name_to_label()
    3129      *
    3130      * @param boolean $includerelations a boolean value to indicate if the labels returned include relation fields
    3131      *
    3132      * @return array|string Array of all element labels if no argument given, otherwise the label of the field
    3133      */
    3134     public function fieldLabels($includerelations = true) {
    3135         $customLabels = $this->stat('field_labels');
    3136         $autoLabels = array();
    3137
    3138         // get all translated static properties as defined in i18nCollectStatics()
    3139         $ancestry = ClassInfo::ancestry($this->class);
    3140         $ancestry = array_reverse($ancestry);
    3141         if($ancestry) foreach($ancestry as $ancestorClass) {
    3142             if($ancestorClass == 'ViewableData') break;
    3143             $types = array();
    3144             $types['db'] = array_merge(
    3145                 array('ClassName'=>1, 'Created'=>1, 'LastEdited'=>1),
    3146                 (array) Object::uninherited_static($ancestorClass, 'db')
    3147             );
    3148             if($includerelations){
    3149                 $types['has_one'] = (array)singleton($ancestorClass)->uninherited('has_one', true);
    3150                 $types['has_many'] = (array)singleton($ancestorClass)->uninherited('has_many', true);
    3151                 $types['many_many'] = (array)singleton($ancestorClass)->uninherited('many_many', true);
    3152             }
    3153             foreach($types as $type => $attrs) {
    3154                 foreach($attrs as $name => $spec) {
    3155                     $label = FormField::name_to_label($name);
    3156                     if (!isset($autoLabels[$name]) || $autoLabels[$name] == $label || preg_match("/{$type}_{$name}/", $autoLabels[$name]))
    3157                         $autoLabels[$name] = _t("{$ancestorClass}.{$type}_{$name}", $label);
    3158                 }
    3159             }
    3160
    3161         }
    3162
    3163         $labels = array_merge((array)$autoLabels, (array)$customLabels);
    3164
    3165         $this->extend('updateFieldLabels', $labels);
    3166
    3167         return $labels;
    3168     }
    3169
    3170     /**
    3171      * Get a human-readable label for a single field,
    3172      * see {@link fieldLabels()} for more details.
    3173      *
    3174      * @uses fieldLabels()
    3175      * @uses FormField::name_to_label()
    3176      *
    3177      * @param string $name Name of the field
    3178      * @return string Label of the field
    3179      */
    3180     public function fieldLabel($name) {
    3181         $labels = $this->fieldLabels();
    3182         return (isset($labels[$name])) ? $labels[$name] : FormField::name_to_label($name);
    3183     }
    3184
    3185     /**
    3186      * Get the default summary fields for this object.
    3187      *
    3188      * @todo use the translation apparatus to return a default field selection for the language
    3189      *
    3190      * @return array
    3191      */
    3192     public function summaryFields(){
    3193
    3194         $fields = $this->stat('summary_fields');
    3195
    3196         // if fields were passed in numeric array,
    3197         // convert to an associative array
    3198         if($fields && array_key_exists(0, $fields)) {
    3199             $localized = array();
    3200             foreach ($fields as $name) {
    3201                 $localized[$name] = $this->fieldLabel($name);
    3202             }
    3203             $fields = $localized;
    3204         }
    3205
    3206         if (!$fields) {
    3207             $fields = array();
    3208             // try to scaffold a couple of usual suspects
    3209             if ($this->hasField('Name')) $fields['Name'] = $this->fieldLabel('Name');
    3210             if ($this->hasDataBaseField('Title')) $fields['Title'] = $this->fieldLabel('Title');
    3211             if ($this->hasField('Description')) $fields['Description'] = $this->fieldLabel('Description');
    3212             if ($this->hasField('FirstName')) $fields['FirstName'] = $this->fieldLabel('FirstName');
    3213         }
    3214         $this->extend("updateSummaryFields", $fields);
    3215
    3216         // Final fail-over, just list ID field
    3217         if(!$fields) $fields['ID'] = 'ID';
    3218
    3219         return $fields;
    3220     }
    3221
    3222     /**
    3223      * Defines a default list of filters for the search context.
    3224      *
    3225      * If a filter class mapping is defined on the data object,
    3226      * it is constructed here. Otherwise, the default filter specified in
    3227      * {@link DBField} is used.
    3228      *
    3229      * @todo error handling/type checking for valid FormField and SearchFilter subclasses?
    3230      *
    3231      * @return array
    3232      */
    3233     public function defaultSearchFilters() {
    3234         $filters = array();
    3235         foreach($this->searchableFields() as $name => $spec) {
    3236             $filterClass = $spec['filter'];
    3237             // if $filterClass is not set a name of any subclass of SearchFilter than assing 'PartiailMatchFilter' to it
    3238             if (!is_subclass_of($filterClass, 'SearchFilter')) {
    3239                 $filterClass = 'PartialMatchFilter';
    3240             }
    3241             $filters[$name] = new $filterClass($name);
    3242         }
    3243         return $filters;
    3244     }
    3245
    3246     /**
    3247      * @return boolean True if the object is in the database
    3248      */
    3249     public function isInDB() {
    3250         return is_numeric( $this->ID ) && $this->ID > 0;
    3251     }
    3252
    3253     /**
    3254      * Sets a 'context object' that can be used to provide hints about how to process a particular get / get_one request.
    3255      * In particular, DataObjectDecorators can use this to amend queries more effectively.
    3256      * Care must be taken to unset the context object after you're done with it, otherwise you will have a stale context,
    3257      * which could cause horrible bugs.
    3258      */
    3259     public static function set_context_obj($obj) {
    3260         if($obj && self::$context_obj) user_error("Dataobject::set_context_obj passed " . $obj->class . "." . $obj->ID . " when there is already a context: " . self::$context_obj->class . '.' . self::$context_obj->ID, E_USER_WARNING);
    3261         self::$context_obj = $obj;
    3262     }
    3263
    3264     /**
    3265      * Retrieve the current context object.
    3266      */
    3267     public static function context_obj() {
    3268         return self::$context_obj;
    3269     }
    3270
    3271     /**
    3272      * @ignore
    3273      */
    3274     protected static $context_obj = null;
    3275
    3276     /*
    3277      * @ignore
    3278      */
    3279     private static $subclass_access = true;
    3280
    3281     /**
    3282      * Temporarily disable subclass access in data object qeur
    3283      */
    3284     static function disable_subclass_access() {
    3285         self::$subclass_access = false;
    3286     }
    3287     static function enable_subclass_access() {
    3288         self::$subclass_access = true;
    3289     }
    3290
    3291     //-------------------------------------------------------------------------------------------//
    3292
    3293     /**
    3294      * Database field definitions.
    3295      * This is a map from field names to field type. The field
    3296      * type should be a class that extends .
    3297      * @var array
    3298      */
    3299     public static $db = null;
    3300
    3301     /**
    3302      * Use a casting object for a field. This is a map from
    3303      * field name to class name of the casting object.
    3304      * @var array
    3305      */
    3306     public static $casting = array(
    3307         "LastEdited" => "SS_Datetime",
    3308         "Created" => "SS_Datetime",
    3309         "Title" => 'Text',
    3310     );
    3311
    3312     /**
    3313      * Specify custom options for a CREATE TABLE call.
    3314      * Can be used to specify a custom storage engine for specific database table.
    3315      * All options have to be keyed for a specific database implementation,
    3316      * identified by their class name (extending from {@link SS_Database}).
    3317      *
    3318      * <code>
    3319      * array(
    3320      *  'MySQLDatabase' => 'ENGINE=MyISAM'
    3321      * )
    3322      * </code>
    3323      *
    3324      * Caution: This API is experimental, and might not be
    3325      * included in the next major release. Please use with care.
    3326      *
    3327      * @var array
    3328      */
    3329     static $create_table_options = array(
    3330         'MySQLDatabase' => 'ENGINE=MyISAM'
    3331     );
    3332
    3333     /**
    3334      * If a field is in this array, then create a database index
    3335      * on that field. This is a map from fieldname to index type.
    3336      * See {@link SS_Database->requireIndex()} and custom subclasses for details on the array notation.
    3337      *
    3338      * @var array
    3339      */
    3340     public static $indexes = null;
    3341
    3342     /**
    3343      * Inserts standard column-values when a DataObject
    3344      * is instanciated. Does not insert default records {@see $default_records}.
    3345      * This is a map from fieldname to default value.
    3346      *
    3347      *  - If you would like to change a default value in a sub-class, just specify it.
    3348      *  - If you would like to disable the default value given by a parent class, set the default value to 0,'',or false in your
    3349      *    subclass.  Setting it to null won't work.
    3350      *
    3351      * @var array
    3352      */
    3353     public static $defaults = null;
    3354
    3355     /**
    3356      * Multidimensional array which inserts default data into the database
    3357      * on a db/build-call as long as the database-table is empty. Please use this only
    3358      * for simple constructs, not for SiteTree-Objects etc. which need special
    3359      * behaviour such as publishing and ParentNodes.
    3360      *
    3361      * Example:
    3362      * array(
    3363      *  array('Title' => "DefaultPage1", 'PageTitle' => 'page1'),
    3364      *  array('Title' => "DefaultPage2")
    3365      * ).
    3366      *
    3367      * @var array
    3368      */
    3369     public static $default_records = null;
    3370
    3371     /**
    3372      * One-to-zero relationship defintion. This is a map of component name to data type. In order to turn this into a
    3373      * true one-to-one relationship you can add a {@link DataObject::$belongs_to} relationship on the child class.
    3374      *
    3375      * Note that you cannot have a has_one and belongs_to relationship with the same name.
    3376      *
    3377      *  @var array
    3378      */
    3379     public static $has_one = null;
    3380
    3381     /**
    3382      * A meta-relationship that allows you to define the reverse side of a {@link DataObject::$has_one}.
    3383      *
    3384      * This does not actually create any data structures, but allows you to query the other object in a one-to-one
    3385      * relationship from the child object. If you have multiple belongs_to links to another object you can use the
    3386      * syntax "ClassName.HasOneName" to specify which foreign has_one key on the other object to use.
    3387      *
    3388      * Note that you cannot have a has_one and belongs_to relationship with the same name.
    3389      *
    3390      * @var array
    3391      */
    3392     public static $belongs_to;
    3393
    3394     /**
    3395      * This defines a one-to-many relationship. It is a map of component name to the remote data class.
    3396      *
    3397      * This relationship type does not actually create a data structure itself - you need to define a matching $has_one
    3398      * relationship on the child class. Also, if the $has_one relationship on the child class has multiple links to this
    3399      * class you can use the syntax "ClassName.HasOneRelationshipName" in the remote data class definition to show
    3400      * which foreign key to use.
    3401      *
    3402      * @var array
    3403      */
    3404     public static $has_many = null;
    3405
    3406     /**
    3407      * many-many relationship definitions.
    3408      * This is a map from component name to data type.
    3409      * @var array
    3410      */
    3411     public static $many_many = null;
    3412
    3413     /**
    3414      * Extra fields to include on the connecting many-many table.
    3415      * This is a map from field name to field type.
    3416      *
    3417      * Example code:
    3418      * <code>
    3419      * public static $many_many_extraFields = array(
    3420      *  'Members' => array(
    3421      *          'Role' => 'Varchar(100)'
    3422      *      )
    3423      * );
    3424      * </code>
    3425      *
    3426      * @var array
    3427      */
    3428     public static $many_many_extraFields = null;
    3429
    3430     /**
    3431      * The inverse side of a many-many relationship.
    3432      * This is a map from component name to data type.
    3433      * @var array
    3434      */
    3435     public static $belongs_many_many = null;
    3436
    3437     /**
    3438      * The default sort expression. This will be inserted in the ORDER BY
    3439      * clause of a SQL query if no other sort expression is provided.
    3440      * @var string
    3441      */
    3442     public static $default_sort = null;
    3443
    3444     /**
    3445      * Default list of fields that can be scaffolded by the ModelAdmin
    3446      * search interface.
    3447      *
    3448      * Overriding the default filter, with a custom defined filter:
    3449      * <code>
    3450      *  static $searchable_fields = array(
    3451      *     "Name" => "PartialMatchFilter"
    3452      *  );
    3453      * </code>
    3454      *
    3455      * Overriding the default form fields, with a custom defined field.
    3456      * The 'filter' parameter will be generated from {@link DBField::$default_search_filter_class}.
    3457      * The 'title' parameter will be generated from {@link DataObject->fieldLabels()}.
    3458      * <code>
    3459      *  static $searchable_fields = array(
    3460      *     "Name" => array(
    3461      *          "field" => "TextField"
    3462      *      )
    3463      *  );
    3464      * </code>
    3465      *
    3466      * Overriding the default form field, filter and title:
    3467      * <code>
    3468      *  static $searchable_fields = array(
    3469      *     "Organisation.ZipCode" => array(
    3470      *          "field" => "TextField",
    3471      *          "filter" => "PartialMatchFilter",
    3472      *          "title" => 'Organisation ZIP'
    3473      *      )
    3474      *  );
    3475      * </code>
    3476      */
    3477     public static $searchable_fields = null;
    3478
    3479     /**
    3480      * User defined labels for searchable_fields, used to override
    3481      * default display in the search form.
    3482      */
    3483     public static $field_labels = null;
    3484
    3485     /**
    3486      * Provides a default list of fields to be used by a 'summary'
    3487      * view of this object.
    3488      */
    3489     public static $summary_fields = null;
    3490
    3491     /**
    3492      * Provides a list of allowed methods that can be called via RESTful api.
    3493      */
    3494     public static $allowed_actions = null;
    3495
    3496     /**
    3497      * Collect all static properties on the object
    3498      * which contain natural language, and need to be translated.
    3499      * The full entity name is composed from the class name and a custom identifier.
    3500      *
    3501      * @return array A numerical array which contains one or more entities in array-form.
    3502      * Each numeric entity array contains the "arguments" for a _t() call as array values:
    3503      * $entity, $string, $priority, $context.
    3504      */
    3505     public function provideI18nEntities() {
    3506         $entities = array();
    3507
    3508         $entities["{$this->class}.SINGULARNAME"] = array(
    3509             $this->singular_name(),
    3510             PR_MEDIUM,
    3511             'Singular name of the object, used in dropdowns and to generally identify a single object in the interface'
    3512         );
    3513
    3514         $entities["{$this->class}.PLURALNAME"] = array(
    3515             $this->plural_name(),
    3516             PR_MEDIUM,
    3517             'Pural name of the object, used in dropdowns and to generally identify a collection of this object in the interface'
    3518         );
    3519
    3520         return $entities;
    3521     }
    3522
    3523     /**
    3524      * Returns true if the given method/parameter has a value
    3525      * (Uses the DBField::hasValue if the parameter is a database field)
    3526      *
    3527      * @param string $field The field name
    3528      * @param array $arguments
    3529      * @param bool $cache
    3530      * @return boolean
    3531      */
    3532     function hasValue($field, $arguments = null, $cache = true) {
    3533         $obj = $this->dbObject($field);
    3534         if($obj) {
    3535             return $obj->hasValue();
    3536         } else {
    3537             return parent::hasValue($field, $arguments, $cache);
    3538         }
    3539     }
    3540
    3541 }

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -