/**
  * Return the DBField object that represents the given field.
  * This works similarly to obj() with 2 key differences:
  *   - it still returns an object even when the field has no value.
  *   - it only matches fields and not methods
  *   - it matches foreign keys generated by has_one relationships, eg, "ParentID"
  *
  * @param string $fieldName Name of the field
  * @return DBField The field as a DBField object
  */
 public function dbObject($fieldName)
 {
     // Check for field in DB
     $helper = static::getSchema()->fieldSpec(static::class, $fieldName, DataObjectSchema::INCLUDE_CLASS);
     if (!$helper) {
         return null;
     }
     $value = isset($this->record[$fieldName]) ? $this->record[$fieldName] : null;
     // If we have a DBField object in $this->record, then return that
     if ($value instanceof DBField) {
         return $value;
     }
     list($table, $spec) = explode('.', $helper);
     /** @var DBField $obj */
     $obj = Object::create_from_string($spec, $fieldName);
     $obj->setTable($table);
     $obj->setValue($value, $this, false);
     return $obj;
 }
 /**
  * Get the value of a field on this object, automatically inserting the value into any available casting objects
  * that have been specified.
  *
  * @param string $fieldName
  * @param array $arguments
  * @param bool $cache Cache this object
  * @param string $cacheName a custom cache name
  * @return Object|DBField
  */
 public function obj($fieldName, $arguments = [], $cache = false, $cacheName = null)
 {
     if (!$cacheName && $cache) {
         $cacheName = $this->objCacheName($fieldName, $arguments);
     }
     // Check pre-cached value
     $value = $cache ? $this->objCacheGet($cacheName) : null;
     if ($value !== null) {
         return $value;
     }
     // Load value from record
     if ($this->hasMethod($fieldName)) {
         $value = call_user_func_array(array($this, $fieldName), $arguments ?: []);
     } else {
         $value = $this->{$fieldName};
     }
     // Cast object
     if (!is_object($value)) {
         // Force cast
         $castingHelper = $this->castingHelper($fieldName);
         $valueObject = Object::create_from_string($castingHelper, $fieldName);
         $valueObject->setValue($value, $this);
         $value = $valueObject;
     }
     // Record in cache
     if ($cache) {
         $this->objCacheSet($cacheName, $value);
     }
     return $value;
 }
 public function testCreate()
 {
     /** @var DBHTMLText $field */
     $field = Object::create_from_string("HTMLFragment(['whitelist' => 'link'])", 'MyField');
     $this->assertEquals(['link'], $field->getWhitelist());
     $field = Object::create_from_string("HTMLFragment(['whitelist' => 'link,a'])", 'MyField');
     $this->assertEquals(['link', 'a'], $field->getWhitelist());
     $field = Object::create_from_string("HTMLFragment(['whitelist' => ['link', 'a']])", 'MyField');
     $this->assertEquals(['link', 'a'], $field->getWhitelist());
     $field = Object::create_from_string("HTMLFragment", 'MyField');
     $this->assertEmpty($field->getWhitelist());
     // Test shortcodes
     $field = Object::create_from_string("HTMLFragment(['shortcodes' => true])", 'MyField');
     $this->assertEquals(true, $field->getProcessShortcodes());
     $field = Object::create_from_string("HTMLFragment(['shortcodes' => false])", 'MyField');
     $this->assertEquals(false, $field->getProcessShortcodes());
     // Mix options
     $field = Object::create_from_string("HTMLFragment(['shortcodes' => true, 'whitelist' => ['a'])", 'MyField');
     $this->assertEquals(true, $field->getProcessShortcodes());
     $this->assertEquals(['a'], $field->getWhitelist());
 }
 /**
  * Get a db object for the named field
  *
  * @param string $field Field name
  * @return DBField|null
  */
 public function dbObject($field)
 {
     $fields = $this->compositeDatabaseFields();
     if (!isset($fields[$field])) {
         return null;
     }
     // Build nested field
     $key = $this->getName() . $field;
     $spec = $fields[$field];
     /** @var DBField $fieldObject */
     $fieldObject = Object::create_from_string($spec, $key);
     $fieldObject->setValue($this->getField($field), null, false);
     return $fieldObject;
 }
 protected function constructExtensions()
 {
     $class = get_class($this);
     // Register this trait as a method source
     $this->registerExtraMethodCallback('defineExtensionMethods', function () {
         $this->defineExtensionMethods();
     });
     // Setup all extension instances for this instance
     foreach (ClassInfo::ancestry($class) as $class) {
         if (in_array($class, self::$unextendable_classes)) {
             continue;
         }
         $extensions = Config::inst()->get($class, 'extensions', Config::UNINHERITED | Config::EXCLUDE_EXTRA_SOURCES);
         if ($extensions) {
             foreach ($extensions as $extension) {
                 $instance = Object::create_from_string($extension);
                 $instance->setOwner(null, $class);
                 $this->extension_instances[$instance->class] = $instance;
             }
         }
     }
     if (!isset(self::$classes_constructed[$class])) {
         $this->defineMethods();
         self::$classes_constructed[$class] = true;
     }
 }
 public function testDefault()
 {
     /** @var DBString $dbField */
     $dbField = Object::create_from_string("StringFieldTest_MyStringField(['default' => 'Here is my default text'])", 'Myfield');
     $this->assertEquals("Here is my default text", $dbField->getDefaultValue());
 }
 /**
  * Add an item to this many_many relationship
  * Does so by adding an entry to the joinTable.
  *
  * @throws InvalidArgumentException
  * @throws Exception
  *
  * @param DataObject|int $item
  * @param array $extraFields A map of additional columns to insert into the joinTable.
  * Column names should be ANSI quoted.
  * @throws Exception
  */
 public function add($item, $extraFields = array())
 {
     // Ensure nulls or empty strings are correctly treated as empty arrays
     if (empty($extraFields)) {
         $extraFields = array();
     }
     // Determine ID of new record
     $itemID = null;
     if (is_numeric($item)) {
         $itemID = $item;
     } elseif ($item instanceof $this->dataClass) {
         $itemID = $item->ID;
     } else {
         throw new InvalidArgumentException("ManyManyList::add() expecting a {$this->dataClass} object, or ID value");
     }
     if (empty($itemID)) {
         throw new InvalidArgumentException("ManyManyList::add() doesn't accept unsaved records");
     }
     // Validate foreignID
     $foreignIDs = $this->getForeignID();
     if (empty($foreignIDs)) {
         throw new BadMethodCallException("ManyManyList::add() can't be called until a foreign ID is set");
     }
     // Apply this item to each given foreign ID record
     if (!is_array($foreignIDs)) {
         $foreignIDs = array($foreignIDs);
     }
     foreach ($foreignIDs as $foreignID) {
         // Check for existing records for this item
         if ($foreignFilter = $this->foreignIDWriteFilter($foreignID)) {
             // With the current query, simply add the foreign and local conditions
             // The query can be a bit odd, especially if custom relation classes
             // don't join expected tables (@see Member_GroupSet for example).
             $query = new SQLSelect("*", "\"{$this->joinTable}\"");
             $query->addWhere($foreignFilter);
             $query->addWhere(array("\"{$this->joinTable}\".\"{$this->localKey}\"" => $itemID));
             $hasExisting = $query->count() > 0;
         } else {
             $hasExisting = false;
         }
         // Blank manipulation
         $manipulation = array($this->joinTable => array('command' => $hasExisting ? 'update' : 'insert', 'fields' => array()));
         if ($hasExisting) {
             $manipulation[$this->joinTable]['where'] = array("\"{$this->joinTable}\".\"{$this->foreignKey}\"" => $foreignID, "\"{$this->joinTable}\".\"{$this->localKey}\"" => $itemID);
         }
         if ($extraFields && $this->extraFields) {
             // Write extra field to manipluation in the same way
             // that DataObject::prepareManipulationTable writes fields
             foreach ($this->extraFields as $fieldName => $fieldSpec) {
                 // Skip fields without an assignment
                 if (array_key_exists($fieldName, $extraFields)) {
                     $fieldObject = Object::create_from_string($fieldSpec, $fieldName);
                     $fieldObject->setValue($extraFields[$fieldName]);
                     $fieldObject->writeToManipulation($manipulation[$this->joinTable]);
                 }
             }
         }
         $manipulation[$this->joinTable]['fields'][$this->localKey] = $itemID;
         $manipulation[$this->joinTable]['fields'][$this->foreignKey] = $foreignID;
         DB::manipulate($manipulation);
     }
 }
 /**
  * Cache all database and composite fields for the given class.
  * Will do nothing if already cached
  *
  * @param string $class Class name to cache
  */
 protected function cacheDatabaseFields($class)
 {
     // Skip if already cached
     if (isset($this->databaseFields[$class]) && isset($this->compositeFields[$class])) {
         return;
     }
     $compositeFields = array();
     $dbFields = array();
     // Ensure fixed fields appear at the start
     $fixedFields = DataObject::config()->get('fixed_fields');
     if (get_parent_class($class) === DataObject::class) {
         // Merge fixed with ClassName spec and custom db fields
         $dbFields = $fixedFields;
     } else {
         $dbFields['ID'] = $fixedFields['ID'];
     }
     // Check each DB value as either a field or composite field
     $db = Config::inst()->get($class, 'db', Config::UNINHERITED) ?: array();
     foreach ($db as $fieldName => $fieldSpec) {
         $fieldClass = strtok($fieldSpec, '(');
         if (singleton($fieldClass) instanceof DBComposite) {
             $compositeFields[$fieldName] = $fieldSpec;
         } else {
             $dbFields[$fieldName] = $fieldSpec;
         }
     }
     // Add in all has_ones
     $hasOne = Config::inst()->get($class, 'has_one', Config::UNINHERITED) ?: array();
     foreach ($hasOne as $fieldName => $hasOneClass) {
         if ($hasOneClass === DataObject::class) {
             $compositeFields[$fieldName] = 'PolymorphicForeignKey';
         } else {
             $dbFields["{$fieldName}ID"] = 'ForeignKey';
         }
     }
     // Merge composite fields into DB
     foreach ($compositeFields as $fieldName => $fieldSpec) {
         $fieldObj = Object::create_from_string($fieldSpec, $fieldName);
         $fieldObj->setTable($class);
         $nestedFields = $fieldObj->compositeDatabaseFields();
         foreach ($nestedFields as $nestedName => $nestedSpec) {
             $dbFields["{$fieldName}{$nestedName}"] = $nestedSpec;
         }
     }
     // Prevent field-less tables with only 'ID'
     if (count($dbFields) < 2) {
         $dbFields = [];
     }
     // Return cached results
     $this->databaseFields[$class] = $dbFields;
     $this->compositeFields[$class] = $compositeFields;
 }
 /**
  * Generate the following table in the database, modifying whatever already exists
  * as necessary.
  *
  * @todo Change detection for CREATE TABLE $options other than "Engine"
  *
  * @param string $table The name of the table
  * @param array $fieldSchema A list of the fields to create, in the same form as DataObject::$db
  * @param array $indexSchema A list of indexes to create. See {@link requireIndex()}
  * The values of the array can be one of:
  *   - true: Create a single column index on the field named the same as the index.
  *   - array('fields' => array('A','B','C'), 'type' => 'index/unique/fulltext'): This gives you full
  *     control over the index.
  * @param boolean $hasAutoIncPK A flag indicating that the primary key on this table is an autoincrement type
  * @param array $options Create table options (ENGINE, etc.)
  * @param array|bool $extensions List of extensions
  */
 public function requireTable($table, $fieldSchema = null, $indexSchema = null, $hasAutoIncPK = true, $options = array(), $extensions = false)
 {
     if (!isset($this->tableList[strtolower($table)])) {
         $this->transCreateTable($table, $options, $extensions);
         $this->alterationMessage("Table {$table}: created", "created");
     } else {
         if (Config::inst()->get(static::class, 'fix_table_case_on_build')) {
             $this->fixTableCase($table);
         }
         if (Config::inst()->get(static::class, 'check_and_repair_on_build')) {
             $this->checkAndRepairTable($table);
         }
         // Check if options changed
         $tableOptionsChanged = false;
         // Check for DB constant on the schema class
         $dbIDName = sprintf('%s::ID', get_class($this));
         $dbID = defined($dbIDName) ? constant($dbIDName) : null;
         if ($dbID && isset($options[$dbID])) {
             if (preg_match('/ENGINE=([^\\s]*)/', $options[$dbID], $alteredEngineMatches)) {
                 $alteredEngine = $alteredEngineMatches[1];
                 $tableStatus = $this->query(sprintf('SHOW TABLE STATUS LIKE \'%s\'', $table))->first();
                 $tableOptionsChanged = $tableStatus['Engine'] != $alteredEngine;
             }
         }
         if ($tableOptionsChanged || $extensions && $this->database->supportsExtensions($extensions)) {
             $this->transAlterTable($table, $options, $extensions);
         }
     }
     //DB ABSTRACTION: we need to convert this to a db-specific version:
     if (!isset($fieldSchema['ID'])) {
         $this->requireField($table, 'ID', $this->IdColumn(false, $hasAutoIncPK));
     }
     // Create custom fields
     if ($fieldSchema) {
         foreach ($fieldSchema as $fieldName => $fieldSpec) {
             //Is this an array field?
             $arrayValue = '';
             if (strpos($fieldSpec, '[') !== false) {
                 //If so, remove it and store that info separately
                 $pos = strpos($fieldSpec, '[');
                 $arrayValue = substr($fieldSpec, $pos);
                 $fieldSpec = substr($fieldSpec, 0, $pos);
             }
             /** @var DBField $fieldObj */
             $fieldObj = Object::create_from_string($fieldSpec, $fieldName);
             $fieldObj->setArrayValue($arrayValue);
             $fieldObj->setTable($table);
             if ($fieldObj instanceof DBPrimaryKey) {
                 /** @var DBPrimaryKey $fieldObj */
                 $fieldObj->setAutoIncrement($hasAutoIncPK);
             }
             $fieldObj->requireField();
         }
     }
     // Create custom indexes
     if ($indexSchema) {
         foreach ($indexSchema as $indexName => $indexDetails) {
             $this->requireIndex($table, $indexName, $indexDetails);
         }
     }
 }
 /**
  * Update the SELECT clause of the query with the columns from the given table
  *
  * @param SQLSelect $query
  * @param string $tableClass Class to select from
  * @param array $columns
  */
 protected function selectColumnsFromTable(SQLSelect &$query, $tableClass, $columns = null)
 {
     // Add SQL for multi-value fields
     $schema = DataObject::getSchema();
     $databaseFields = $schema->databaseFields($tableClass, false);
     $compositeFields = $schema->compositeFields($tableClass, false);
     unset($databaseFields['ID']);
     foreach ($databaseFields as $k => $v) {
         if ((is_null($columns) || in_array($k, $columns)) && !isset($compositeFields[$k])) {
             // Update $collidingFields if necessary
             $expressionForField = $query->expressionForField($k);
             $quotedField = $schema->sqlColumnForField($tableClass, $k);
             if ($expressionForField) {
                 if (!isset($this->collidingFields[$k])) {
                     $this->collidingFields[$k] = array($expressionForField);
                 }
                 $this->collidingFields[$k][] = $quotedField;
             } else {
                 $query->selectField($quotedField, $k);
             }
         }
     }
     foreach ($compositeFields as $k => $v) {
         if ((is_null($columns) || in_array($k, $columns)) && $v) {
             $tableName = $schema->tableName($tableClass);
             $dbO = Object::create_from_string($v, $k);
             $dbO->setTable($tableName);
             $dbO->addToQuery($query);
         }
     }
 }