Merge branch '8.x' of git.drupal.org:project/drupal into 8.x
commit
fe147abd4b
|
@ -222,6 +222,14 @@ const LANGUAGE_NOT_APPLICABLE = 'zxx';
|
|||
*/
|
||||
const LANGUAGE_MULTIPLE = 'mul';
|
||||
|
||||
/**
|
||||
* Language code referring to the default language of data, e.g. of an entity.
|
||||
*
|
||||
* @todo: Change value to differ from LANGUAGE_NOT_SPECIFIED once field API
|
||||
* leverages the property API.
|
||||
*/
|
||||
const LANGUAGE_DEFAULT = 'und';
|
||||
|
||||
/**
|
||||
* The language state when referring to configurable languages.
|
||||
*/
|
||||
|
@ -2475,6 +2483,19 @@ function state() {
|
|||
return drupal_container()->get('state.storage');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the typed data manager service.
|
||||
*
|
||||
* Use the typed data manager service for creating typed data objects.
|
||||
*
|
||||
* @see Drupal\Core\TypedData\TypedDataManager::create()
|
||||
*
|
||||
* @return Drupal\Core\TypedData\TypedDataManager
|
||||
*/
|
||||
function typed_data() {
|
||||
return drupal_container()->get('typed_data');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the test prefix if this is an internal request from SimpleTest.
|
||||
*
|
||||
|
|
|
@ -466,3 +466,68 @@ function hook_entity_view_mode_alter(&$view_mode, Drupal\Core\Entity\EntityInter
|
|||
$view_mode = 'my_custom_view_mode';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Define custom entity properties.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* The entity type for which to define entity properties.
|
||||
*
|
||||
* @return array
|
||||
* An array of property information having the following optional entries:
|
||||
* - definitions: An array of property definitions to add all entities of this
|
||||
* type, keyed by property name. See
|
||||
* Drupal\Core\TypedData\TypedDataManager::create() for a list of supported
|
||||
* keys in property definitions.
|
||||
* - optional: An array of property definitions for optional properties keyed
|
||||
* by property name. Optional properties are properties that only exist for
|
||||
* certain bundles of the entity type.
|
||||
* - bundle map: An array keyed by bundle name containing the names of
|
||||
* optional properties that entities of this bundle have.
|
||||
*
|
||||
* @see Drupal\Core\TypedData\TypedDataManager::create()
|
||||
* @see hook_entity_field_info_alter()
|
||||
* @see Drupal\Core\Entity\StorageControllerInterface::getPropertyDefinitions()
|
||||
*/
|
||||
function hook_entity_field_info($entity_type) {
|
||||
if (mymodule_uses_entity_type($entity_type)) {
|
||||
$info = array();
|
||||
$info['definitions']['mymodule_text'] = array(
|
||||
'type' => 'string_item',
|
||||
'list' => TRUE,
|
||||
'label' => t('The text'),
|
||||
'description' => t('A text property added by mymodule.'),
|
||||
'computed' => TRUE,
|
||||
'class' => '\Drupal\mymodule\EntityComputedText',
|
||||
);
|
||||
if ($entity_type == 'node') {
|
||||
// Add a property only to entities of the 'article' bundle.
|
||||
$info['optional']['mymodule_text_more'] = array(
|
||||
'type' => 'string_item',
|
||||
'list' => TRUE,
|
||||
'label' => t('More text'),
|
||||
'computed' => TRUE,
|
||||
'class' => '\Drupal\mymodule\EntityComputedMoreText',
|
||||
);
|
||||
$info['bundle map']['article'][0] = 'mymodule_text_more';
|
||||
}
|
||||
return $info;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter defined entity properties.
|
||||
*
|
||||
* @param array $info
|
||||
* The property info array as returned by hook_entity_field_info().
|
||||
* @param string $entity_type
|
||||
* The entity type for which entity properties are defined.
|
||||
*
|
||||
* @see hook_entity_field_info()
|
||||
*/
|
||||
function hook_entity_field_info_alter(&$info, $entity_type) {
|
||||
if (!empty($info['definitions']['mymodule_text'])) {
|
||||
// Alter the mymodule_text property to use a custom class.
|
||||
$info['definitions']['mymodule_text']['class'] = '\Drupal\anothermodule\EntityComputedText';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -346,6 +346,13 @@ class ConfigStorageController implements EntityStorageControllerInterface {
|
|||
protected function postDelete($entities) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Entity\EntityStorageControllerInterface::getFieldDefinitions().
|
||||
*/
|
||||
public function getFieldDefinitions(array $constraints) {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes a hook on behalf of the entity.
|
||||
*
|
||||
|
|
|
@ -52,6 +52,7 @@ class CoreBundle extends Bundle
|
|||
->setFactoryClass('Drupal\Core\Database\Database')
|
||||
->setFactoryMethod('getConnection')
|
||||
->addArgument('slave');
|
||||
$container->register('typed_data', 'Drupal\Core\TypedData\TypedDataManager');
|
||||
|
||||
// @todo Replace below lines with the commented out block below it when it's
|
||||
// performant to do so: http://drupal.org/node/1706064.
|
||||
|
|
|
@ -45,6 +45,15 @@ class DatabaseStorageController implements EntityStorageControllerInterface {
|
|||
*/
|
||||
protected $entityInfo;
|
||||
|
||||
/**
|
||||
* An array of field information, i.e. containing definitions.
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @see hook_entity_field_info()
|
||||
*/
|
||||
protected $entityFieldInfo;
|
||||
|
||||
/**
|
||||
* Additional arguments to pass to hook_TYPE_load().
|
||||
*
|
||||
|
@ -201,7 +210,7 @@ class DatabaseStorageController implements EntityStorageControllerInterface {
|
|||
// Remove any invalid ids from the array.
|
||||
$passed_ids = array_intersect_key($passed_ids, $entities);
|
||||
foreach ($entities as $entity) {
|
||||
$passed_ids[$entity->{$this->idKey}] = $entity;
|
||||
$passed_ids[$entity->id()] = $entity;
|
||||
}
|
||||
$entities = $passed_ids;
|
||||
}
|
||||
|
@ -470,7 +479,7 @@ class DatabaseStorageController implements EntityStorageControllerInterface {
|
|||
|
||||
if (!$entity->isNew()) {
|
||||
$return = drupal_write_record($this->entityInfo['base table'], $entity, $this->idKey);
|
||||
$this->resetCache(array($entity->{$this->idKey}));
|
||||
$this->resetCache(array($entity->id()));
|
||||
$this->postSave($entity, TRUE);
|
||||
$this->invokeHook('update', $entity);
|
||||
}
|
||||
|
@ -547,4 +556,57 @@ class DatabaseStorageController implements EntityStorageControllerInterface {
|
|||
// Invoke the respective entity-level hook.
|
||||
module_invoke_all('entity_' . $hook, $entity, $this->entityType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Entity\EntityStorageControllerInterface::getFieldDefinitions().
|
||||
*/
|
||||
public function getFieldDefinitions(array $constraints) {
|
||||
// @todo: Add caching for $this->propertyInfo.
|
||||
if (!isset($this->entityFieldInfo)) {
|
||||
$this->entityFieldInfo = array(
|
||||
'definitions' => $this->baseFieldDefinitions(),
|
||||
// Contains definitions of optional (per-bundle) properties.
|
||||
'optional' => array(),
|
||||
// An array keyed by bundle name containing the names of the per-bundle
|
||||
// properties.
|
||||
'bundle map' => array(),
|
||||
);
|
||||
|
||||
// Invoke hooks.
|
||||
$result = module_invoke_all($this->entityType . '_property_info');
|
||||
$this->entityFieldInfo = array_merge_recursive($this->entityFieldInfo, $result);
|
||||
$result = module_invoke_all('entity_field_info', $this->entityType);
|
||||
$this->entityFieldInfo = array_merge_recursive($this->entityFieldInfo, $result);
|
||||
|
||||
$hooks = array('entity_field_info', $this->entityType . '_property_info');
|
||||
drupal_alter($hooks, $this->entityFieldInfo, $this->entityType);
|
||||
|
||||
// Enforce fields to be multiple by default.
|
||||
foreach ($this->entityFieldInfo['definitions'] as &$definition) {
|
||||
$definition['list'] = TRUE;
|
||||
}
|
||||
foreach ($this->entityFieldInfo['optional'] as &$definition) {
|
||||
$definition['list'] = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
$definitions = $this->entityFieldInfo['definitions'];
|
||||
|
||||
// Add in per-bundle properties.
|
||||
// @todo: Should this be statically cached as well?
|
||||
if (!empty($constraints['bundle']) && isset($this->entityFieldInfo['bundle map'][$constraints['bundle']])) {
|
||||
$definitions += array_intersect_key($this->entityFieldInfo['optional'], array_flip($this->entityFieldInfo['bundle map'][$constraints['bundle']]));
|
||||
}
|
||||
|
||||
return $definitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the base properties of the entity type.
|
||||
*
|
||||
* @todo: Define abstract once all entity types have been converted.
|
||||
*/
|
||||
public function baseFieldDefinitions() {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,242 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\Entity\DatabaseStorageControllerNG.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Entity;
|
||||
|
||||
use PDO;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\DatabaseStorageController;
|
||||
use Drupal\Core\Entity\EntityStorageException;
|
||||
use Drupal\Component\Uuid\Uuid;
|
||||
|
||||
/**
|
||||
* Implements Field API specific enhancements to the DatabaseStorageController class.
|
||||
*
|
||||
* @todo: Once all entity types have been converted, merge improvements into the
|
||||
* DatabaseStorageController class.
|
||||
*/
|
||||
class DatabaseStorageControllerNG extends DatabaseStorageController {
|
||||
|
||||
/**
|
||||
* The entity class to use.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $entityClass;
|
||||
|
||||
/**
|
||||
* The entity bundle key.
|
||||
*
|
||||
* @var string|bool
|
||||
*/
|
||||
protected $bundleKey;
|
||||
|
||||
/**
|
||||
* Overrides DatabaseStorageController::__construct().
|
||||
*/
|
||||
public function __construct($entityType) {
|
||||
parent::__construct($entityType);
|
||||
$this->bundleKey = !empty($this->entityInfo['entity keys']['bundle']) ? $this->entityInfo['entity keys']['bundle'] : FALSE;
|
||||
$this->entityClass = $this->entityInfo['entity class'];
|
||||
|
||||
// Work-a-round to let load() get stdClass storage records without having to
|
||||
// override it. We map storage records to entities in
|
||||
// DatabaseStorageControllerNG:: mapFromStorageRecords().
|
||||
// @todo: Remove this once this is moved in the main controller.
|
||||
unset($this->entityInfo['entity class']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides DatabaseStorageController::create().
|
||||
*
|
||||
* @param array $values
|
||||
* An array of values to set, keyed by field name. The value has to be
|
||||
* the plain value of an entity field, i.e. an array of field items.
|
||||
* If no numerically indexed array is given, the value will be set for the
|
||||
* first field item. For example, to set the first item of a 'name'
|
||||
* property one can pass:
|
||||
* @code
|
||||
* $values = array('name' => array(0 => array('value' => 'the name')));
|
||||
* @endcode
|
||||
* or
|
||||
* @code
|
||||
* $values = array('name' => array('value' => 'the name'));
|
||||
* @endcode
|
||||
* If the 'name' field is a defined as 'string_item' which supports
|
||||
* setting by string value, it's also possible to just pass the name string:
|
||||
* @code
|
||||
* $values = array('name' => 'the name');
|
||||
* @endcode
|
||||
*
|
||||
* @return Drupal\Core\Entity\EntityInterface
|
||||
* A new entity object.
|
||||
*/
|
||||
public function create(array $values) {
|
||||
$entity = new $this->entityClass(array(), $this->entityType);
|
||||
|
||||
// Make sure to set the bundle first.
|
||||
if ($this->bundleKey) {
|
||||
$entity->{$this->bundleKey} = $values[$this->bundleKey];
|
||||
unset($values[$this->bundleKey]);
|
||||
}
|
||||
// Set all other given values.
|
||||
foreach ($values as $name => $value) {
|
||||
$entity->$name = $value;
|
||||
}
|
||||
|
||||
// Assign a new UUID if there is none yet.
|
||||
if ($this->uuidKey && !isset($entity->{$this->uuidKey})) {
|
||||
$uuid = new Uuid();
|
||||
$entity->{$this->uuidKey}->value = $uuid->generate();
|
||||
}
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides DatabaseStorageController::attachLoad().
|
||||
*
|
||||
* Added mapping from storage records to entities.
|
||||
*/
|
||||
protected function attachLoad(&$queried_entities, $load_revision = FALSE) {
|
||||
// Now map the record values to the according entity properties and
|
||||
// activate compatibility mode.
|
||||
$queried_entities = $this->mapFromStorageRecords($queried_entities);
|
||||
|
||||
// Attach fields.
|
||||
if ($this->entityInfo['fieldable']) {
|
||||
if ($load_revision) {
|
||||
field_attach_load_revision($this->entityType, $queried_entities);
|
||||
}
|
||||
else {
|
||||
field_attach_load($this->entityType, $queried_entities);
|
||||
}
|
||||
}
|
||||
|
||||
// Loading is finished, so disable compatibility mode now.
|
||||
foreach ($queried_entities as $entity) {
|
||||
$entity->setCompatibilityMode(FALSE);
|
||||
}
|
||||
|
||||
// Call hook_entity_load().
|
||||
foreach (module_implements('entity_load') as $module) {
|
||||
$function = $module . '_entity_load';
|
||||
$function($queried_entities, $this->entityType);
|
||||
}
|
||||
// Call hook_TYPE_load(). The first argument for hook_TYPE_load() are
|
||||
// always the queried entities, followed by additional arguments set in
|
||||
// $this->hookLoadArguments.
|
||||
$args = array_merge(array($queried_entities), $this->hookLoadArguments);
|
||||
foreach (module_implements($this->entityType . '_load') as $module) {
|
||||
call_user_func_array($module . '_' . $this->entityType . '_load', $args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps from storage records to entity objects.
|
||||
*
|
||||
* @return array
|
||||
* An array of entity objects implementing the EntityInterface.
|
||||
*/
|
||||
protected function mapFromStorageRecords(array $records) {
|
||||
|
||||
foreach ($records as $id => $record) {
|
||||
$entity = new $this->entityClass(array(), $this->entityType);
|
||||
$entity->setCompatibilityMode(TRUE);
|
||||
|
||||
foreach ($record as $name => $value) {
|
||||
$entity->{$name}[LANGUAGE_DEFAULT][0]['value'] = $value;
|
||||
}
|
||||
$records[$id] = $entity;
|
||||
}
|
||||
return $records;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides DatabaseStorageController::save().
|
||||
*
|
||||
* Added mapping from entities to storage records before saving.
|
||||
*/
|
||||
public function save(EntityInterface $entity) {
|
||||
$transaction = db_transaction();
|
||||
try {
|
||||
// Load the stored entity, if any.
|
||||
if (!$entity->isNew() && !isset($entity->original)) {
|
||||
$entity->original = entity_load_unchanged($this->entityType, $entity->id());
|
||||
}
|
||||
|
||||
$this->preSave($entity);
|
||||
$this->invokeHook('presave', $entity);
|
||||
|
||||
// Create the storage record to be saved.
|
||||
$record = $this->maptoStorageRecord($entity);
|
||||
// Update the original values so that the compatibility mode works with
|
||||
// the update values, what is required by field API attachers.
|
||||
// @todo Once field API has been converted to use the Field API, move
|
||||
// this after insert/update hooks.
|
||||
$entity->updateOriginalValues();
|
||||
|
||||
if (!$entity->isNew()) {
|
||||
$return = drupal_write_record($this->entityInfo['base table'], $record, $this->idKey);
|
||||
$this->resetCache(array($entity->id()));
|
||||
$this->postSave($entity, TRUE);
|
||||
$this->invokeHook('update', $entity);
|
||||
}
|
||||
else {
|
||||
$return = drupal_write_record($this->entityInfo['base table'], $record);
|
||||
// Reset general caches, but keep caches specific to certain entities.
|
||||
$this->resetCache(array());
|
||||
|
||||
$entity->{$this->idKey}->value = $record->{$this->idKey};
|
||||
$entity->enforceIsNew(FALSE);
|
||||
$this->postSave($entity, FALSE);
|
||||
$this->invokeHook('insert', $entity);
|
||||
}
|
||||
|
||||
// Ignore slave server temporarily.
|
||||
db_ignore_slave();
|
||||
unset($entity->original);
|
||||
|
||||
return $return;
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$transaction->rollback();
|
||||
watchdog_exception($this->entityType, $e);
|
||||
throw new EntityStorageException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides DatabaseStorageController::invokeHook().
|
||||
*
|
||||
* Invokes field API attachers in compatibility mode and disables it
|
||||
* afterwards.
|
||||
*/
|
||||
protected function invokeHook($hook, EntityInterface $entity) {
|
||||
if (!empty($this->entityInfo['fieldable']) && function_exists($function = 'field_attach_' . $hook)) {
|
||||
$entity->setCompatibilityMode(TRUE);
|
||||
$function($this->entityType, $entity);
|
||||
$entity->setCompatibilityMode(FALSE);
|
||||
}
|
||||
|
||||
// Invoke the hook.
|
||||
module_invoke_all($this->entityType . '_' . $hook, $entity);
|
||||
// Invoke the respective entity-level hook.
|
||||
module_invoke_all('entity_' . $hook, $entity, $this->entityType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps from an entity object to the storage record of the base table.
|
||||
*/
|
||||
protected function mapToStorageRecord(EntityInterface $entity) {
|
||||
$record = new \stdClass();
|
||||
foreach ($this->entityInfo['schema_fields_sql']['base table'] as $name) {
|
||||
$record->$name = $entity->$name->value;
|
||||
}
|
||||
return $record;
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ namespace Drupal\Core\Entity;
|
|||
|
||||
use Drupal\Component\Uuid\Uuid;
|
||||
use Drupal\Core\Language\Language;
|
||||
use IteratorAggregate;
|
||||
|
||||
/**
|
||||
* Defines a base entity class.
|
||||
|
@ -18,7 +19,7 @@ use Drupal\Core\Language\Language;
|
|||
* This class can be used as-is by simple entity types. Entity types requiring
|
||||
* special handling can extend the class.
|
||||
*/
|
||||
class Entity implements EntityInterface {
|
||||
class Entity implements IteratorAggregate, EntityInterface {
|
||||
|
||||
/**
|
||||
* The language code of the entity's default language.
|
||||
|
@ -146,14 +147,109 @@ class Entity implements EntityInterface {
|
|||
}
|
||||
|
||||
/**
|
||||
* Implements EntityInterface::language().
|
||||
* Implements EntityInterface::get().
|
||||
*/
|
||||
public function get($property_name, $langcode = NULL) {
|
||||
// @todo: Replace by EntityNG implementation once all entity types have been
|
||||
// converted to use the entity field API.
|
||||
return isset($this->{$property_name}) ? $this->{$property_name} : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::set().
|
||||
*/
|
||||
public function set($property_name, $value) {
|
||||
// @todo: Replace by EntityNG implementation once all entity types have been
|
||||
// converted to use the entity field API.
|
||||
$this->{$property_name} = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::getProperties().
|
||||
*/
|
||||
public function getProperties($include_computed = FALSE) {
|
||||
// @todo: Replace by EntityNG implementation once all entity types have been
|
||||
// converted to use the entity field API.
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::getPropertyValues().
|
||||
*/
|
||||
public function getPropertyValues() {
|
||||
// @todo: Replace by EntityNG implementation once all entity types have been
|
||||
// converted to use the entity field API.
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::setPropertyValues().
|
||||
*/
|
||||
public function setPropertyValues($values) {
|
||||
// @todo: Replace by EntityNG implementation once all entity types have been
|
||||
// converted to use the entity field API.
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::getPropertyDefinition().
|
||||
*/
|
||||
public function getPropertyDefinition($name) {
|
||||
// @todo: Replace by EntityNG implementation once all entity types have been
|
||||
// converted to use the entity field API.
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::getPropertyDefinitions().
|
||||
*/
|
||||
public function getPropertyDefinitions() {
|
||||
// @todo: Replace by EntityNG implementation once all entity types have been
|
||||
// converted to use the entity field API.
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::isEmpty().
|
||||
*/
|
||||
public function isEmpty() {
|
||||
// @todo: Replace by EntityNG implementation once all entity types have been
|
||||
// converted to use the entity field API.
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::getIterator().
|
||||
*/
|
||||
public function getIterator() {
|
||||
// @todo: Replace by EntityNG implementation once all entity types have been
|
||||
// converted to use the entity field API.
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements AccessibleInterface::access().
|
||||
*/
|
||||
public function access(\Drupal\user\User $account = NULL) {
|
||||
// TODO: Implement access() method.
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TranslatableInterface::language().
|
||||
*/
|
||||
public function language() {
|
||||
// @todo: Replace by EntityNG implementation once all entity types have been
|
||||
// converted to use the entity field API.
|
||||
return !empty($this->langcode) ? language_load($this->langcode) : new Language(array('langcode' => LANGUAGE_NOT_SPECIFIED));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements EntityInterface::translations().
|
||||
* Implements TranslatableInterface::getTranslation().
|
||||
*/
|
||||
public function getTranslation($langcode, $strict = TRUE) {
|
||||
// @todo: Replace by EntityNG implementation once all entity types have been
|
||||
// converted to use the entity field API.
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the languages the entity is translated to.
|
||||
*
|
||||
* @todo: Remove once all entity types implement the entity field API. This
|
||||
* is deprecated by
|
||||
* TranslatableInterface::getTranslationLanguages().
|
||||
*/
|
||||
public function translations() {
|
||||
$languages = array();
|
||||
|
@ -177,108 +273,11 @@ class Entity implements EntityInterface {
|
|||
}
|
||||
|
||||
/**
|
||||
* Implements EntityInterface::get().
|
||||
* Implements TranslatableInterface::getTranslationLanguages().
|
||||
*/
|
||||
public function get($property_name, $langcode = NULL) {
|
||||
// Handle fields.
|
||||
$entity_info = $this->entityInfo();
|
||||
if ($entity_info['fieldable'] && field_info_instance($this->entityType, $property_name, $this->bundle())) {
|
||||
$field = field_info_field($property_name);
|
||||
// Prevent getFieldLangcode() from throwing an exception in case a
|
||||
// $langcode has been passed and it is invalid for the field.
|
||||
$langcode = $this->getFieldLangcode($field, $langcode, FALSE);
|
||||
return isset($this->{$property_name}[$langcode]) ? $this->{$property_name}[$langcode] : NULL;
|
||||
}
|
||||
else {
|
||||
// Handle properties being not fields.
|
||||
// @todo: Add support for translatable properties being not fields.
|
||||
return isset($this->{$property_name}) ? $this->{$property_name} : NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements EntityInterface::set().
|
||||
*/
|
||||
public function set($property_name, $value, $langcode = NULL) {
|
||||
// Handle fields.
|
||||
$entity_info = $this->entityInfo();
|
||||
if ($entity_info['fieldable'] && field_info_instance($this->entityType, $property_name, $this->bundle())) {
|
||||
$field = field_info_field($property_name);
|
||||
// Throws an exception if the $langcode is invalid.
|
||||
$langcode = $this->getFieldLangcode($field, $langcode);
|
||||
$this->{$property_name}[$langcode] = $value;
|
||||
}
|
||||
else {
|
||||
// Handle properties being not fields.
|
||||
// @todo: Add support for translatable properties being not fields.
|
||||
$this->{$property_name} = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the language code for accessing a field value.
|
||||
*
|
||||
* The effective language code to be used for a field varies:
|
||||
* - If the entity is language-specific and the requested field is
|
||||
* translatable, the entity's language code should be used to access the
|
||||
* field value when no language is explicitly provided.
|
||||
* - If the entity is not language-specific, LANGUAGE_NOT_SPECIFIED should be
|
||||
* used to access all field values.
|
||||
* - If a field's values are non-translatable (shared among all language
|
||||
* versions of an entity), LANGUAGE_NOT_SPECIFIED should be used to access
|
||||
* them.
|
||||
*
|
||||
* There cannot be valid field values if a field is not translatable and the
|
||||
* requested langcode is not LANGUAGE_NOT_SPECIFIED. Therefore, this function
|
||||
* throws an exception in that case (or returns NULL when $strict is FALSE).
|
||||
*
|
||||
* @param string $field
|
||||
* Field the language code is being determined for.
|
||||
* @param string|null $langcode
|
||||
* (optional) The language code attempting to be applied to the field.
|
||||
* Defaults to the entity language.
|
||||
* @param bool $strict
|
||||
* (optional) When $strict is TRUE, an exception is thrown if the field is
|
||||
* not translatable and the langcode is not LANGUAGE_NOT_SPECIFIED. When
|
||||
* $strict is FALSE, NULL is returned and no exception is thrown. For
|
||||
* example, EntityInterface::set() passes TRUE, since it must not set field
|
||||
* values for invalid langcodes. EntityInterface::get() passes FALSE to
|
||||
* determine whether any field values exist for a specific langcode.
|
||||
* Defaults to TRUE.
|
||||
*
|
||||
* @return string|null
|
||||
* The langcode if appropriate, LANGUAGE_NOT_SPECIFIED for non-translatable
|
||||
* fields, or NULL when an invalid langcode was used in non-strict mode.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* Thrown in case a $langcode other than LANGUAGE_NOT_SPECIFIED is passed
|
||||
* for a non-translatable field and $strict is TRUE.
|
||||
*/
|
||||
protected function getFieldLangcode($field, $langcode = NULL, $strict = TRUE) {
|
||||
// Only apply the given langcode if the entity is language-specific.
|
||||
// Otherwise translatable fields are handled as non-translatable fields.
|
||||
if (field_is_translatable($this->entityType, $field) && ($default_language = $this->language()) && !language_is_locked($this->langcode)) {
|
||||
// For translatable fields the values in default language are stored using
|
||||
// the language code of the default language.
|
||||
return isset($langcode) ? $langcode : $default_language->langcode;
|
||||
}
|
||||
else {
|
||||
// The field is not translatable, but the caller requested a specific
|
||||
// langcode that does not exist.
|
||||
if (isset($langcode) && $langcode !== LANGUAGE_NOT_SPECIFIED) {
|
||||
if ($strict) {
|
||||
throw new \InvalidArgumentException(format_string('Unable to resolve @langcode for non-translatable field @field_name. Use langcode LANGUAGE_NOT_SPECIFIED instead.', array(
|
||||
'@field_name' => $field['field_name'],
|
||||
'@langcode' => $langcode,
|
||||
)));
|
||||
}
|
||||
else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
// The field is not translatable and no $langcode was specified.
|
||||
return LANGUAGE_NOT_SPECIFIED;
|
||||
}
|
||||
public function getTranslationLanguages($include_default = TRUE) {
|
||||
// @todo: Replace by EntityNG implementation once all entity types have been
|
||||
// converted to use the entity field API.
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -337,5 +336,4 @@ class Entity implements EntityInterface {
|
|||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ class EntityFormController implements EntityFormControllerInterface {
|
|||
* @see Drupal\Core\Entity\EntityFormController::build()
|
||||
*/
|
||||
public function form(array $form, array &$form_state, EntityInterface $entity) {
|
||||
// @todo Exploit the Property API to generate the default widgets for the
|
||||
// @todo Exploit the Field API to generate the default widgets for the
|
||||
// entity properties.
|
||||
$info = $entity->entityInfo();
|
||||
if (!empty($info['fieldable'])) {
|
||||
|
@ -145,7 +145,7 @@ class EntityFormController implements EntityFormControllerInterface {
|
|||
* Implements Drupal\Core\Entity\EntityFormControllerInterface::validate().
|
||||
*/
|
||||
public function validate(array $form, array &$form_state) {
|
||||
// @todo Exploit the Property API to validate the values submitted for the
|
||||
// @todo Exploit the Field API to validate the values submitted for the
|
||||
// entity properties.
|
||||
$entity = $this->buildEntity($form, $form_state);
|
||||
$info = $entity->entityInfo();
|
||||
|
@ -236,7 +236,7 @@ class EntityFormController implements EntityFormControllerInterface {
|
|||
public function buildEntity(array $form, array &$form_state) {
|
||||
$entity = clone $this->getEntity($form_state);
|
||||
// @todo Move entity_form_submit_build_entity() here.
|
||||
// @todo Exploit the Property API to process the submitted entity property.
|
||||
// @todo Exploit the Field API to process the submitted entity field.
|
||||
entity_form_submit_build_entity($entity->entityType(), $entity, $form, $form_state);
|
||||
return $entity;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\Entity\EntityFormControllerNG.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Entity;
|
||||
|
||||
/**
|
||||
* Entity form controller variant for entity types using the new property API.
|
||||
*
|
||||
* @todo: Merge with EntityFormController and overhaul once all entity types
|
||||
* are converted to the new property API.
|
||||
*/
|
||||
class EntityFormControllerNG extends EntityFormController {
|
||||
|
||||
/**
|
||||
* Overrides EntityFormController::form().
|
||||
*/
|
||||
public function form(array $form, array &$form_state, EntityInterface $entity) {
|
||||
// @todo Exploit the Field API to generate the default widgets for the
|
||||
// entity properties.
|
||||
$info = $entity->entityInfo();
|
||||
if (!empty($info['fieldable'])) {
|
||||
$entity->setCompatibilityMode(TRUE);
|
||||
field_attach_form($entity->entityType(), $entity, $form, $form_state, $this->getFormLangcode($form_state));
|
||||
$entity->setCompatibilityMode(FALSE);
|
||||
}
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides EntityFormController::validate().
|
||||
*/
|
||||
public function validate(array $form, array &$form_state) {
|
||||
// @todo Exploit the Field API to validate the values submitted for the
|
||||
// entity properties.
|
||||
$entity = $this->buildEntity($form, $form_state);
|
||||
$info = $entity->entityInfo();
|
||||
|
||||
if (!empty($info['fieldable'])) {
|
||||
$entity->setCompatibilityMode(TRUE);
|
||||
field_attach_form_validate($entity->entityType(), $entity, $form, $form_state);
|
||||
$entity->setCompatibilityMode(FALSE);
|
||||
}
|
||||
|
||||
// @todo Remove this.
|
||||
// Execute legacy global validation handlers.
|
||||
unset($form_state['validate_handlers']);
|
||||
form_execute_handlers('validate', $form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides EntityFormController::buildEntity().
|
||||
*/
|
||||
public function buildEntity(array $form, array &$form_state) {
|
||||
$entity = clone $this->getEntity($form_state);
|
||||
$entity_type = $entity->entityType();
|
||||
$info = entity_get_info($entity_type);
|
||||
// @todo Exploit the Field API to process the submitted entity field.
|
||||
|
||||
// Copy top-level form values that are not for fields to entity properties,
|
||||
// without changing existing entity properties that are not being edited by
|
||||
// this form. Copying field values must be done using field_attach_submit().
|
||||
$values_excluding_fields = $info['fieldable'] ? array_diff_key($form_state['values'], field_info_instances($entity_type, $entity->bundle())) : $form_state['values'];
|
||||
foreach ($values_excluding_fields as $key => $value) {
|
||||
$entity->$key = $value;
|
||||
}
|
||||
|
||||
// Invoke all specified builders for copying form values to entity properties.
|
||||
if (isset($form['#entity_builders'])) {
|
||||
foreach ($form['#entity_builders'] as $function) {
|
||||
$function($entity_type, $entity, $form, $form_state);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy field values to the entity.
|
||||
if ($info['fieldable']) {
|
||||
$entity->setCompatibilityMode(TRUE);
|
||||
field_attach_submit($entity_type, $entity, $form, $form_state);
|
||||
$entity->setCompatibilityMode(FALSE);
|
||||
}
|
||||
return $entity;
|
||||
}
|
||||
}
|
|
@ -7,10 +7,17 @@
|
|||
|
||||
namespace Drupal\Core\Entity;
|
||||
|
||||
use Drupal\Core\TypedData\AccessibleInterface;
|
||||
use Drupal\Core\TypedData\ComplexDataInterface;
|
||||
use Drupal\Core\TypedData\TranslatableInterface;
|
||||
|
||||
/**
|
||||
* Defines a common interface for all entity objects.
|
||||
*
|
||||
* When implementing this interface which extends Traversable, make sure to list
|
||||
* IteratorAggregate or Iterator before this interface in the implements clause.
|
||||
*/
|
||||
interface EntityInterface {
|
||||
interface EntityInterface extends ComplexDataInterface, AccessibleInterface, TranslatableInterface {
|
||||
|
||||
/**
|
||||
* Constructs a new entity object.
|
||||
|
@ -110,60 +117,6 @@ interface EntityInterface {
|
|||
*/
|
||||
public function uri();
|
||||
|
||||
/**
|
||||
* Returns the default language of a language-specific entity.
|
||||
*
|
||||
* @return
|
||||
* The language object of the entity's default language, or FALSE if the
|
||||
* entity is not language-specific.
|
||||
*
|
||||
* @see Drupal\Core\Entity\EntityInterface::translations()
|
||||
*/
|
||||
public function language();
|
||||
|
||||
/**
|
||||
* Returns the languages the entity is translated to.
|
||||
*
|
||||
* @return
|
||||
* An array of language objects, keyed by language codes.
|
||||
*
|
||||
* @see Drupal\Core\Entity\EntityInterface::language()
|
||||
*/
|
||||
public function translations();
|
||||
|
||||
/**
|
||||
* Returns the value of an entity property.
|
||||
*
|
||||
* @param $property_name
|
||||
* The name of the property to return; e.g., 'title'.
|
||||
* @param $langcode
|
||||
* (optional) If the property is translatable, the language code of the
|
||||
* language that should be used for getting the property. If set to NULL,
|
||||
* the entity's default language is being used.
|
||||
*
|
||||
* @return
|
||||
* The property value, or NULL if it is not defined.
|
||||
*
|
||||
* @see Drupal\Core\Entity\EntityInterface::language()
|
||||
*/
|
||||
public function get($property_name, $langcode = NULL);
|
||||
|
||||
/**
|
||||
* Sets the value of an entity property.
|
||||
*
|
||||
* @param $property_name
|
||||
* The name of the property to set; e.g., 'title'.
|
||||
* @param $value
|
||||
* The value to set, or NULL to unset the property.
|
||||
* @param $langcode
|
||||
* (optional) If the property is translatable, the language code of the
|
||||
* language that should be used for setting the property. If set to NULL,
|
||||
* the entity's default language is being used.
|
||||
*
|
||||
* @see Drupal\Core\Entity\EntityInterface::language()
|
||||
*/
|
||||
public function set($property_name, $value, $langcode = NULL);
|
||||
|
||||
/**
|
||||
* Saves an entity permanently.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,424 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\Entity\EntityNG.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Entity;
|
||||
|
||||
use Drupal\Core\TypedData\ContextAwareInterface;
|
||||
use Drupal\Core\TypedData\TypedDataInterface;
|
||||
use Drupal\Component\Uuid\Uuid;
|
||||
use ArrayIterator;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Implements Entity Field API specific enhancements to the Entity class.
|
||||
*
|
||||
* An entity implements the ComplexDataInterface, thus is complex data
|
||||
* containing fields as its data properties. The entity fields have to implement
|
||||
* the \Drupal\Core\Entity\Field\FieldInterface.
|
||||
*
|
||||
* @todo: Once all entity types have been converted, merge improvements into the
|
||||
* Entity class and overhaul the EntityInterface.
|
||||
*/
|
||||
class EntityNG extends Entity {
|
||||
|
||||
/**
|
||||
* The plain data values of the contained fields.
|
||||
*
|
||||
* This always holds the original, unchanged values of the entity. The values
|
||||
* are keyed by language code, whereas LANGUAGE_NOT_SPECIFIED is used for
|
||||
* values in default language.
|
||||
*
|
||||
* @todo: Add methods for getting original fields and for determining
|
||||
* changes.
|
||||
* @todo: Provide a better way for defining default values.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $values = array(
|
||||
'langcode' => array(LANGUAGE_DEFAULT => array(0 => array('value' => LANGUAGE_NOT_SPECIFIED))),
|
||||
);
|
||||
|
||||
/**
|
||||
* The array of fields, each being an instance of FieldInterface.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fields = array();
|
||||
|
||||
/**
|
||||
* Whether the entity is in pre-Entity Field API compatibility mode.
|
||||
*
|
||||
* If set to TRUE, field values are written directly to $this->values, thus
|
||||
* must be plain property values keyed by language code. This must be enabled
|
||||
* when calling legacy field API attachers.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $compatibilityMode = FALSE;
|
||||
|
||||
|
||||
/**
|
||||
* Overrides Entity::id().
|
||||
*/
|
||||
public function id() {
|
||||
return $this->get('id')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides Entity::uuid().
|
||||
*/
|
||||
public function uuid() {
|
||||
return $this->get('uuid')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::get().
|
||||
*/
|
||||
public function get($property_name) {
|
||||
// Values in default language are always stored using the LANGUAGE_DEFAULT
|
||||
// constant.
|
||||
if (!isset($this->fields[$property_name][LANGUAGE_DEFAULT])) {
|
||||
return $this->getTranslatedField($property_name, LANGUAGE_DEFAULT);
|
||||
}
|
||||
return $this->fields[$property_name][LANGUAGE_DEFAULT];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a translated field.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\Field\FieldInterface
|
||||
*/
|
||||
protected function getTranslatedField($property_name, $langcode) {
|
||||
// Populate $this->properties to fasten further lookups and to keep track of
|
||||
// property objects, possibly holding changes to properties.
|
||||
if (!isset($this->fields[$property_name][$langcode])) {
|
||||
$definition = $this->getPropertyDefinition($property_name);
|
||||
if (!$definition) {
|
||||
throw new InvalidArgumentException('Field ' . check_plain($property_name) . ' is unknown.');
|
||||
}
|
||||
// Non-translatable properties always use default language.
|
||||
if ($langcode != LANGUAGE_DEFAULT && empty($definition['translatable'])) {
|
||||
$this->fields[$property_name][$langcode] = $this->getTranslatedField($property_name, LANGUAGE_DEFAULT);
|
||||
}
|
||||
else {
|
||||
$value = isset($this->values[$property_name][$langcode]) ? $this->values[$property_name][$langcode] : NULL;
|
||||
$context = array('parent' => $this, 'name' => $property_name);
|
||||
$this->fields[$property_name][$langcode] = typed_data()->create($definition, $value, $context);
|
||||
}
|
||||
}
|
||||
return $this->fields[$property_name][$langcode];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::set().
|
||||
*/
|
||||
public function set($property_name, $value) {
|
||||
$this->get($property_name)->setValue($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::getProperties().
|
||||
*/
|
||||
public function getProperties($include_computed = FALSE) {
|
||||
$properties = array();
|
||||
foreach ($this->getPropertyDefinitions() as $name => $definition) {
|
||||
if ($include_computed || empty($definition['computed'])) {
|
||||
$properties[$name] = $this->get($name);
|
||||
}
|
||||
}
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements IteratorAggregate::getIterator().
|
||||
*/
|
||||
public function getIterator() {
|
||||
return new ArrayIterator($this->getProperties());
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::getPropertyDefinition().
|
||||
*/
|
||||
public function getPropertyDefinition($name) {
|
||||
// First try getting property definitions which apply to all entities of
|
||||
// this type. Then if this fails add in definitions of optional properties
|
||||
// as well. That way we can use property definitions of base properties
|
||||
// when determining the optional properties of an entity.
|
||||
$definitions = entity_get_controller($this->entityType)->getFieldDefinitions(array());
|
||||
|
||||
if (isset($definitions[$name])) {
|
||||
return $definitions[$name];
|
||||
}
|
||||
// Add in optional properties if any.
|
||||
if ($definitions = $this->getPropertyDefinitions()) {
|
||||
return isset($definitions[$name]) ? $definitions[$name] : FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::getPropertyDefinitions().
|
||||
*/
|
||||
public function getPropertyDefinitions() {
|
||||
return entity_get_controller($this->entityType)->getFieldDefinitions(array(
|
||||
'entity type' => $this->entityType,
|
||||
'bundle' => $this->bundle(),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::getPropertyValues().
|
||||
*/
|
||||
public function getPropertyValues() {
|
||||
$values = array();
|
||||
foreach ($this->getProperties() as $name => $property) {
|
||||
$values[$name] = $property->getValue();
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::setPropertyValues().
|
||||
*/
|
||||
public function setPropertyValues($values) {
|
||||
foreach ($values as $name => $value) {
|
||||
$this->get($name)->setValue($value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::isEmpty().
|
||||
*/
|
||||
public function isEmpty() {
|
||||
if (!$this->isNew()) {
|
||||
return FALSE;
|
||||
}
|
||||
foreach ($this->getProperties() as $property) {
|
||||
if ($property->getValue() !== NULL) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TranslatableInterface::language().
|
||||
*/
|
||||
public function language() {
|
||||
return $this->get('langcode')->language;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TranslatableInterface::getTranslation().
|
||||
*
|
||||
* @return \Drupal\Core\Entity\Field\Type\EntityTranslation
|
||||
*/
|
||||
public function getTranslation($langcode, $strict = TRUE) {
|
||||
// If the default language is LANGUAGE_NOT_SPECIFIED, the entity is not
|
||||
// translatable, so we use LANGUAGE_DEFAULT.
|
||||
if ($langcode == LANGUAGE_DEFAULT || in_array($this->language()->langcode, array(LANGUAGE_NOT_SPECIFIED, $langcode))) {
|
||||
// No translation needed, return the entity.
|
||||
return $this;
|
||||
}
|
||||
// Check whether the language code is valid, thus is of an available
|
||||
// language.
|
||||
$languages = language_list(LANGUAGE_ALL);
|
||||
if (!isset($languages[$langcode])) {
|
||||
throw new InvalidArgumentException("Unable to get translation for the invalid language '$langcode'.");
|
||||
}
|
||||
$fields = array();
|
||||
foreach ($this->getPropertyDefinitions() as $name => $definition) {
|
||||
// Load only translatable properties in strict mode.
|
||||
if (!empty($definition['translatable']) || !$strict) {
|
||||
$fields[$name] = $this->getTranslatedField($name, $langcode);
|
||||
}
|
||||
}
|
||||
$translation_definition = array(
|
||||
'type' => 'entity_translation',
|
||||
'constraints' => array(
|
||||
'entity type' => $this->entityType(),
|
||||
'bundle' => $this->bundle(),
|
||||
),
|
||||
);
|
||||
$translation = typed_data()->create($translation_definition, $fields, array(
|
||||
'parent' => $this,
|
||||
'name' => $langcode,
|
||||
));
|
||||
$translation->setStrictMode($strict);
|
||||
return $translation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TranslatableInterface::getTranslationLanguages().
|
||||
*/
|
||||
public function getTranslationLanguages($include_default = TRUE) {
|
||||
$translations = array();
|
||||
// Build an array with the translation langcodes set as keys.
|
||||
foreach ($this->getProperties() as $name => $property) {
|
||||
if (isset($this->values[$name])) {
|
||||
$translations += $this->values[$name];
|
||||
}
|
||||
$translations += $this->fields[$name];
|
||||
}
|
||||
unset($translations[LANGUAGE_DEFAULT]);
|
||||
|
||||
if ($include_default) {
|
||||
$translations[$this->language()->langcode] = TRUE;
|
||||
}
|
||||
|
||||
// Now get languages based upon translation langcodes.
|
||||
$languages = array_intersect_key(language_list(LANGUAGE_ALL), $translations);
|
||||
return $languages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides Entity::translations().
|
||||
*
|
||||
* @todo: Remove once Entity::translations() gets removed.
|
||||
*/
|
||||
public function translations() {
|
||||
return $this->getTranslationLanguages(FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disable the compatibility mode.
|
||||
*
|
||||
* @param bool $enabled
|
||||
* Whether to enable the mode.
|
||||
*
|
||||
* @see EntityNG::compatibilityMode
|
||||
*/
|
||||
public function setCompatibilityMode($enabled) {
|
||||
$this->compatibilityMode = (bool) $enabled;
|
||||
if ($enabled) {
|
||||
$this->updateOriginalValues();
|
||||
$this->fields = array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the compatibility mode is active.
|
||||
*/
|
||||
public function getCompatibilityMode() {
|
||||
return $this->compatibilityMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the original values with the interim changes.
|
||||
*
|
||||
* Note: This should be called by the storage controller during a save
|
||||
* operation.
|
||||
*/
|
||||
public function updateOriginalValues() {
|
||||
foreach ($this->fields as $name => $properties) {
|
||||
foreach ($properties as $langcode => $property) {
|
||||
$this->values[$name][$langcode] = $property->getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic getter: Gets the property in default language.
|
||||
*
|
||||
* For compatibility mode to work this must return a reference.
|
||||
*/
|
||||
public function &__get($name) {
|
||||
if ($this->compatibilityMode) {
|
||||
if (!isset($this->values[$name])) {
|
||||
$this->values[$name] = NULL;
|
||||
}
|
||||
return $this->values[$name];
|
||||
}
|
||||
if (isset($this->fields[$name][LANGUAGE_DEFAULT])) {
|
||||
return $this->fields[$name][LANGUAGE_DEFAULT];
|
||||
}
|
||||
if ($this->getPropertyDefinition($name)) {
|
||||
$return = $this->get($name);
|
||||
return $return;
|
||||
}
|
||||
if (!isset($this->$name)) {
|
||||
$this->$name = NULL;
|
||||
}
|
||||
return $this->$name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic getter: Sets the property in default language.
|
||||
*/
|
||||
public function __set($name, $value) {
|
||||
// Support setting values via property objects.
|
||||
if ($value instanceof TypedDataInterface) {
|
||||
$value = $value->getValue();
|
||||
}
|
||||
|
||||
if ($this->compatibilityMode) {
|
||||
$this->values[$name] = $value;
|
||||
}
|
||||
elseif (isset($this->fields[$name][LANGUAGE_DEFAULT])) {
|
||||
$this->fields[$name][LANGUAGE_DEFAULT]->setValue($value);
|
||||
}
|
||||
elseif ($this->getPropertyDefinition($name)) {
|
||||
$this->get($name)->setValue($value);
|
||||
}
|
||||
else {
|
||||
$this->$name = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method.
|
||||
*/
|
||||
public function __isset($name) {
|
||||
if ($this->compatibilityMode) {
|
||||
return isset($this->values[$name]);
|
||||
}
|
||||
elseif ($this->getPropertyDefinition($name)) {
|
||||
return (bool) count($this->get($name));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method.
|
||||
*/
|
||||
public function __unset($name) {
|
||||
if ($this->compatibilityMode) {
|
||||
unset($this->values[$name]);
|
||||
}
|
||||
elseif ($this->getPropertyDefinition($name)) {
|
||||
$this->get($name)->setValue(array());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides Entity::createDuplicate().
|
||||
*/
|
||||
public function createDuplicate() {
|
||||
$duplicate = clone $this;
|
||||
$entity_info = $this->entityInfo();
|
||||
$this->{$entity_info['entity keys']['id']}->value = NULL;
|
||||
|
||||
// Check if the entity type supports UUIDs and generate a new one if so.
|
||||
if (!empty($entity_info['entity keys']['uuid'])) {
|
||||
$uuid = new Uuid();
|
||||
$duplicate->{$entity_info['entity keys']['uuid']}->value = $uuid->generate();
|
||||
}
|
||||
return $duplicate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a deep clone.
|
||||
*/
|
||||
public function __clone() {
|
||||
foreach ($this->fields as $name => $properties) {
|
||||
foreach ($properties as $langcode => $property) {
|
||||
$this->fields[$name][$langcode] = clone $property;
|
||||
if ($property instanceof ContextAwareInterface) {
|
||||
$this->fields[$name][$langcode]->setParent($this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -109,4 +109,37 @@ interface EntityStorageControllerInterface {
|
|||
*/
|
||||
public function save(EntityInterface $entity);
|
||||
|
||||
/**
|
||||
* Gets an array of entity field definitions.
|
||||
*
|
||||
* If a 'bundle' key is present in the given entity definition, fields
|
||||
* specific to this bundle are included.
|
||||
* Entity fields are always multi-valued, so 'list' is TRUE for each
|
||||
* returned field definition.
|
||||
*
|
||||
* @param array $constraints
|
||||
* An array of entity constraints as used for entities in typed data
|
||||
* definitions, i.e. an array having an 'entity type' and optionally a
|
||||
* 'bundle' key. For example:
|
||||
* @code
|
||||
* array(
|
||||
* 'entity type' => 'node',
|
||||
* 'bundle' => 'article',
|
||||
* )
|
||||
* @endcode
|
||||
*
|
||||
* @return array
|
||||
* An array of field definitions of entity fields, keyed by field
|
||||
* name. In addition to the typed data definition keys as described at
|
||||
* typed_data()->create() the follow keys are supported:
|
||||
* - queryable: Whether the field is queryable via EntityFieldQuery.
|
||||
* Defaults to TRUE if 'computed' is FALSE or not set, to FALSE otherwise.
|
||||
* - translatable: Whether the field is translatable. Defaults to FALSE.
|
||||
* - configurable: A boolean indicating whether the field is configurable
|
||||
* via field.module. Defaults to FALSE.
|
||||
*
|
||||
* @see Drupal\Core\TypedData\TypedDataManager::create()
|
||||
* @see typed_data()
|
||||
*/
|
||||
public function getFieldDefinitions(array $constraints);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\Entity\Field\FieldInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Entity\Field;
|
||||
|
||||
use Drupal\Core\TypedData\AccessibleInterface;
|
||||
use Drupal\Core\TypedData\ContextAwareInterface;
|
||||
use Drupal\Core\TypedData\ListInterface;
|
||||
use Drupal\Core\TypedData\TypedDataInterface;
|
||||
|
||||
/**
|
||||
* Interface for fields, being lists of field items.
|
||||
*
|
||||
* Contained items must implement the FieldItemInterface. This
|
||||
* interface is required for every property of an entity. Some methods are
|
||||
* delegated to the first contained item, in particular get() and set() as well
|
||||
* as their magic equivalences.
|
||||
*
|
||||
* Optionally, a typed data object implementing
|
||||
* Drupal\Core\TypedData\TypedDataInterface may be passed to
|
||||
* ArrayAccess::offsetSet() instead of a plain value.
|
||||
*
|
||||
* When implementing this interface which extends Traversable, make sure to list
|
||||
* IteratorAggregate or Iterator before this interface in the implements clause.
|
||||
*/
|
||||
interface FieldInterface extends ListInterface, AccessibleInterface, ContextAwareInterface, TypedDataInterface {
|
||||
|
||||
/**
|
||||
* Delegates to the first item.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\Field\FieldItemInterface::get()
|
||||
*/
|
||||
public function get($property_name);
|
||||
|
||||
/**
|
||||
* Magic getter: Delegates to the first item.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\Field\FieldItemInterface::__get()
|
||||
*/
|
||||
public function __get($property_name);
|
||||
|
||||
/**
|
||||
* Magic setter: Delegates to the first item.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\Field\FieldItemInterface::__set()
|
||||
*/
|
||||
public function __set($property_name, $value);
|
||||
|
||||
/**
|
||||
* Magic method for isset(): Delegates to the first item.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\Field\FieldItemInterface::__isset()
|
||||
*/
|
||||
public function __isset($property_name);
|
||||
|
||||
/**
|
||||
* Magic method for unset(): Delegates to the first item.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\Field\FieldItemInterface::__unset()
|
||||
*/
|
||||
public function __unset($property_name);
|
||||
|
||||
|
||||
/**
|
||||
* Delegates to the first item.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\Field\FieldItemInterface::getPropertyDefinition()
|
||||
*/
|
||||
public function getPropertyDefinition($name);
|
||||
|
||||
/**
|
||||
* Delegates to the first item.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\Field\FieldItemInterface::getPropertyDefinitions()
|
||||
*/
|
||||
public function getPropertyDefinitions();
|
||||
}
|
|
@ -0,0 +1,278 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\Entity\Field\FieldItemBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Entity\Field;
|
||||
|
||||
use Drupal\Core\TypedData\Type\TypedData;
|
||||
use Drupal\Core\TypedData\ComplexDataInterface;
|
||||
use Drupal\Core\TypedData\ContextAwareInterface;
|
||||
use Drupal\Core\TypedData\TypedDataInterface;
|
||||
use Drupal\user;
|
||||
use ArrayIterator;
|
||||
use IteratorAggregate;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* An entity field item.
|
||||
*
|
||||
* Entity field items making use of this base class have to implement
|
||||
* ComplexDataInterface::getPropertyDefinitions().
|
||||
*
|
||||
* @see \Drupal\Core\Entity\Field\FieldItemInterface
|
||||
*/
|
||||
abstract class FieldItemBase extends TypedData implements IteratorAggregate, FieldItemInterface {
|
||||
|
||||
/**
|
||||
* The item delta or name.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* The parent entity field.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\Field\FieldInterface
|
||||
*/
|
||||
protected $parent;
|
||||
|
||||
/**
|
||||
* The array of properties.
|
||||
*
|
||||
* Field objects are instantiated during object construction and cannot be
|
||||
* replaced by others, so computed properties can safely store references on
|
||||
* other properties.
|
||||
*
|
||||
* @var array<TypedDataInterface>
|
||||
*/
|
||||
protected $properties = array();
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::__construct().
|
||||
*/
|
||||
public function __construct(array $definition) {
|
||||
$this->definition = $definition;
|
||||
|
||||
// Initialize all property objects, but postpone the creating of computed
|
||||
// properties to a second step. That way computed properties can safely get
|
||||
// references on non-computed properties during construction.
|
||||
$step2 = array();
|
||||
foreach ($this->getPropertyDefinitions() as $name => $definition) {
|
||||
if (empty($definition['computed'])) {
|
||||
$context = array('name' => $name, 'parent' => $this);
|
||||
$this->properties[$name] = typed_data()->create($definition, NULL, $context);
|
||||
}
|
||||
else {
|
||||
$step2[$name] = $definition;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($step2 as $name => $definition) {
|
||||
$context = array('name' => $name, 'parent' => $this);
|
||||
$this->properties[$name] = typed_data()->create($definition, NULL, $context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::getValue().
|
||||
*/
|
||||
public function getValue() {
|
||||
$values = array();
|
||||
foreach ($this->getProperties() as $name => $property) {
|
||||
$values[$name] = $property->getValue();
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::setValue().
|
||||
*
|
||||
* @param array $values
|
||||
* An array of property values.
|
||||
*/
|
||||
public function setValue($values) {
|
||||
// Treat the values as property value of the first property, if no array is
|
||||
// given and we only have one property.
|
||||
if (!is_array($values) && count($this->properties) == 1) {
|
||||
$keys = array_keys($this->properties);
|
||||
$values = array($keys[0] => $values);
|
||||
}
|
||||
|
||||
foreach ($this->properties as $name => $property) {
|
||||
$property->setValue(isset($values[$name]) ? $values[$name] : NULL);
|
||||
}
|
||||
// @todo: Throw an exception for invalid values once conversion is
|
||||
// totally completed.
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::getString().
|
||||
*/
|
||||
public function getString() {
|
||||
$strings = array();
|
||||
foreach ($this->getProperties() as $property) {
|
||||
$strings[] = $property->getString();
|
||||
}
|
||||
return implode(', ', array_filter($strings));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::validate().
|
||||
*/
|
||||
public function validate() {
|
||||
// @todo implement
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::get().
|
||||
*/
|
||||
public function get($property_name) {
|
||||
if (!isset($this->properties[$property_name])) {
|
||||
throw new InvalidArgumentException('Field ' . check_plain($property_name) . ' is unknown.');
|
||||
}
|
||||
return $this->properties[$property_name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::set().
|
||||
*/
|
||||
public function set($property_name, $value) {
|
||||
$this->get($property_name)->setValue($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements FieldItemInterface::__get().
|
||||
*/
|
||||
public function __get($name) {
|
||||
return $this->get($name)->getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements FieldItemInterface::__set().
|
||||
*/
|
||||
public function __set($name, $value) {
|
||||
// Support setting values via property objects.
|
||||
if ($value instanceof TypedDataInterface) {
|
||||
$value = $value->getValue();
|
||||
}
|
||||
$this->get($name)->setValue($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements FieldItemInterface::__isset().
|
||||
*/
|
||||
public function __isset($name) {
|
||||
return isset($this->properties[$name]) && $this->properties[$name]->getValue() !== NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements FieldItemInterface::__unset().
|
||||
*/
|
||||
public function __unset($name) {
|
||||
if (isset($this->properties[$name])) {
|
||||
$this->properties[$name]->setValue(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ContextAwareInterface::getName().
|
||||
*/
|
||||
public function getName() {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ContextAwareInterface::setName().
|
||||
*/
|
||||
public function setName($name) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ContextAwareInterface::getParent().
|
||||
*
|
||||
* @return \Drupal\Core\Entity\Field\FieldInterface
|
||||
*/
|
||||
public function getParent() {
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ContextAwareInterface::setParent().
|
||||
*/
|
||||
public function setParent($parent) {
|
||||
$this->parent = $parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::getProperties().
|
||||
*/
|
||||
public function getProperties($include_computed = FALSE) {
|
||||
$properties = array();
|
||||
foreach ($this->getPropertyDefinitions() as $name => $definition) {
|
||||
if ($include_computed || empty($definition['computed'])) {
|
||||
$properties[$name] = $this->properties[$name];
|
||||
}
|
||||
}
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::getPropertyValues().
|
||||
*/
|
||||
public function getPropertyValues() {
|
||||
return $this->getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::setPropertyValues().
|
||||
*/
|
||||
public function setPropertyValues($values) {
|
||||
foreach ($values as $name => $value) {
|
||||
$this->get($name)->setValue($value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements IteratorAggregate::getIterator().
|
||||
*/
|
||||
public function getIterator() {
|
||||
return new ArrayIterator($this->getProperties());
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::getPropertyDefinition().
|
||||
*/
|
||||
public function getPropertyDefinition($name) {
|
||||
$definitions = $this->getPropertyDefinitions();
|
||||
return isset($definitions[$name]) ? $definitions[$name] : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::isEmpty().
|
||||
*/
|
||||
public function isEmpty() {
|
||||
foreach ($this->getProperties() as $property) {
|
||||
if ($property->getValue() !== NULL) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a deep clone.
|
||||
*/
|
||||
public function __clone() {
|
||||
foreach ($this->properties as $name => $property) {
|
||||
$this->properties[$name] = clone $property;
|
||||
if ($property instanceof ContextAwareInterface) {
|
||||
$this->properties[$name]->setParent($this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\Entity\Field\FieldItemInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Entity\Field;
|
||||
|
||||
use Drupal\Core\TypedData\ComplexDataInterface;
|
||||
use Drupal\Core\TypedData\ContextAwareInterface;
|
||||
use Drupal\Core\TypedData\TypedDataInterface;
|
||||
|
||||
/**
|
||||
* Interface for entity field items, which are complex data objects
|
||||
* containing the values.
|
||||
*
|
||||
* When implementing this interface which extends Traversable, make sure to list
|
||||
* IteratorAggregate or Iterator before this interface in the implements clause.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\Field\FieldInterface
|
||||
* @see \Drupal\Core\Entity\Field\FieldItemBase
|
||||
*/
|
||||
interface FieldItemInterface extends ComplexDataInterface, ContextAwareInterface, TypedDataInterface {
|
||||
|
||||
/**
|
||||
* Magic getter: Get the property value.
|
||||
*
|
||||
* @param $property_name
|
||||
* The name of the property to get; e.g., 'title' or 'name'.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* If a not existing property is accessed.
|
||||
*
|
||||
* @return \Drupal\Core\TypedData\TypedDataInterface
|
||||
* The property object.
|
||||
*/
|
||||
public function __get($property_name);
|
||||
|
||||
/**
|
||||
* Magic setter: Set the property value.
|
||||
*
|
||||
* @param $property_name
|
||||
* The name of the property to set; e.g., 'title' or 'name'.
|
||||
* @param $value
|
||||
* The value to set, or NULL to unset the property. Optionally, a typed
|
||||
* data object implementing Drupal\Core\TypedData\TypedDataInterface may be
|
||||
* passed instead of a plain value.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* If a not existing property is set.
|
||||
*/
|
||||
public function __set($property_name, $value);
|
||||
|
||||
/**
|
||||
* Magic method for isset().
|
||||
*
|
||||
* @param $property_name
|
||||
* The name of the property to get; e.g., 'title' or 'name'.
|
||||
*
|
||||
* @return boolean
|
||||
* Returns TRUE if the property exists and is set, FALSE otherwise.
|
||||
*/
|
||||
public function __isset($property_name);
|
||||
|
||||
/**
|
||||
* Magic method for unset().
|
||||
*
|
||||
* @param $property_name
|
||||
* The name of the property to get; e.g., 'title' or 'name'.
|
||||
*/
|
||||
public function __unset($property_name);
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\Entity\Field\Type\BooleanItem.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Entity\Field\Type;
|
||||
|
||||
use Drupal\Core\Entity\Field\FieldItemBase;
|
||||
|
||||
/**
|
||||
* Defines the 'boolean_field' entity field item.
|
||||
*/
|
||||
class BooleanItem extends FieldItemBase {
|
||||
|
||||
/**
|
||||
* Field definitions of the contained properties.
|
||||
*
|
||||
* @see self::getPropertyDefinitions()
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
static $propertyDefinitions;
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::getPropertyDefinitions().
|
||||
*/
|
||||
public function getPropertyDefinitions() {
|
||||
|
||||
if (!isset(self::$propertyDefinitions)) {
|
||||
self::$propertyDefinitions['value'] = array(
|
||||
'type' => 'boolean',
|
||||
'label' => t('Boolean value'),
|
||||
);
|
||||
}
|
||||
return self::$propertyDefinitions;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\Entity\Field\Type\DateItem.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Entity\Field\Type;
|
||||
|
||||
use Drupal\Core\Entity\Field\FieldItemBase;
|
||||
|
||||
/**
|
||||
* Defines the 'date_field' entity field item.
|
||||
*/
|
||||
class DateItem extends FieldItemBase {
|
||||
|
||||
/**
|
||||
* Field definitions of the contained properties.
|
||||
*
|
||||
* @see self::getPropertyDefinitions()
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
static $propertyDefinitions;
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::getPropertyDefinitions().
|
||||
*/
|
||||
public function getPropertyDefinitions() {
|
||||
|
||||
if (!isset(self::$propertyDefinitions)) {
|
||||
self::$propertyDefinitions['value'] = array(
|
||||
'type' => 'date',
|
||||
'label' => t('Date value'),
|
||||
);
|
||||
}
|
||||
return self::$propertyDefinitions;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\Entity\Field\Type\EntityReferenceItem.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Entity\Field\Type;
|
||||
|
||||
use Drupal\Core\Entity\Field\FieldItemBase;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Defines the 'entityreference_field' entity field item.
|
||||
*
|
||||
* Required settings (below the definition's 'settings' key) are:
|
||||
* - entity type: The entity type to reference.
|
||||
*/
|
||||
class EntityReferenceItem extends FieldItemBase {
|
||||
|
||||
/**
|
||||
* Field definitions of the contained properties.
|
||||
*
|
||||
* @see self::getPropertyDefinitions()
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
static $propertyDefinitions;
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::getPropertyDefinitions().
|
||||
*/
|
||||
public function getPropertyDefinitions() {
|
||||
// Definitions vary by entity type, so key them by entity type.
|
||||
$entity_type = $this->definition['settings']['entity type'];
|
||||
|
||||
if (!isset(self::$propertyDefinitions[$entity_type])) {
|
||||
self::$propertyDefinitions[$entity_type]['value'] = array(
|
||||
// @todo: Lookup the entity type's ID data type and use it here.
|
||||
'type' => 'integer',
|
||||
'label' => t('Entity ID'),
|
||||
);
|
||||
self::$propertyDefinitions[$entity_type]['entity'] = array(
|
||||
'type' => 'entity',
|
||||
'constraints' => array(
|
||||
'entity type' => $entity_type,
|
||||
),
|
||||
'label' => t('Entity'),
|
||||
'description' => t('The referenced entity'),
|
||||
// The entity object is computed out of the entity id.
|
||||
'computed' => TRUE,
|
||||
'read-only' => FALSE,
|
||||
'settings' => array('id source' => 'value'),
|
||||
);
|
||||
}
|
||||
return self::$propertyDefinitions[$entity_type];
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides FieldItemBase::setValue().
|
||||
*/
|
||||
public function setValue($values) {
|
||||
// Treat the values as property value of the entity field, if no array
|
||||
// is given.
|
||||
if (!is_array($values)) {
|
||||
$values = array('entity' => $values);
|
||||
}
|
||||
|
||||
// Entity is computed out of the ID, so we only need to update the ID. Only
|
||||
// set the entity field if no ID is given.
|
||||
if (!empty($values['value'])) {
|
||||
$this->properties['value']->setValue($values['value']);
|
||||
}
|
||||
else {
|
||||
$this->properties['entity']->setValue(isset($values['entity']) ? $values['entity'] : NULL);
|
||||
}
|
||||
unset($values['entity'], $values['value']);
|
||||
if ($values) {
|
||||
throw new InvalidArgumentException('Property ' . key($values) . ' is unknown.');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,246 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\Entity\Type\EntityTranslation.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Entity\Field\Type;
|
||||
|
||||
use Drupal\Core\TypedData\Type\TypedData;
|
||||
use Drupal\Core\TypedData\AccessibleInterface;
|
||||
use Drupal\Core\TypedData\ComplexDataInterface;
|
||||
use Drupal\Core\TypedData\ContextAwareInterface;
|
||||
use Drupal\Core\TypedData\TypedDataInterface;
|
||||
use ArrayIterator;
|
||||
use IteratorAggregate;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Makes translated entity properties available via the Field API.
|
||||
*/
|
||||
class EntityTranslation extends TypedData implements IteratorAggregate, AccessibleInterface, ComplexDataInterface, ContextAwareInterface {
|
||||
|
||||
/**
|
||||
* The array of translated properties, each being an instance of
|
||||
* FieldInterface.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $properties = array();
|
||||
|
||||
/**
|
||||
* The language code of the translation.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $langcode;
|
||||
|
||||
/**
|
||||
* The parent entity.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityInterface
|
||||
*/
|
||||
protected $parent;
|
||||
|
||||
/**
|
||||
* Whether the entity translation acts in strict mode.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $strict = TRUE;
|
||||
|
||||
/**
|
||||
* Returns whether the entity translation acts in strict mode.
|
||||
*
|
||||
* @return boolean
|
||||
* Whether the entity translation acts in strict mode.
|
||||
*/
|
||||
public function getStrictMode() {
|
||||
return $this->strict;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the entity translation acts in strict mode.
|
||||
*
|
||||
* @param boolean $strict
|
||||
* Whether the entity translation acts in strict mode.
|
||||
*
|
||||
* @see \Drupal\Core\TypedData\TranslatableInterface::getTranslation()
|
||||
*/
|
||||
public function setStrictMode($strict = TRUE) {
|
||||
$this->strict = $strict;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ContextAwareInterface::getName().
|
||||
*/
|
||||
public function getName() {
|
||||
// The name of the translation is the language code.
|
||||
return $this->langcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ContextAwareInterface::setName().
|
||||
*/
|
||||
public function setName($name) {
|
||||
// The name of the translation is the language code.
|
||||
$this->langcode = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ContextAwareInterface::getParent().
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface
|
||||
*/
|
||||
public function getParent() {
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ContextAwareInterface::setParent().
|
||||
*/
|
||||
public function setParent($parent) {
|
||||
$this->parent = $parent;
|
||||
}
|
||||
/**
|
||||
* Implements TypedDataInterface::getValue().
|
||||
*/
|
||||
public function getValue() {
|
||||
// The value of the translation is the array of translated property objects.
|
||||
return $this->properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::setValue().
|
||||
*/
|
||||
public function setValue($values) {
|
||||
$this->properties = $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::getString().
|
||||
*/
|
||||
public function getString() {
|
||||
$strings = array();
|
||||
foreach ($this->getProperties() as $property) {
|
||||
$strings[] = $property->getString();
|
||||
}
|
||||
return implode(', ', array_filter($strings));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::get().
|
||||
*/
|
||||
public function get($property_name) {
|
||||
$definitions = $this->getPropertyDefinitions();
|
||||
if (!isset($definitions[$property_name])) {
|
||||
throw new InvalidArgumentException(format_string('Field @name is unknown or not translatable.', array('@name' => $property_name)));
|
||||
}
|
||||
return $this->properties[$property_name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::set().
|
||||
*/
|
||||
public function set($property_name, $value) {
|
||||
$this->get($property_name)->setValue($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::getProperties().
|
||||
*/
|
||||
public function getProperties($include_computed = FALSE) {
|
||||
$properties = array();
|
||||
foreach ($this->getPropertyDefinitions() as $name => $definition) {
|
||||
if ($include_computed || empty($definition['computed'])) {
|
||||
$properties[$name] = $this->get($name);
|
||||
}
|
||||
}
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic getter: Gets the translated property.
|
||||
*/
|
||||
public function __get($name) {
|
||||
return $this->get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic getter: Sets the translated property.
|
||||
*/
|
||||
public function __set($name, $value) {
|
||||
$this->get($name)->setValue($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements IteratorAggregate::getIterator().
|
||||
*/
|
||||
public function getIterator() {
|
||||
return new ArrayIterator($this->getProperties());
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::getPropertyDefinition().
|
||||
*/
|
||||
public function getPropertyDefinition($name) {
|
||||
$definitions = $this->getPropertyDefinitions();
|
||||
return isset($definitions[$name]) ? $definitions[$name] : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::getPropertyDefinitions().
|
||||
*/
|
||||
public function getPropertyDefinitions() {
|
||||
$definitions = array();
|
||||
foreach ($this->parent->getPropertyDefinitions() as $name => $definition) {
|
||||
if (!empty($definition['translatable']) || !$this->strict) {
|
||||
$definitions[$name] = $definition;
|
||||
}
|
||||
}
|
||||
return $definitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::getPropertyValues().
|
||||
*/
|
||||
public function getPropertyValues() {
|
||||
return $this->getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::setPropertyValues().
|
||||
*/
|
||||
public function setPropertyValues($values) {
|
||||
foreach ($values as $name => $value) {
|
||||
$this->get($name)->setValue($value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::isEmpty().
|
||||
*/
|
||||
public function isEmpty() {
|
||||
foreach ($this->getProperties() as $property) {
|
||||
if ($property->getValue() !== NULL) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements AccessibleInterface::access().
|
||||
*/
|
||||
public function access(\Drupal\user\User $account = NULL) {
|
||||
// @todo implement
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::validate().
|
||||
*/
|
||||
public function validate($value = NULL) {
|
||||
// @todo implement
|
||||
}
|
||||
}
|
|
@ -0,0 +1,233 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\Entity\Field\Type\EntityWrapper.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Entity\Field\Type;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\TypedData\Type\TypedData;
|
||||
use Drupal\Core\TypedData\ComplexDataInterface;
|
||||
use Drupal\Core\TypedData\ContextAwareInterface;
|
||||
use Drupal\Core\TypedData\TypedDataInterface;
|
||||
use ArrayIterator;
|
||||
use IteratorAggregate;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Defines an 'entity' data type, e.g. the computed 'entity' property of entity references.
|
||||
*
|
||||
* This object wraps the regular entity object and implements the
|
||||
* ComplexDataInterface by forwarding most of its methods to the wrapped entity
|
||||
* (if set).
|
||||
*
|
||||
* The plain value of this wrapper is the entity object, i.e. an instance of
|
||||
* Drupal\Core\Entity\EntityInterface. For setting the value the entity object
|
||||
* or the entity ID may be passed, whereas passing the ID is only supported if
|
||||
* an 'entity type' constraint is specified.
|
||||
*
|
||||
* Supported constraints (below the definition's 'constraints' key) are:
|
||||
* - entity type: The entity type.
|
||||
* - bundle: The bundle or an array of possible bundles.
|
||||
*
|
||||
* Supported settings (below the definition's 'settings' key) are:
|
||||
* - id source: If used as computed property, the ID property used to load
|
||||
* the entity object.
|
||||
*/
|
||||
class EntityWrapper extends TypedData implements IteratorAggregate, ComplexDataInterface, ContextAwareInterface {
|
||||
|
||||
/**
|
||||
* The name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* The parent data structure.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $parent;
|
||||
|
||||
/**
|
||||
* The referenced entity type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $entityType;
|
||||
|
||||
/**
|
||||
* The entity ID if no 'id source' is used.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::__construct().
|
||||
*/
|
||||
public function __construct(array $definition) {
|
||||
$this->definition = $definition + array('constraints' => array());
|
||||
$this->entityType = isset($this->definition['constraints']['entity type']) ? $this->definition['constraints']['entity type'] : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::getValue().
|
||||
*/
|
||||
public function getValue() {
|
||||
$source = $this->getIdSource();
|
||||
$id = $source ? $source->getValue() : $this->id;
|
||||
return $id ? entity_load($this->entityType, $id) : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get the typed data object holding the source entity ID.
|
||||
*
|
||||
* @return \Drupal\Core\TypedData\TypedDataInterface|FALSE
|
||||
*/
|
||||
protected function getIdSource() {
|
||||
return !empty($this->definition['settings']['id source']) ? $this->parent->get($this->definition['settings']['id source']) : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::setValue().
|
||||
*
|
||||
* Both the entity ID and the entity object may be passed as value.
|
||||
*/
|
||||
public function setValue($value) {
|
||||
// Support passing in the entity object.
|
||||
if ($value instanceof EntityInterface) {
|
||||
$this->entityType = $value->entityType();
|
||||
$value = $value->id();
|
||||
}
|
||||
elseif (isset($value) && !(is_scalar($value) && !empty($this->definition['constraints']['entity type']))) {
|
||||
throw new InvalidArgumentException('Value is no valid entity.');
|
||||
}
|
||||
|
||||
$source = $this->getIdSource();
|
||||
if ($source) {
|
||||
$source->setValue($value);
|
||||
}
|
||||
else {
|
||||
$this->id = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::getString().
|
||||
*/
|
||||
public function getString() {
|
||||
$entity = $this->getValue();
|
||||
return $entity ? $entity->label() : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::validate().
|
||||
*/
|
||||
public function validate($value = NULL) {
|
||||
// TODO: Implement validate() method.
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements IteratorAggregate::getIterator().
|
||||
*/
|
||||
public function getIterator() {
|
||||
$entity = $this->getValue();
|
||||
return $entity ? $entity->getIterator() : new ArrayIterator(array());
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::get().
|
||||
*/
|
||||
public function get($property_name) {
|
||||
$entity = $this->getValue();
|
||||
// @todo: Allow navigating through the tree without data as well.
|
||||
return $entity ? $entity->get($property_name) : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::set().
|
||||
*/
|
||||
public function set($property_name, $value) {
|
||||
$this->get($property_name)->setValue($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ContextAwareInterface::getName().
|
||||
*/
|
||||
public function getName() {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ContextAwareInterface::setName().
|
||||
*/
|
||||
public function setName($name) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ContextAwareInterface::getParent().
|
||||
*/
|
||||
public function getParent() {
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ContextAwareInterface::setParent().
|
||||
*/
|
||||
public function setParent($parent) {
|
||||
$this->parent = $parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::getProperties().
|
||||
*/
|
||||
public function getProperties($include_computed = FALSE) {
|
||||
$entity = $this->getValue();
|
||||
return $entity ? $entity->getProperties($include_computed) : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::getPropertyDefinition().
|
||||
*/
|
||||
public function getPropertyDefinition($name) {
|
||||
$definitions = $this->getPropertyDefinitions();
|
||||
return isset($definitions[$name]) ? $definitions[$name] : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::getPropertyDefinitions().
|
||||
*/
|
||||
public function getPropertyDefinitions() {
|
||||
// @todo: Support getting definitions if multiple bundles are specified.
|
||||
return entity_get_controller($this->entityType)->getFieldDefinitions($this->definition['constraints']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::getPropertyValues().
|
||||
*/
|
||||
public function getPropertyValues() {
|
||||
$entity = $this->getValue();
|
||||
return $entity ? $entity->getPropertyValues() : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::setPropertyValues().
|
||||
*/
|
||||
public function setPropertyValues($values) {
|
||||
if ($entity = $this->getValue()) {
|
||||
$entity->setPropertyValues($values);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::isEmpty().
|
||||
*/
|
||||
public function isEmpty() {
|
||||
return (bool) $this->getValue();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,303 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\Entity\Field\Type\Field.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Entity\Field\Type;
|
||||
|
||||
use Drupal\Core\Entity\Field\FieldInterface;
|
||||
use Drupal\Core\TypedData\TypedDataInterface;
|
||||
use Drupal\Core\TypedData\Type\TypedData;
|
||||
use Drupal\user\User;
|
||||
use ArrayIterator;
|
||||
use IteratorAggregate;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* An entity field, i.e. a list of field items.
|
||||
*
|
||||
* An entity field is a list of field items, which contain only primitive
|
||||
* properties or entity references. Note that even single-valued entity
|
||||
* fields are represented as list of items, however for easy access to the
|
||||
* contained item the entity field delegates __get() and __set() calls
|
||||
* directly to the first item.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\Field\FieldInterface
|
||||
*/
|
||||
class Field extends TypedData implements IteratorAggregate, FieldInterface {
|
||||
|
||||
/**
|
||||
* The entity field name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* The parent entity.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityInterface
|
||||
*/
|
||||
protected $parent;
|
||||
|
||||
/**
|
||||
* Numerically indexed array of field items, implementing the
|
||||
* FieldItemInterface.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $list = array();
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::getValue().
|
||||
*/
|
||||
public function getValue() {
|
||||
$values = array();
|
||||
foreach ($this->list as $delta => $item) {
|
||||
$values[$delta] = !$item->isEmpty() ? $item->getValue() : NULL;
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::setValue().
|
||||
*
|
||||
* @param array $values
|
||||
* An array of values of the field items.
|
||||
*/
|
||||
public function setValue($values) {
|
||||
if (!empty($values)) {
|
||||
|
||||
// Support passing in only the value of the first item.
|
||||
if (!is_array($values) || !is_numeric(current(array_keys($values)))) {
|
||||
$values = array(0 => $values);
|
||||
}
|
||||
|
||||
if (!is_array($values)) {
|
||||
throw new InvalidArgumentException("An entity field requires a numerically indexed array of items as value.");
|
||||
}
|
||||
// Clear the values of properties for which no value has been passed.
|
||||
foreach (array_diff_key($this->list, $values) as $delta => $item) {
|
||||
unset($this->list[$delta]);
|
||||
}
|
||||
|
||||
// Set the values.
|
||||
foreach ($values as $delta => $value) {
|
||||
if (!is_numeric($delta)) {
|
||||
throw new InvalidArgumentException('Unable to set a value with a non-numeric delta in a list.');
|
||||
}
|
||||
elseif (!isset($this->list[$delta])) {
|
||||
$this->list[$delta] = $this->createItem($value);
|
||||
}
|
||||
else {
|
||||
$this->list[$delta]->setValue($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->list = array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the field.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getString() {
|
||||
$strings = array();
|
||||
foreach ($this->list() as $item) {
|
||||
$strings[] = $item->getString();
|
||||
}
|
||||
return implode(', ', array_filter($strings));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::validate().
|
||||
*/
|
||||
public function validate() {
|
||||
// @todo implement
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ArrayAccess::offsetExists().
|
||||
*/
|
||||
public function offsetExists($offset) {
|
||||
return array_key_exists($offset, $this->list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ArrayAccess::offsetUnset().
|
||||
*/
|
||||
public function offsetUnset($offset) {
|
||||
unset($this->list[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ArrayAccess::offsetGet().
|
||||
*/
|
||||
public function offsetGet($offset) {
|
||||
if (!is_numeric($offset)) {
|
||||
throw new InvalidArgumentException('Unable to get a value with a non-numeric delta in a list.');
|
||||
}
|
||||
// Allow getting not yet existing items as well.
|
||||
// @todo: Maybe add a public createItem() method in addition?
|
||||
elseif (!isset($this->list[$offset])) {
|
||||
$this->list[$offset] = $this->createItem();
|
||||
}
|
||||
return $this->list[$offset];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for creating a list item object.
|
||||
*
|
||||
* @return \Drupal\Core\TypedData\TypedDataInterface
|
||||
*/
|
||||
protected function createItem($value = NULL) {
|
||||
$context = array('parent' => $this);
|
||||
return typed_data()->create(array('list' => FALSE) + $this->definition, $value, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ArrayAccess::offsetSet().
|
||||
*/
|
||||
public function offsetSet($offset, $value) {
|
||||
if (!isset($offset)) {
|
||||
// The [] operator has been used so point at a new entry.
|
||||
$offset = $this->list ? max(array_keys($this->list)) + 1 : 0;
|
||||
}
|
||||
if (is_numeric($offset)) {
|
||||
// Support setting values via typed data objects.
|
||||
if ($value instanceof TypedDataInterface) {
|
||||
$value = $value->getValue();
|
||||
}
|
||||
$this->offsetGet($offset)->setValue($value);
|
||||
}
|
||||
else {
|
||||
throw new InvalidArgumentException('Unable to set a value with a non-numeric delta in a list.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements IteratorAggregate::getIterator().
|
||||
*/
|
||||
public function getIterator() {
|
||||
return new ArrayIterator($this->list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Countable::count().
|
||||
*/
|
||||
public function count() {
|
||||
return count($this->list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ContextAwareInterface::getName().
|
||||
*/
|
||||
public function getName() {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ContextAwareInterface::setName().
|
||||
*/
|
||||
public function setName($name) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ContextAwareInterface::getParent().
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface
|
||||
*/
|
||||
public function getParent() {
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ContextAwareInterface::setParent().
|
||||
*/
|
||||
public function setParent($parent) {
|
||||
$this->parent = $parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate.
|
||||
*/
|
||||
public function getPropertyDefinition($name) {
|
||||
return $this->offsetGet(0)->getPropertyDefinition($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate.
|
||||
*/
|
||||
public function getPropertyDefinitions() {
|
||||
return $this->offsetGet(0)->getPropertyDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate.
|
||||
*/
|
||||
public function __get($property_name) {
|
||||
return $this->offsetGet(0)->__get($property_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate.
|
||||
*/
|
||||
public function get($property_name) {
|
||||
return $this->offsetGet(0)->get($property_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate.
|
||||
*/
|
||||
public function __set($property_name, $value) {
|
||||
$this->offsetGet(0)->__set($property_name, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate.
|
||||
*/
|
||||
public function __isset($property_name) {
|
||||
return $this->offsetGet(0)->__isset($property_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate.
|
||||
*/
|
||||
public function __unset($property_name) {
|
||||
return $this->offsetGet(0)->__unset($property_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ListInterface::isEmpty().
|
||||
*/
|
||||
public function isEmpty() {
|
||||
foreach ($this->list as $item) {
|
||||
if (!$item->isEmpty()) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a deep clone.
|
||||
*/
|
||||
public function __clone() {
|
||||
foreach ($this->list as $delta => $property) {
|
||||
$this->list[$delta] = clone $property;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements AccessibleInterface::access().
|
||||
*/
|
||||
public function access(User $account = NULL) {
|
||||
// TODO: Implement access() method. Use item access.
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\Entity\Field\Type\IntegerItem.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Entity\Field\Type;
|
||||
|
||||
use Drupal\Core\Entity\Field\FieldItemBase;
|
||||
|
||||
/**
|
||||
* Defines the 'integer_field' entity field item.
|
||||
*/
|
||||
class IntegerItem extends FieldItemBase {
|
||||
|
||||
/**
|
||||
* Field definitions of the contained properties.
|
||||
*
|
||||
* @see self::getPropertyDefinitions()
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
static $propertyDefinitions;
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::getPropertyDefinitions().
|
||||
*/
|
||||
public function getPropertyDefinitions() {
|
||||
|
||||
if (!isset(self::$propertyDefinitions)) {
|
||||
self::$propertyDefinitions['value'] = array(
|
||||
'type' => 'integer',
|
||||
'label' => t('Integer value'),
|
||||
);
|
||||
}
|
||||
return self::$propertyDefinitions;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\Entity\Field\Type\LanguageItem.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Entity\Field\Type;
|
||||
|
||||
use Drupal\Core\Entity\Field\FieldItemBase;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Defines the 'language_field' entity field item.
|
||||
*/
|
||||
class LanguageItem extends FieldItemBase {
|
||||
|
||||
/**
|
||||
* Array of property definitions of contained properties.
|
||||
*
|
||||
* @see PropertyEntityReferenceItem::getPropertyDefinitions()
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
static $propertyDefinitions;
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::getPropertyDefinitions().
|
||||
*/
|
||||
public function getPropertyDefinitions() {
|
||||
|
||||
if (!isset(self::$propertyDefinitions)) {
|
||||
self::$propertyDefinitions['value'] = array(
|
||||
'type' => 'string',
|
||||
'label' => t('Language code'),
|
||||
);
|
||||
self::$propertyDefinitions['language'] = array(
|
||||
'type' => 'language',
|
||||
'label' => t('Language object'),
|
||||
// The language object is retrieved via the language code.
|
||||
'computed' => TRUE,
|
||||
'read-only' => FALSE,
|
||||
'settings' => array('langcode source' => 'value'),
|
||||
);
|
||||
}
|
||||
return self::$propertyDefinitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides FieldItemBase::setValue().
|
||||
*/
|
||||
public function setValue($values) {
|
||||
// Treat the values as property value of the object property, if no array
|
||||
// is given.
|
||||
if (!is_array($values)) {
|
||||
$values = array('language' => $values);
|
||||
}
|
||||
|
||||
// Language is computed out of the langcode, so we only need to update the
|
||||
// langcode. Only set the language property if no langcode is given.
|
||||
if (!empty($values['value'])) {
|
||||
$this->properties['value']->setValue($values['value']);
|
||||
}
|
||||
else {
|
||||
$this->properties['language']->setValue(isset($values['language']) ? $values['language'] : NULL);
|
||||
}
|
||||
unset($values['language'], $values['value']);
|
||||
if ($values) {
|
||||
throw new InvalidArgumentException('Property ' . key($values) . ' is unknown.');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\Entity\Field\Type\StringItem.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Entity\Field\Type;
|
||||
|
||||
use Drupal\Core\Entity\Field\FieldItemBase;
|
||||
|
||||
/**
|
||||
* Defines the 'string_field' entity field item.
|
||||
*/
|
||||
class StringItem extends FieldItemBase {
|
||||
|
||||
/**
|
||||
* Field definitions of the contained properties.
|
||||
*
|
||||
* @see self::getPropertyDefinitions()
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
static $propertyDefinitions;
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::getPropertyDefinitions().
|
||||
*/
|
||||
public function getPropertyDefinitions() {
|
||||
|
||||
if (!isset(self::$propertyDefinitions)) {
|
||||
self::$propertyDefinitions['value'] = array(
|
||||
'type' => 'string',
|
||||
'label' => t('Text value'),
|
||||
);
|
||||
}
|
||||
return self::$propertyDefinitions;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\TypedData\AccessibleInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\TypedData;
|
||||
|
||||
/**
|
||||
* Interface for checking access.
|
||||
*/
|
||||
interface AccessibleInterface {
|
||||
|
||||
/**
|
||||
* Checks data value access.
|
||||
*
|
||||
* @param \Drupal\user\User $account
|
||||
* (optional) The user account to check access for. Defaults to the current
|
||||
* user.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the given user has access; otherwise FALSE.
|
||||
*/
|
||||
public function access(\Drupal\user\User $account = NULL);
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\TypedData\ComplexDataInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\TypedData;
|
||||
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* Interface for complex data; i.e. data containing named and typed properties.
|
||||
*
|
||||
* This is implemented by entities as well as by field item classes of
|
||||
* entities.
|
||||
*
|
||||
* When implementing this interface which extends Traversable, make sure to list
|
||||
* IteratorAggregate or Iterator before this interface in the implements clause.
|
||||
*/
|
||||
interface ComplexDataInterface extends Traversable {
|
||||
|
||||
/**
|
||||
* Gets a property object.
|
||||
*
|
||||
* @param $property_name
|
||||
* The name of the property to get; e.g., 'title' or 'name'.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* If an invalid property name is given.
|
||||
*
|
||||
* @return \Drupal\Core\TypedData\TypedDataInterface
|
||||
* The property object.
|
||||
*/
|
||||
public function get($property_name);
|
||||
|
||||
/**
|
||||
* Sets a property value.
|
||||
*
|
||||
* @param $property_name
|
||||
* The name of the property to set; e.g., 'title' or 'name'.
|
||||
* @param $value
|
||||
* The value to set, or NULL to unset the property.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* If the specified property does not exist.
|
||||
*
|
||||
* @return \Drupal\Core\TypedData\TypedDataInterface
|
||||
* The property object.
|
||||
*/
|
||||
public function set($property_name, $value);
|
||||
|
||||
/**
|
||||
* Gets an array of property objects.
|
||||
*
|
||||
* @param bool $include_computed
|
||||
* If set to TRUE, computed properties are included. Defaults to FALSE.
|
||||
*
|
||||
* @return array
|
||||
* An array of property objects implementing the TypedDataInterface, keyed
|
||||
* by property name.
|
||||
*/
|
||||
public function getProperties($include_computed = FALSE);
|
||||
|
||||
/**
|
||||
* Gets an array of property values.
|
||||
*
|
||||
* Gets an array of plain property values including all not-computed
|
||||
* properties.
|
||||
*
|
||||
* @return array
|
||||
* An array keyed by property name containing the property value.
|
||||
*/
|
||||
public function getPropertyValues();
|
||||
|
||||
/**
|
||||
* Sets multiple property values.
|
||||
*
|
||||
* @param array
|
||||
* The array of property values to set, keyed by property name.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* If the value of a not existing property is to be set.
|
||||
* @throws \Drupal\Core\TypedData\ReadOnlyException
|
||||
* If a read-only property is set.
|
||||
*/
|
||||
public function setPropertyValues($values);
|
||||
|
||||
/**
|
||||
* Gets the definition of a contained property.
|
||||
*
|
||||
* @param string $name
|
||||
* The name of property.
|
||||
*
|
||||
* @return array|FALSE
|
||||
* The definition of the property or FALSE if the property does not exist.
|
||||
*/
|
||||
public function getPropertyDefinition($name);
|
||||
|
||||
/**
|
||||
* Gets an array property definitions of contained properties.
|
||||
*
|
||||
* @param array $definition
|
||||
* The definition of the container's property, e.g. the definition of an
|
||||
* entity reference property.
|
||||
*
|
||||
* @return array
|
||||
* An array of property definitions of contained properties, keyed by
|
||||
* property name.
|
||||
*/
|
||||
public function getPropertyDefinitions();
|
||||
|
||||
/**
|
||||
* Determines whether the data structure is empty.
|
||||
*
|
||||
* @return boolean
|
||||
* TRUE if the data structure is empty, FALSE otherwise.
|
||||
*/
|
||||
public function isEmpty();
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\TypedData\ContextAwareInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\TypedData;
|
||||
|
||||
/**
|
||||
* Interface for context aware data.
|
||||
*/
|
||||
interface ContextAwareInterface {
|
||||
|
||||
/**
|
||||
* Returns the name of a property or item.
|
||||
*
|
||||
* @return string
|
||||
* If the data is a property of some complex data, the name of the property.
|
||||
* If the data is an item of a list, the name is the numeric position of the
|
||||
* item in the list, starting with 0. Otherwise, NULL is returned.
|
||||
*/
|
||||
public function getName();
|
||||
|
||||
/**
|
||||
* Sets the name of a property or item.
|
||||
*
|
||||
* This method is supposed to be used by the parental data structure in order
|
||||
* to provide appropriate context only.
|
||||
*
|
||||
* @param string $name
|
||||
* The name to set for a property or item.
|
||||
*
|
||||
* @see ContextAwareInterface::getName()
|
||||
*/
|
||||
public function setName($name);
|
||||
|
||||
/**
|
||||
* Returns the parent data structure; i.e. either complex data or a list.
|
||||
*
|
||||
* @return Drupal\Core\TypedData\ComplexDataInterface|Drupal\Core\TypedData\ListInterface
|
||||
* The parent data structure; either complex data or a list.
|
||||
*/
|
||||
public function getParent();
|
||||
|
||||
/**
|
||||
* Sets the parent of a property or item.
|
||||
*
|
||||
* This method is supposed to be used by the parental data structure in order
|
||||
* to provide appropriate context only.
|
||||
*
|
||||
* @param mixed $parent
|
||||
* The parent data structure; either complex data or a list.
|
||||
*
|
||||
* @see ContextAwareInterface::getParent()
|
||||
*/
|
||||
public function setParent($parent);
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\TypedData\ListInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\TypedData;
|
||||
|
||||
use ArrayAccess;
|
||||
use Countable;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* Interface for a list of typed data.
|
||||
*
|
||||
* A list of typed data contains only items of the same type, is ordered and may
|
||||
* contain duplicates.
|
||||
*
|
||||
* When implementing this interface which extends Traversable, make sure to list
|
||||
* IteratorAggregate or Iterator before this interface in the implements clause.
|
||||
*/
|
||||
interface ListInterface extends ArrayAccess, Countable, Traversable {
|
||||
|
||||
/**
|
||||
* Determines whether the list contains any non-empty items.
|
||||
*
|
||||
* @return boolean
|
||||
* TRUE if the list is empty, FALSE otherwise.
|
||||
*/
|
||||
public function isEmpty();
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\TypedData\MissingContextException.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\TypedData;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when data wrappers miss contextual information.
|
||||
*/
|
||||
class MissingContextException extends Exception {
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\TypedData\Primitive.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\TypedData;
|
||||
|
||||
/**
|
||||
* Class that holds constants for all primitive data types.
|
||||
*/
|
||||
final class Primitive {
|
||||
|
||||
/**
|
||||
* The BOOLEAN primitive data type.
|
||||
*/
|
||||
const BOOLEAN = 1;
|
||||
|
||||
/**
|
||||
* The STRING primitive data type.
|
||||
*/
|
||||
const STRING = 2;
|
||||
|
||||
/**
|
||||
* The INTEGER primitive data type.
|
||||
*/
|
||||
const INTEGER = 3;
|
||||
|
||||
/**
|
||||
* The FLOAT primitive data type.
|
||||
*/
|
||||
const FLOAT = 4;
|
||||
|
||||
/**
|
||||
* The DATE primitive data type.
|
||||
*/
|
||||
const DATE = 5;
|
||||
|
||||
/**
|
||||
* The DURATION primitive data type.
|
||||
*/
|
||||
const DURATION = 6;
|
||||
|
||||
/**
|
||||
* The URI primitive data type.
|
||||
*/
|
||||
const URI = 7;
|
||||
|
||||
/**
|
||||
* The BINARY primitive data type.
|
||||
*/
|
||||
const BINARY = 8;
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\TypedData\ReadOnlyException.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\TypedData;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when trying to write or set ready-only data.
|
||||
*/
|
||||
class ReadOnlyException extends Exception {
|
||||
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\TypedData\TranslatableInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\TypedData;
|
||||
|
||||
/**
|
||||
* Interface for translatable data.
|
||||
*/
|
||||
interface TranslatableInterface {
|
||||
|
||||
/**
|
||||
* Returns the default language.
|
||||
*
|
||||
* @return
|
||||
* The language object.
|
||||
*/
|
||||
public function language();
|
||||
|
||||
/**
|
||||
* Returns the languages the data is translated to.
|
||||
*
|
||||
* @param bool $include_default
|
||||
* Whether the default language should be included.
|
||||
*
|
||||
* @return
|
||||
* An array of language objects, keyed by language codes.
|
||||
*/
|
||||
public function getTranslationLanguages($include_default = TRUE);
|
||||
|
||||
/**
|
||||
* Gets a translation of the data.
|
||||
*
|
||||
* The returned translation has to be implement the same typed data interfaces
|
||||
* as this typed data object, excluding the TranslatableInterface. E.g., if
|
||||
* this typed data object implements the ComplexDataInterface and
|
||||
* AccessibleInterface, the translation object has to implement both as well.
|
||||
*
|
||||
* @param $langcode
|
||||
* The language code of the translation to get or LANGUAGE_DEFAULT to get
|
||||
* the data in default language.
|
||||
* @param $strict
|
||||
* (optional) If the data is complex, whether the translation should include
|
||||
* only translatable properties. If set to FALSE, untranslatable properties
|
||||
* are included (in default language) as well as translatable properties in
|
||||
* the specified language. Defaults to TRUE.
|
||||
*
|
||||
* @return \Drupal\Core\TypedData\TypedDataInterface
|
||||
* A typed data object for the translated data.
|
||||
*/
|
||||
public function getTranslation($langcode, $strict = TRUE);
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\TypedData\Type\Binary.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\TypedData\Type;
|
||||
|
||||
use Drupal\Core\TypedData\TypedDataInterface;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* The binary data type.
|
||||
*
|
||||
* The plain value of binary data is a PHP file resource, see
|
||||
* http://php.net/manual/en/language.types.resource.php. For setting the value
|
||||
* a PHP file resource or a (absolute) stream resource URI may be passed.
|
||||
*/
|
||||
class Binary extends TypedData implements TypedDataInterface {
|
||||
|
||||
/**
|
||||
* The file resource URI.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $uri;
|
||||
|
||||
/**
|
||||
* A generic file resource handle.
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
public $handle = NULL;
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::getValue().
|
||||
*/
|
||||
public function getValue() {
|
||||
if (!isset($this->handle) && isset($this->uri)) {
|
||||
$this->handle = fopen($this->uri, 'rb');
|
||||
}
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::setValue().
|
||||
*/
|
||||
public function setValue($value) {
|
||||
if (!isset($value)) {
|
||||
$this->handle = NULL;
|
||||
$this->uri = NULL;
|
||||
}
|
||||
elseif (is_resource($value)) {
|
||||
$this->handle = $value;
|
||||
}
|
||||
elseif (is_string($value)) {
|
||||
// Note: For performance reasons we store the given URI and access the
|
||||
// resource upon request. See Binary::getValue()
|
||||
$this->uri = $value;
|
||||
}
|
||||
else {
|
||||
throw new InvalidArgumentException("Invalid value for binary data given.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::getString().
|
||||
*/
|
||||
public function getString() {
|
||||
$contents = '';
|
||||
while (!feof($this->getValue())) {
|
||||
$contents .= fread($this->handle, 8192);
|
||||
}
|
||||
return $contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::validate().
|
||||
*/
|
||||
public function validate() {
|
||||
// TODO: Implement validate() method.
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\TypedData\Type\Boolean.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\TypedData\Type;
|
||||
|
||||
use Drupal\Core\TypedData\TypedDataInterface;
|
||||
|
||||
/**
|
||||
* The boolean data type.
|
||||
*
|
||||
* The plain value of a boolean is a regular PHP boolean. For setting the value
|
||||
* any PHP variable that casts to a boolean may be passed.
|
||||
*/
|
||||
class Boolean extends TypedData implements TypedDataInterface {
|
||||
|
||||
/**
|
||||
* The data value.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::setValue().
|
||||
*/
|
||||
public function setValue($value) {
|
||||
$this->value = isset($value) ? (bool) $value : $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::validate().
|
||||
*/
|
||||
public function validate() {
|
||||
// TODO: Implement validate() method.
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\TypedData\Type\Date.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\TypedData\Type;
|
||||
|
||||
use Drupal\Core\TypedData\TypedDataInterface;
|
||||
use DateTime;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* The date data type.
|
||||
*
|
||||
* The plain value of a date is an instance of the DateTime class. For setting
|
||||
* the value an instance of the DateTime class, any string supported by
|
||||
* DateTime::__construct(), or a timestamp as integer may be passed.
|
||||
*/
|
||||
class Date extends TypedData implements TypedDataInterface {
|
||||
|
||||
/**
|
||||
* The data value.
|
||||
*
|
||||
* @var DateTime
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::setValue().
|
||||
*/
|
||||
public function setValue($value) {
|
||||
if ($value instanceof DateTime || !isset($value)) {
|
||||
$this->value = $value;
|
||||
}
|
||||
// Treat integer values as timestamps, even if supplied as PHP string.
|
||||
elseif ((string) (int) $value === (string) $value) {
|
||||
$this->value = new DateTime('@' . $value);
|
||||
}
|
||||
elseif (is_string($value)) {
|
||||
$this->value = new DateTime($value);
|
||||
}
|
||||
else {
|
||||
throw new InvalidArgumentException("Invalid date format given.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::getString().
|
||||
*/
|
||||
public function getString() {
|
||||
return (string) $this->getValue()->format(DateTime::ISO8601);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::validate().
|
||||
*/
|
||||
public function validate() {
|
||||
// TODO: Implement validate() method.
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\TypedData\Type\Duration.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\TypedData\Type;
|
||||
|
||||
use Drupal\Core\TypedData\TypedDataInterface;
|
||||
use DateInterval;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* The duration data type.
|
||||
*
|
||||
* The plain value of a duration is an instance of the DateInterval class. For
|
||||
* setting the value an instance of the DateInterval class, a ISO8601 string as
|
||||
* supported by DateInterval::__construct, or an integer in seconds may be
|
||||
* passed.
|
||||
*/
|
||||
class Duration extends TypedData implements TypedDataInterface {
|
||||
|
||||
/**
|
||||
* The data value.
|
||||
*
|
||||
* @var \DateInterval
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::setValue().
|
||||
*/
|
||||
public function setValue($value) {
|
||||
if ($value instanceof DateInterval || !isset($value)) {
|
||||
$this->value = $value;
|
||||
}
|
||||
// Treat integer values as time spans in seconds, even if supplied as PHP
|
||||
// string.
|
||||
elseif ((string) (int) $value === (string) $value) {
|
||||
$this->value = new DateInterval('PT' . $value . 'S');
|
||||
}
|
||||
elseif (is_string($value)) {
|
||||
// @todo: Add support for negative intervals on top of the DateInterval
|
||||
// constructor.
|
||||
$this->value = new DateInterval($value);
|
||||
}
|
||||
else {
|
||||
throw new InvalidArgumentException("Invalid duration format given.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::getString().
|
||||
*/
|
||||
public function getString() {
|
||||
// Generate an ISO 8601 formatted string as supported by
|
||||
// DateInterval::__construct() and setValue().
|
||||
return (string) $this->getValue()->format('%rP%yY%mM%dDT%hH%mM%sS');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::validate().
|
||||
*/
|
||||
public function validate() {
|
||||
// TODO: Implement validate() method.
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\TypedData\Type\Float.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\TypedData\Type;
|
||||
|
||||
use Drupal\Core\TypedData\TypedDataInterface;
|
||||
|
||||
/**
|
||||
* The float data type.
|
||||
*
|
||||
* The plain value of a float is a regular PHP float. For setting the value
|
||||
* any PHP variable that casts to a float may be passed.
|
||||
*/
|
||||
class Float extends TypedData implements TypedDataInterface {
|
||||
|
||||
/**
|
||||
* The data value.
|
||||
*
|
||||
* @var float
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::setValue().
|
||||
*/
|
||||
public function setValue($value) {
|
||||
$this->value = isset($value) ? (float) $value : $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::validate().
|
||||
*/
|
||||
public function validate() {
|
||||
// TODO: Implement validate() method.
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\TypedData\Type\Integer.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\TypedData\Type;
|
||||
|
||||
use Drupal\Core\TypedData\TypedDataInterface;
|
||||
|
||||
/**
|
||||
* The integer data type.
|
||||
*
|
||||
* The plain value of an integer is a regular PHP integer. For setting the value
|
||||
* any PHP variable that casts to an integer may be passed.
|
||||
*/
|
||||
class Integer extends TypedData implements TypedDataInterface {
|
||||
|
||||
/**
|
||||
* The data value.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::setValue().
|
||||
*/
|
||||
public function setValue($value) {
|
||||
$this->value = isset($value) ? (int) $value : $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::validate().
|
||||
*/
|
||||
public function validate() {
|
||||
// TODO: Implement validate() method.
|
||||
}
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\TypedData\Type\Language.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\TypedData\Type;
|
||||
|
||||
use Drupal\Core\TypedData\ContextAwareInterface;
|
||||
use Drupal\Core\TypedData\TypedDataInterface;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Defines the 'language' data type.
|
||||
*
|
||||
* The plain value of a language is the language object, i.e. an instance of
|
||||
* Drupal\Core\Language\Language. For setting the value the language object or
|
||||
* the language code as string may be passed.
|
||||
*
|
||||
* Optionally, this class may be used as computed property, see the supported
|
||||
* settings below. E.g., it is used as 'language' property of language items.
|
||||
*
|
||||
* Supported settings (below the definition's 'settings' key) are:
|
||||
* - langcode source: If used as computed property, the langcode property used
|
||||
* to load the language object.
|
||||
*/
|
||||
class Language extends TypedData implements TypedDataInterface, ContextAwareInterface {
|
||||
|
||||
/**
|
||||
* The name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* The parent data structure.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $parent;
|
||||
|
||||
/**
|
||||
* The language code of the language if no 'langcode source' is used.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $langcode;
|
||||
|
||||
/**
|
||||
* Implements ContextAwareInterface::getName().
|
||||
*/
|
||||
public function getName() {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ContextAwareInterface::setName().
|
||||
*/
|
||||
public function setName($name) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ContextAwareInterface::getParent().
|
||||
*/
|
||||
public function getParent() {
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ContextAwareInterface::setParent().
|
||||
*/
|
||||
public function setParent($parent) {
|
||||
$this->parent = $parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::getValue().
|
||||
*/
|
||||
public function getValue() {
|
||||
$source = $this->getLanguageCodeSource();
|
||||
$langcode = $source ? $source->getValue() : $this->langcode;
|
||||
return $langcode ? language_load($langcode) : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get the typed data object holding the source language code.
|
||||
*
|
||||
* @return \Drupal\Core\TypedData\TypedDataInterface|FALSE
|
||||
*/
|
||||
protected function getLanguageCodeSource() {
|
||||
return !empty($this->definition['settings']['langcode source']) ? $this->parent->get($this->definition['settings']['langcode source']) : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::setValue().
|
||||
*
|
||||
* Both the langcode and the language object may be passed as value.
|
||||
*/
|
||||
public function setValue($value) {
|
||||
// Support passing language objects.
|
||||
if (is_object($value)) {
|
||||
$value = $value->langcode;
|
||||
}
|
||||
elseif (isset($value) && !is_scalar($value)) {
|
||||
throw new InvalidArgumentException('Value is no valid langcode or language object.');
|
||||
}
|
||||
|
||||
$source = $this->getLanguageCodeSource();
|
||||
if ($source) {
|
||||
$source->setValue($value);
|
||||
}
|
||||
else {
|
||||
$this->langcode = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::getString().
|
||||
*/
|
||||
public function getString() {
|
||||
$language = $this->getValue();
|
||||
return $language ? $language->name : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::validate().
|
||||
*/
|
||||
public function validate() {
|
||||
// TODO: Implement validate() method.
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\TypedData\Type\String.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\TypedData\Type;
|
||||
|
||||
use Drupal\Core\TypedData\TypedDataInterface;
|
||||
|
||||
/**
|
||||
* The string data type.
|
||||
*
|
||||
* The plain value of a string is a regular PHP string. For setting the value
|
||||
* any PHP variable that casts to a string may be passed.
|
||||
*/
|
||||
class String extends TypedData implements TypedDataInterface {
|
||||
|
||||
/**
|
||||
* The data value.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::setValue().
|
||||
*/
|
||||
public function setValue($value) {
|
||||
$this->value = isset($value) ? (string) $value : $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::validate().
|
||||
*/
|
||||
public function validate() {
|
||||
// TODO: Implement validate() method.
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\TypedData\Type\TypedData.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\TypedData\Type;
|
||||
|
||||
use Drupal\Core\TypedData\TypedDataInterface;
|
||||
|
||||
/**
|
||||
* The abstract base class for typed data.
|
||||
*
|
||||
* Classes deriving from this base class have to declare $value
|
||||
* or override getValue() or setValue().
|
||||
*/
|
||||
abstract class TypedData implements TypedDataInterface {
|
||||
|
||||
/**
|
||||
* The data definition.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $definition;
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::__construct().
|
||||
*/
|
||||
public function __construct(array $definition) {
|
||||
$this->definition = $definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::getType().
|
||||
*/
|
||||
public function getType() {
|
||||
return $this->definition['type'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::getDefinition().
|
||||
*/
|
||||
public function getDefinition() {
|
||||
return $this->definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::getValue().
|
||||
*/
|
||||
public function getValue() {
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::setValue().
|
||||
*/
|
||||
public function setValue($value) {
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::getString().
|
||||
*/
|
||||
public function getString() {
|
||||
return (string) $this->getValue();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\TypedData\Type\Uri.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\TypedData\Type;
|
||||
|
||||
use Drupal\Core\TypedData\TypedDataInterface;
|
||||
|
||||
/**
|
||||
* The URI data type.
|
||||
*
|
||||
* The plain value of a URI is an absolute URI represented as PHP string.
|
||||
*/
|
||||
class Uri extends TypedData implements TypedDataInterface {
|
||||
|
||||
/**
|
||||
* The data value.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::setValue().
|
||||
*/
|
||||
public function setValue($value) {
|
||||
$this->value = isset($value) ? (string) $value : $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::validate().
|
||||
*/
|
||||
public function validate() {
|
||||
// TODO: Implement validate() method.
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\TypedData\TypedDataFactory.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\TypedData;
|
||||
|
||||
use Drupal\Component\Plugin\Factory\DefaultFactory;
|
||||
use Drupal\Component\Plugin\Exception\PluginException;
|
||||
|
||||
/**
|
||||
* A factory for typed data objects.
|
||||
*
|
||||
* The factory incorporates list classes if the typed data is a list as well as
|
||||
* class overrides that are specified in data definitions.
|
||||
*/
|
||||
class TypedDataFactory extends DefaultFactory {
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\Plugin\Factory\FactoryInterface::createInstance().
|
||||
*
|
||||
* @param string $plugin_id
|
||||
* The id of a plugin, i.e. the data type.
|
||||
* @param array $configuration
|
||||
* The plugin configuration, i.e. the data definition.
|
||||
*
|
||||
* @return Drupal\Core\TypedData\TypedDataInterface
|
||||
*/
|
||||
public function createInstance($plugin_id, array $configuration) {
|
||||
$type_definition = $this->discovery->getDefinition($plugin_id);
|
||||
|
||||
// Allow per-data definition overrides of the used classes and generally
|
||||
// default to the data type definition.
|
||||
$definition = $configuration + $type_definition;
|
||||
|
||||
if (empty($definition['list'])) {
|
||||
if (empty($definition['class'])) {
|
||||
throw new PluginException(sprintf('The plugin (%s) did not specify an instance class.', $plugin_id));
|
||||
}
|
||||
$plugin_class = $definition['class'];
|
||||
}
|
||||
else {
|
||||
if (empty($definition['list class'])) {
|
||||
throw new PluginException(sprintf('The plugin (%s) did not specify a list instance class.', $plugin_id));
|
||||
}
|
||||
$plugin_class = $definition['list class'];
|
||||
}
|
||||
return new $plugin_class($definition, $plugin_id, $this->discovery);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\TypedData\TypedDataInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\TypedData;
|
||||
|
||||
use Drupal\user;
|
||||
|
||||
/**
|
||||
* Interface for typed data objects.
|
||||
*/
|
||||
interface TypedDataInterface {
|
||||
|
||||
/**
|
||||
* Creates a typed data object given its definition.
|
||||
*
|
||||
* @param array $definition
|
||||
* The data definition.
|
||||
*
|
||||
* @see Drupal\Core\TypedData\TypedDataManager::create()
|
||||
*/
|
||||
public function __construct(array $definition);
|
||||
|
||||
/**
|
||||
* Gets the data type.
|
||||
*
|
||||
* @return string
|
||||
* The data type of the wrapped data.
|
||||
*/
|
||||
public function getType();
|
||||
|
||||
/**
|
||||
* Gets the data definition.
|
||||
*
|
||||
* @return array
|
||||
* The data definition array.
|
||||
*/
|
||||
public function getDefinition();
|
||||
|
||||
/**
|
||||
* Gets the data value.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getValue();
|
||||
|
||||
/**
|
||||
* Sets the data value.
|
||||
*
|
||||
* @param mixed $value
|
||||
* The value to set in the format as documented for the data type or NULL to
|
||||
* unset the data value.
|
||||
*
|
||||
* @throws \Drupal\Core\TypedData\ReadOnlyException
|
||||
* If the data is read-only.
|
||||
*/
|
||||
public function setValue($value);
|
||||
|
||||
/**
|
||||
* Returns a string representation of the data.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getString();
|
||||
|
||||
/**
|
||||
* Validates the currently set data value.
|
||||
*/
|
||||
public function validate();
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\TypedData\TypedDataManager.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\TypedData;
|
||||
|
||||
use Drupal\Component\Plugin\PluginManagerBase;
|
||||
use Drupal\Core\Plugin\Discovery\CacheDecorator;
|
||||
use Drupal\Core\Plugin\Discovery\HookDiscovery;
|
||||
|
||||
/**
|
||||
* Manages data type plugins.
|
||||
*/
|
||||
class TypedDataManager extends PluginManagerBase {
|
||||
|
||||
public function __construct() {
|
||||
$this->discovery = new CacheDecorator(new HookDiscovery('data_type_info'), 'typed_data:types');
|
||||
$this->factory = new TypedDataFactory($this->discovery);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\Plugin\PluginManagerInterface::createInstance().
|
||||
*
|
||||
* @param string $plugin_id
|
||||
* The id of a plugin, i.e. the data type.
|
||||
* @param array $configuration
|
||||
* The plugin configuration, i.e. the data definition.
|
||||
*
|
||||
* @return Drupal\Core\TypedData\TypedDataInterface
|
||||
*/
|
||||
public function createInstance($plugin_id, array $configuration) {
|
||||
return $this->factory->createInstance($plugin_id, $configuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new typed data object wrapping the passed value.
|
||||
*
|
||||
* @param array $definition
|
||||
* The data definition array with the following array keys and values:
|
||||
* - type: The data type of the data to wrap. Required.
|
||||
* - label: A human readable label.
|
||||
* - description: A human readable description.
|
||||
* - list: Whether the data is multi-valued, i.e. a list of data items.
|
||||
* Defaults to FALSE.
|
||||
* - computed: A boolean specifying whether the data value is computed by
|
||||
* the object, e.g. depending on some other values.
|
||||
* - read-only: A boolean specifying whether the data is read-only. Defaults
|
||||
* to TRUE for computed properties, to FALSE otherwise.
|
||||
* - class: If set and 'list' is FALSE, the class to use for creating the
|
||||
* typed data object; otherwise the default class of the data type will be
|
||||
* used.
|
||||
* - list class: If set and 'list' is TRUE, the class to use for creating
|
||||
* the typed data object; otherwise the default list class of the data
|
||||
* type will be used.
|
||||
* - settings: An array of settings, as required by the used 'class'. See
|
||||
* the documentation of the class for supported or required settings.
|
||||
* - list settings: An array of settings as required by the used
|
||||
* 'list class'. See the documentation of the list class for support or
|
||||
* required settings.
|
||||
* - constraints: An array of type specific value constraints, e.g. for data
|
||||
* of type 'entity' the 'entity type' and 'bundle' may be specified. See
|
||||
* the documentation of the data type 'class' for supported constraints.
|
||||
* - required: A boolean specifying whether a non-NULL value is mandatory.
|
||||
* Further keys may be supported in certain usages, e.g. for further keys
|
||||
* supported for entity field definitions see
|
||||
* Drupal\Core\Entity\StorageControllerInterface::getPropertyDefinitions().
|
||||
* @param mixed $value
|
||||
* (optional) The data value. If set, it has to match one of the supported
|
||||
* data type format as documented for the data type classes.
|
||||
* @param array $context
|
||||
* (optional) An array describing the context of the data object, e.g. its
|
||||
* name or parent data structure. The context should be passed if a typed
|
||||
* data object is created as part of a data structure. The following keys
|
||||
* are supported:
|
||||
* - name: The name associated with the data.
|
||||
* - parent: The parent object containing the data. Must be an instance of
|
||||
* Drupal\Core\TypedData\ComplexDataInterface or
|
||||
* Drupal\Core\TypedData\ListInterface.
|
||||
*
|
||||
* @return Drupal\Core\TypedData\TypedDataInterface
|
||||
*
|
||||
* @see typed_data()
|
||||
* @see Drupal\Core\TypedData\Type\Integer
|
||||
* @see Drupal\Core\TypedData\Type\Float
|
||||
* @see Drupal\Core\TypedData\Type\String
|
||||
* @see Drupal\Core\TypedData\Type\Boolean
|
||||
* @see Drupal\Core\TypedData\Type\Duration
|
||||
* @see Drupal\Core\TypedData\Type\Date
|
||||
* @see Drupal\Core\TypedData\Type\Uri
|
||||
* @see Drupal\Core\TypedData\Type\Binary
|
||||
* @see Drupal\Core\Entity\Field\EntityWrapper
|
||||
*/
|
||||
function create(array $definition, $value = NULL, array $context = array()) {
|
||||
$wrapper = $this->createInstance($definition['type'], $definition);
|
||||
if (isset($value)) {
|
||||
$wrapper->setValue($value);
|
||||
}
|
||||
if ($wrapper instanceof ContextAwareInterface) {
|
||||
if (isset($context['name'])) {
|
||||
$wrapper->setName($context['name']);
|
||||
}
|
||||
if (isset($context['parent'])) {
|
||||
$wrapper->setParent($context['parent']);
|
||||
}
|
||||
}
|
||||
return $wrapper;
|
||||
}
|
||||
}
|
|
@ -80,7 +80,7 @@ function field_default_insert($entity_type, $entity, $field, $instance, $langcod
|
|||
// assigning it a default value. This way we ensure that only the intended
|
||||
// languages get a default value. Otherwise we could have default values for
|
||||
// not yet open languages.
|
||||
if (empty($entity) || !property_exists($entity, $field['field_name']) ||
|
||||
if (empty($entity) || (!isset($entity->{$field['field_name']}[$langcode]) && !property_exists($entity, $field['field_name'])) ||
|
||||
(isset($entity->{$field['field_name']}[$langcode]) && count($entity->{$field['field_name']}[$langcode]) == 0)) {
|
||||
$items = field_get_default_value($entity_type, $entity, $field, $instance, $langcode);
|
||||
}
|
||||
|
|
|
@ -364,6 +364,65 @@ function field_system_info_alter(&$info, $file, $type) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_data_type_info() to register data types for all field types.
|
||||
*/
|
||||
function field_data_type_info() {
|
||||
$field_types = field_info_field_types();
|
||||
$items = array();
|
||||
|
||||
// Expose data types for all the field type items.
|
||||
// @todo: Make 'field item class' mandatory.
|
||||
foreach ($field_types as $type_name => $type_info) {
|
||||
|
||||
if (!empty($type_info['field item class'])) {
|
||||
$items[$type_name . '_field'] = array(
|
||||
'label' => t('Field !label item', array('!label' => $type_info['label'])),
|
||||
'class' => $type_info['field item class'],
|
||||
'list class' => !empty($type_info['field class']) ? $type_info['field class'] : '\Drupal\Core\Entity\Field\Type\Field',
|
||||
);
|
||||
}
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_field_info() to define all configured fields.
|
||||
*/
|
||||
function field_entity_field_info($entity_type) {
|
||||
$property_info = array();
|
||||
$field_types = field_info_field_types();
|
||||
|
||||
foreach (field_info_instances($entity_type) as $bundle_name => $instances) {
|
||||
$optional = $bundle_name != $entity_type;
|
||||
|
||||
foreach ($instances as $field_name => $instance) {
|
||||
$field = field_info_field($field_name);
|
||||
|
||||
if (!empty($field_types[$field['type']]['field item class'])) {
|
||||
|
||||
// @todo: Allow for adding field type settings.
|
||||
$definition = array(
|
||||
'label' => t('Field !name', array('!name' => $field_name)),
|
||||
'type' => $field['type'] . '_field',
|
||||
'configurable' => TRUE,
|
||||
'translatable' => !empty($field['translatable'])
|
||||
);
|
||||
|
||||
if ($optional) {
|
||||
$property_info['optional'][$field_name] = $definition;
|
||||
$property_info['bundle map'][$bundle_name][] = $field_name;
|
||||
}
|
||||
else {
|
||||
$property_info['definitions'][$field_name] = $definition;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $property_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies language fallback rules to the fields attached to the given entity.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\text\TextProcessed.
|
||||
*/
|
||||
|
||||
namespace Drupal\text;
|
||||
|
||||
use Drupal\Core\TypedData\ContextAwareInterface;
|
||||
use Drupal\Core\TypedData\Type\String;
|
||||
use Drupal\Core\TypedData\ReadOnlyException;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* A computed property for processing text with a format.
|
||||
*
|
||||
* Required settings (below the definition's 'settings' key) are:
|
||||
* - text source: The text property containing the to be processed text.
|
||||
*/
|
||||
class TextProcessed extends String implements ContextAwareInterface {
|
||||
|
||||
/**
|
||||
* The text property.
|
||||
*
|
||||
* @var \Drupal\Core\TypedData\TypedDataInterface
|
||||
*/
|
||||
protected $text;
|
||||
|
||||
/**
|
||||
* The text format property.
|
||||
*
|
||||
* @var \Drupal\Core\TypedData\TypedDataInterface
|
||||
*/
|
||||
protected $format;
|
||||
|
||||
/**
|
||||
* The name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* The parent data structure.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\Field\FieldItemInterface
|
||||
*/
|
||||
protected $parent;
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::__construct().
|
||||
*/
|
||||
public function __construct(array $definition) {
|
||||
$this->definition = $definition;
|
||||
|
||||
if (!isset($definition['settings']['text source'])) {
|
||||
throw new InvalidArgumentException("The definition's 'source' key has to specify the name of the text property to be processed.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ContextAwareInterface::getName().
|
||||
*/
|
||||
public function getName() {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ContextAwareInterface::setName().
|
||||
*/
|
||||
public function setName($name) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ContextAwareInterface::getParent().
|
||||
*
|
||||
* @return \Drupal\Core\Entity\Field\FieldItemInterface
|
||||
*/
|
||||
public function getParent() {
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ContextAwareInterface::setParent().
|
||||
*/
|
||||
public function setParent($parent) {
|
||||
$this->parent = $parent;
|
||||
$this->text = $parent->get($this->definition['settings']['text source']);
|
||||
$this->format = $parent->get('format');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::getValue().
|
||||
*/
|
||||
public function getValue($langcode = NULL) {
|
||||
|
||||
if (!isset($this->text)) {
|
||||
throw new InvalidArgumentException('Computed properties require context for computation.');
|
||||
}
|
||||
|
||||
$field = $this->parent->getParent();
|
||||
$entity = $field->getParent();
|
||||
$instance = field_info_instance($entity->entityType(), $field->getName(), $entity->bundle());
|
||||
|
||||
if (!empty($instance['settings']['text_processing']) && $this->format->value) {
|
||||
return check_markup($this->text->value, $this->format->value, $entity->language()->langcode);
|
||||
}
|
||||
else {
|
||||
// If no format is available, still make sure to sanitize the text.
|
||||
return check_plain($this->text->value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::setValue().
|
||||
*/
|
||||
public function setValue($value) {
|
||||
if (isset($value)) {
|
||||
throw new ReadOnlyException('Unable to set a computed property.');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\text\Type\TextItem.
|
||||
*/
|
||||
|
||||
namespace Drupal\text\Type;
|
||||
|
||||
use Drupal\Core\Entity\Field\FieldItemBase;
|
||||
|
||||
/**
|
||||
* Defines the 'text_item' and 'text_long_item' entity field items.
|
||||
*/
|
||||
class TextItem extends FieldItemBase {
|
||||
|
||||
/**
|
||||
* Field definitions of the contained properties.
|
||||
*
|
||||
* @see self::getPropertyDefinitions()
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
static $propertyDefinitions;
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::getPropertyDefinitions().
|
||||
*/
|
||||
public function getPropertyDefinitions() {
|
||||
|
||||
if (!isset(self::$propertyDefinitions)) {
|
||||
self::$propertyDefinitions['value'] = array(
|
||||
'type' => 'string',
|
||||
'label' => t('Text value'),
|
||||
);
|
||||
self::$propertyDefinitions['format'] = array(
|
||||
'type' => 'string',
|
||||
'label' => t('Text format'),
|
||||
);
|
||||
self::$propertyDefinitions['processed'] = array(
|
||||
'type' => 'string',
|
||||
'label' => t('Processed text'),
|
||||
'description' => t('The text value with the text format applied.'),
|
||||
'computed' => TRUE,
|
||||
'class' => '\Drupal\text\TextProcessed',
|
||||
'settings' => array(
|
||||
'text source' => 'value',
|
||||
),
|
||||
);
|
||||
}
|
||||
return self::$propertyDefinitions;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\text\Type\TextSummaryItem.
|
||||
*/
|
||||
|
||||
namespace Drupal\text\Type;
|
||||
|
||||
/**
|
||||
* Defines the 'text_with_summary' entity field item.
|
||||
*/
|
||||
class TextSummaryItem extends TextItem {
|
||||
|
||||
/**
|
||||
* Field definitions of the contained properties.
|
||||
*
|
||||
* @see self::getPropertyDefinitions()
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
static $propertyDefinitions;
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::getPropertyDefinitions().
|
||||
*/
|
||||
public function getPropertyDefinitions() {
|
||||
|
||||
if (!isset(self::$propertyDefinitions)) {
|
||||
|
||||
self::$propertyDefinitions = parent::getPropertyDefinitions();
|
||||
|
||||
self::$propertyDefinitions['summary'] = array(
|
||||
'type' => 'string',
|
||||
'label' => t('Summary text value'),
|
||||
);
|
||||
self::$propertyDefinitions['summary_processed'] = array(
|
||||
'type' => 'string',
|
||||
'label' => t('Processed summary text'),
|
||||
'description' => t('The summary text value with the text format applied.'),
|
||||
'computed' => TRUE,
|
||||
'class' => '\Drupal\text\TextProcessed',
|
||||
'settings' => array(
|
||||
'text source' => 'summary',
|
||||
),
|
||||
);
|
||||
}
|
||||
return self::$propertyDefinitions;
|
||||
}
|
||||
}
|
|
@ -38,6 +38,7 @@ function text_field_info() {
|
|||
'instance_settings' => array('text_processing' => 0),
|
||||
'default_widget' => 'text_textfield',
|
||||
'default_formatter' => 'text_default',
|
||||
'field item class' => '\Drupal\text\Type\TextItem',
|
||||
),
|
||||
'text_long' => array(
|
||||
'label' => t('Long text'),
|
||||
|
@ -45,6 +46,7 @@ function text_field_info() {
|
|||
'instance_settings' => array('text_processing' => 0),
|
||||
'default_widget' => 'text_textarea',
|
||||
'default_formatter' => 'text_default',
|
||||
'field item class' => '\Drupal\text\Type\TextItem',
|
||||
),
|
||||
'text_with_summary' => array(
|
||||
'label' => t('Long text and summary'),
|
||||
|
@ -52,6 +54,7 @@ function text_field_info() {
|
|||
'instance_settings' => array('text_processing' => 1, 'display_summary' => 0),
|
||||
'default_widget' => 'text_textarea_with_summary',
|
||||
'default_formatter' => 'text_default',
|
||||
'field item class' => '\Drupal\text\Type\TextSummaryItem',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2032,6 +2032,25 @@ abstract class WebTestBase extends TestBase {
|
|||
$this->drupalSettings = $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a typed data object and executes some basic assertions.
|
||||
*
|
||||
* @see Drupal\Core\TypedData\TypedDataManager::create().
|
||||
*/
|
||||
protected function createTypedData($definition, $value = NULL, $context = array()) {
|
||||
// Save the type that was passed in so we can compare with it later.
|
||||
$type = $definition['type'];
|
||||
// Construct the object.
|
||||
$data = typed_data()->create($definition, $value, $context);
|
||||
// Assert the definition of the wrapper.
|
||||
$this->assertTrue($data instanceof \Drupal\Core\TypedData\TypedDataInterface, 'Typed data object is an instance of the typed data interface.');
|
||||
$definition = $data->getDefinition();
|
||||
$this->assertTrue(!empty($definition['label']), $definition['label'] . ' data definition was returned.');
|
||||
// Assert that the correct type was constructed.
|
||||
$this->assertEqual($data->getType(), $type, $definition['label'] . ' object returned type.');
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass if the internal browser's URL matches the given path.
|
||||
*
|
||||
|
|
|
@ -36,21 +36,20 @@ class EntityApiTest extends WebTestBase {
|
|||
$user1 = $this->drupalCreateUser();
|
||||
|
||||
// Create some test entities.
|
||||
$entity = entity_create('entity_test', array('name' => 'test', 'uid' => $user1->uid));
|
||||
$entity = entity_create('entity_test', array('name' => 'test', 'user_id' => $user1->uid));
|
||||
$entity->save();
|
||||
$entity = entity_create('entity_test', array('name' => 'test2', 'uid' => $user1->uid));
|
||||
$entity = entity_create('entity_test', array('name' => 'test2', 'user_id' => $user1->uid));
|
||||
$entity->save();
|
||||
$entity = entity_create('entity_test', array('name' => 'test', 'uid' => NULL));
|
||||
$entity = entity_create('entity_test', array('name' => 'test', 'user_id' => NULL));
|
||||
$entity->save();
|
||||
|
||||
$entities = array_values(entity_load_multiple_by_properties('entity_test', array('name' => 'test')));
|
||||
|
||||
$this->assertEqual($entities[0]->get('name'), 'test', 'Created and loaded entity.');
|
||||
$this->assertEqual($entities[1]->get('name'), 'test', 'Created and loaded entity.');
|
||||
$this->assertEqual($entities[0]->name->value, 'test', 'Created and loaded entity.');
|
||||
$this->assertEqual($entities[1]->name->value, 'test', 'Created and loaded entity.');
|
||||
|
||||
// Test loading a single entity.
|
||||
$loaded_entity = entity_test_load($entity->id);
|
||||
$this->assertEqual($loaded_entity->id, $entity->id, 'Loaded a single entity by id.');
|
||||
$loaded_entity = entity_test_load($entity->id());
|
||||
$this->assertEqual($loaded_entity->id(), $entity->id(), 'Loaded a single entity by id.');
|
||||
|
||||
// Test deleting an entity.
|
||||
$entities = array_values(entity_load_multiple_by_properties('entity_test', array('name' => 'test2')));
|
||||
|
@ -60,10 +59,10 @@ class EntityApiTest extends WebTestBase {
|
|||
|
||||
// Test updating an entity.
|
||||
$entities = array_values(entity_load_multiple_by_properties('entity_test', array('name' => 'test')));
|
||||
$entities[0]->set('name', 'test3');
|
||||
$entities[0]->name->value = 'test3';
|
||||
$entities[0]->save();
|
||||
$entity = entity_test_load($entities[0]->id);
|
||||
$this->assertEqual($entity->get('name'), 'test3', 'Entity updated.');
|
||||
$entity = entity_test_load($entities[0]->id());
|
||||
$this->assertEqual($entity->name->value, 'test3', 'Entity updated.');
|
||||
|
||||
// Try deleting multiple test entities by deleting all.
|
||||
$ids = array_keys(entity_test_load_multiple());
|
||||
|
@ -72,26 +71,4 @@ class EntityApiTest extends WebTestBase {
|
|||
$all = entity_test_load_multiple();
|
||||
$this->assertTrue(empty($all), 'Deleted all entities.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Entity getters/setters.
|
||||
*/
|
||||
function testEntityGettersSetters() {
|
||||
$entity = entity_create('entity_test', array('name' => 'test', 'uid' => NULL));
|
||||
$this->assertNull($entity->get('uid'), 'Property is not set.');
|
||||
|
||||
$entity->set('uid', $GLOBALS['user']->uid);
|
||||
$this->assertEqual($entity->get('uid'), $GLOBALS['user']->uid, 'Property has been set.');
|
||||
|
||||
$value = $entity->get('uid');
|
||||
$this->assertEqual($value, $entity->get('uid'), 'Property has been retrieved.');
|
||||
|
||||
// Make sure setting/getting translations boils down to setting/getting the
|
||||
// regular value as the entity and property are not translatable.
|
||||
$entity->set('uid', NULL, 'en');
|
||||
$this->assertNull($entity->uid, 'Language neutral property has been set.');
|
||||
|
||||
$value = $entity->get('uid', 'en');
|
||||
$this->assertNull($value, 'Language neutral property has been retrieved.');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,395 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\Entity\Tests\EntityFieldTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Entity;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\Field\FieldInterface;
|
||||
use Drupal\Core\Entity\Field\FieldItemInterface;
|
||||
use Drupal\Core\TypedData\TypedDataInterface;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Tests Entity API base functionality.
|
||||
*/
|
||||
class EntityFieldTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('entity_test');
|
||||
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Entity Field API',
|
||||
'description' => 'Tests the Entity Field API',
|
||||
'group' => 'Entity API',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a test entity.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface
|
||||
*/
|
||||
protected function createTestEntity() {
|
||||
$this->entity_name = $this->randomName();
|
||||
$this->entity_user = $this->drupalCreateUser();
|
||||
$this->entity_field_text = $this->randomName();
|
||||
|
||||
// Pass in the value of the name field when creating. With the user
|
||||
// field we test setting a field after creation.
|
||||
$entity = entity_create('entity_test', array());
|
||||
$entity->user_id->value = $this->entity_user->uid;
|
||||
$entity->name->value = $this->entity_name;
|
||||
|
||||
// Set a value for the test field.
|
||||
$entity->field_test_text->value = $this->entity_field_text;
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests reading and writing properties and field items.
|
||||
*/
|
||||
public function testReadWrite() {
|
||||
$entity = $this->createTestEntity();
|
||||
|
||||
// Access the name field.
|
||||
$this->assertTrue($entity->name instanceof FieldInterface, 'Field implements interface');
|
||||
$this->assertTrue($entity->name[0] instanceof FieldItemInterface, 'Field item implements interface');
|
||||
|
||||
$this->assertEqual($this->entity_name, $entity->name->value, 'Name value can be read.');
|
||||
$this->assertEqual($this->entity_name, $entity->name[0]->value, 'Name value can be read through list access.');
|
||||
$this->assertEqual($entity->name->getValue(), array(0 => array('value' => $this->entity_name)), 'Plain field value returned.');
|
||||
|
||||
// Change the name.
|
||||
$new_name = $this->randomName();
|
||||
$entity->name->value = $new_name;
|
||||
$this->assertEqual($new_name, $entity->name->value, 'Name can be updated and read.');
|
||||
$this->assertEqual($entity->name->getValue(), array(0 => array('value' => $new_name)), 'Plain field value reflects the update.');
|
||||
|
||||
$new_name = $this->randomName();
|
||||
$entity->name[0]->value = $new_name;
|
||||
$this->assertEqual($new_name, $entity->name->value, 'Name can be updated and read through list access.');
|
||||
|
||||
// Access the user field.
|
||||
$this->assertTrue($entity->user_id instanceof FieldInterface, 'Field implements interface');
|
||||
$this->assertTrue($entity->user_id[0] instanceof FieldItemInterface, 'Field item implements interface');
|
||||
|
||||
$this->assertEqual($this->entity_user->uid, $entity->user_id->value, 'User id can be read.');
|
||||
$this->assertEqual($this->entity_user->name, $entity->user_id->entity->name, 'User name can be read.');
|
||||
|
||||
// Change the assigned user by entity.
|
||||
$new_user = $this->drupalCreateUser();
|
||||
$entity->user_id->entity = $new_user;
|
||||
$this->assertEqual($new_user->uid, $entity->user_id->value, 'Updated user id can be read.');
|
||||
$this->assertEqual($new_user->name, $entity->user_id->entity->name, 'Updated user name value can be read.');
|
||||
|
||||
// Change the assigned user by id.
|
||||
$new_user = $this->drupalCreateUser();
|
||||
$entity->user_id->value = $new_user->uid;
|
||||
$this->assertEqual($new_user->uid, $entity->user_id->value, 'Updated user id can be read.');
|
||||
$this->assertEqual($new_user->name, $entity->user_id->entity->name, 'Updated user name value can be read.');
|
||||
|
||||
// Try unsetting a field.
|
||||
$entity->name->value = NULL;
|
||||
$entity->user_id->value = NULL;
|
||||
$this->assertNull($entity->name->value, 'Name field is not set.');
|
||||
$this->assertNull($entity->user_id->value, 'User ID field is not set.');
|
||||
$this->assertNull($entity->user_id->entity, 'User entity field is not set.');
|
||||
|
||||
// Test using isset(), empty() and unset().
|
||||
$entity->name->value = 'test unset';
|
||||
unset($entity->name->value);
|
||||
$this->assertFalse(isset($entity->name->value), 'Name is not set.');
|
||||
$this->assertFalse(isset($entity->name[0]->value), 'Name is not set.');
|
||||
$this->assertTrue(empty($entity->name->value), 'Name is empty.');
|
||||
$this->assertTrue(empty($entity->name[0]->value), 'Name is empty.');
|
||||
|
||||
$entity->name->value = 'a value';
|
||||
$this->assertTrue(isset($entity->name->value), 'Name is set.');
|
||||
$this->assertTrue(isset($entity->name[0]->value), 'Name is set.');
|
||||
$this->assertFalse(empty($entity->name->value), 'Name is not empty.');
|
||||
$this->assertFalse(empty($entity->name[0]->value), 'Name is not empty.');
|
||||
$this->assertTrue(isset($entity->name[0]), 'Name string item is set.');
|
||||
$this->assertFalse(isset($entity->name[1]), 'Second name string item is not set as it does not exist');
|
||||
$this->assertTrue(isset($entity->name), 'Name field is set.');
|
||||
$this->assertFalse(isset($entity->nameInvalid), 'Not existing field is not set.');
|
||||
|
||||
unset($entity->name[0]);
|
||||
$this->assertFalse(isset($entity->name[0]), 'Name field item is not set.');
|
||||
$this->assertFalse(isset($entity->name[0]->value), 'Name is not set.');
|
||||
$this->assertFalse(isset($entity->name->value), 'Name is not set.');
|
||||
|
||||
$entity->name->value = 'a value';
|
||||
$this->assertTrue(isset($entity->name->value), 'Name is set.');
|
||||
unset($entity->name);
|
||||
$this->assertFalse(isset($entity->name), 'Name field is not set.');
|
||||
$this->assertFalse(isset($entity->name[0]), 'Name field item is not set.');
|
||||
$this->assertFalse(isset($entity->name[0]->value), 'Name is not set.');
|
||||
$this->assertFalse(isset($entity->name->value), 'Name is not set.');
|
||||
|
||||
// Access the language field.
|
||||
$this->assertEqual(LANGUAGE_NOT_SPECIFIED, $entity->langcode->value, 'Language code can be read.');
|
||||
$this->assertEqual(language_load(LANGUAGE_NOT_SPECIFIED), $entity->langcode->language, 'Language object can be read.');
|
||||
|
||||
// Change the language by code.
|
||||
$entity->langcode->value = language_default()->langcode;
|
||||
$this->assertEqual(language_default()->langcode, $entity->langcode->value, 'Language code can be read.');
|
||||
$this->assertEqual(language_default(), $entity->langcode->language, 'Language object can be read.');
|
||||
|
||||
// Revert language by code then try setting it by language object.
|
||||
$entity->langcode->value = LANGUAGE_NOT_SPECIFIED;
|
||||
$entity->langcode->language = language_default();
|
||||
$this->assertEqual(language_default()->langcode, $entity->langcode->value, 'Language code can be read.');
|
||||
$this->assertEqual(language_default(), $entity->langcode->language, 'Language object can be read.');
|
||||
|
||||
// Access the text field and test updating.
|
||||
$this->assertEqual($entity->field_test_text->value, $this->entity_field_text, 'Text field can be read.');
|
||||
$new_text = $this->randomName();
|
||||
$entity->field_test_text->value = $new_text;
|
||||
$this->assertEqual($entity->field_test_text->value, $new_text, 'Updated text field can be read.');
|
||||
|
||||
// Test creating the entity by passing in plain values.
|
||||
$this->entity_name = $this->randomName();
|
||||
$name_item[0]['value'] = $this->entity_name;
|
||||
$this->entity_user = $this->drupalCreateUser();
|
||||
$user_item[0]['value'] = $this->entity_user->uid;
|
||||
$this->entity_field_text = $this->randomName();
|
||||
$text_item[0]['value'] = $this->entity_field_text;
|
||||
|
||||
$entity = entity_create('entity_test', array(
|
||||
'name' => $name_item,
|
||||
'user_id' => $user_item,
|
||||
'field_test_text' => $text_item,
|
||||
));
|
||||
$this->assertEqual($this->entity_name, $entity->name->value, 'Name value can be read.');
|
||||
$this->assertEqual($this->entity_user->uid, $entity->user_id->value, 'User id can be read.');
|
||||
$this->assertEqual($this->entity_user->name, $entity->user_id->entity->name, 'User name can be read.');
|
||||
$this->assertEqual($this->entity_field_text, $entity->field_test_text->value, 'Text field can be read.');
|
||||
|
||||
// Test copying field values.
|
||||
$entity2 = $this->createTestEntity();
|
||||
$entity2->name = $entity->name;
|
||||
$entity2->user_id = $entity->user_id;
|
||||
$entity2->field_test_text = $entity->field_test_text;
|
||||
|
||||
$this->assertTrue($entity->name !== $entity2->name, 'Copying properties results in a different field object.');
|
||||
$this->assertEqual($entity->name->value, $entity2->name->value, 'Name field copied.');
|
||||
$this->assertEqual($entity->user_id->value, $entity2->user_id->value, 'User id field copied.');
|
||||
$this->assertEqual($entity->field_test_text->value, $entity2->field_test_text->value, 'Text field copied.');
|
||||
|
||||
// Tests adding a value to a field item list.
|
||||
$entity->name[] = 'Another name';
|
||||
$this->assertEqual($entity->name[1]->value == 'Another name', 'List item added via [].');
|
||||
$entity->name[2]->value = 'Third name';
|
||||
$this->assertEqual($entity->name[2]->value == 'Third name', 'List item added by a accessing not yet created item.');
|
||||
|
||||
// Test removing and empty-ing list items.
|
||||
$this->assertEqual(count($entity->name), 3, 'List has 3 items.');
|
||||
unset($entity->name[1]);
|
||||
$this->assertEqual(count($entity->name), 2, 'Second list item has been removed.');
|
||||
$entity->name[2] = NULL;
|
||||
$this->assertEqual(count($entity->name), 2, 'Assigning NULL does not reduce array count.');
|
||||
$this->assertTrue($entity->name[2]->isEmpty(), 'Assigning NULL empties the item.');
|
||||
|
||||
// Test using isEmpty().
|
||||
unset($entity->name[2]);
|
||||
$this->assertFalse($entity->name[0]->isEmpty(), 'Name item is not empty.');
|
||||
$entity->name->value = NULL;
|
||||
$this->assertTrue($entity->name[0]->isEmpty(), 'Name item is empty.');
|
||||
$this->assertTrue($entity->name->isEmpty(), 'Name field is empty.');
|
||||
$this->assertEqual(count($entity->name), 1, 'Empty item is considered when counting.');
|
||||
$this->assertEqual(count(iterator_to_array($entity->name->getIterator())), count($entity->name), 'Count matches iterator count.');
|
||||
$this->assertTrue($entity->name->getValue() === array(0 => NULL), 'Name field value contains a NULL value.');
|
||||
|
||||
// Test get and set field values.
|
||||
$entity->name = 'foo';
|
||||
$this->assertEqual($entity->name[0]->getPropertyValues(), array('value' => 'foo'), 'Field value has been retrieved via getPropertyValue()');
|
||||
$entity->name[0]->setPropertyValues(array('value' => 'bar'));
|
||||
$this->assertEqual($entity->name->value, 'bar', 'Field value has been set via setPropertyValue()');
|
||||
|
||||
$values = $entity->getPropertyValues();
|
||||
$this->assertEqual($values['name'], array(0 => array('value' => 'bar')), 'Field value has been retrieved via getPropertyValue() from an entity.');
|
||||
$entity->setPropertyValues(array('name' => 'foo'));
|
||||
$this->assertEqual($entity->name->value, 'foo', 'Field value has been set via setPropertyValue() on an entity.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to save and load an entity again.
|
||||
*/
|
||||
public function testSave() {
|
||||
$entity = $this->createTestEntity();
|
||||
$entity->save();
|
||||
$this->assertTrue((bool) $entity->id(), 'Entity has received an id.');
|
||||
|
||||
$entity = entity_load('entity_test', $entity->id());
|
||||
$this->assertTrue((bool) $entity->id(), 'Entity loaded.');
|
||||
|
||||
// Access the name field.
|
||||
$this->assertEqual(1, $entity->id->value, 'ID value can be read.');
|
||||
$this->assertTrue(is_string($entity->uuid->value), 'UUID value can be read.');
|
||||
$this->assertEqual(LANGUAGE_NOT_SPECIFIED, $entity->langcode->value, 'Language code can be read.');
|
||||
$this->assertEqual(language_load(LANGUAGE_NOT_SPECIFIED), $entity->langcode->language, 'Language object can be read.');
|
||||
$this->assertEqual($this->entity_user->uid, $entity->user_id->value, 'User id can be read.');
|
||||
$this->assertEqual($this->entity_user->name, $entity->user_id->entity->name, 'User name can be read.');
|
||||
$this->assertEqual($this->entity_field_text, $entity->field_test_text->value, 'Text field can be read.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests introspection and getting metadata upfront.
|
||||
*/
|
||||
public function testIntrospection() {
|
||||
// Test getting metadata upfront, i.e. without having an entity object.
|
||||
$definition = array(
|
||||
'type' => 'entity',
|
||||
'constraints' => array(
|
||||
'entity type' => 'entity_test',
|
||||
),
|
||||
'label' => t('Test entity'),
|
||||
);
|
||||
$wrapped_entity = typed_data()->create($definition);
|
||||
$definitions = $wrapped_entity->getPropertyDefinitions($definition);
|
||||
$this->assertEqual($definitions['name']['type'], 'string_field', 'Name field found.');
|
||||
$this->assertEqual($definitions['user_id']['type'], 'entityreference_field', 'User field found.');
|
||||
$this->assertEqual($definitions['field_test_text']['type'], 'text_field', 'Test-text-field field found.');
|
||||
|
||||
// Test introspecting an entity object.
|
||||
// @todo: Add bundles and test bundles as well.
|
||||
$entity = entity_create('entity_test', array());
|
||||
|
||||
$definitions = $entity->getPropertyDefinitions();
|
||||
$this->assertEqual($definitions['name']['type'], 'string_field', 'Name field found.');
|
||||
$this->assertEqual($definitions['user_id']['type'], 'entityreference_field', 'User field found.');
|
||||
$this->assertEqual($definitions['field_test_text']['type'], 'text_field', 'Test-text-field field found.');
|
||||
|
||||
$name_properties = $entity->name->getPropertyDefinitions();
|
||||
$this->assertEqual($name_properties['value']['type'], 'string', 'String value property of the name found.');
|
||||
|
||||
$userref_properties = $entity->user_id->getPropertyDefinitions();
|
||||
$this->assertEqual($userref_properties['value']['type'], 'integer', 'Entity id property of the user found.');
|
||||
$this->assertEqual($userref_properties['entity']['type'], 'entity', 'Entity reference property of the user found.');
|
||||
|
||||
$textfield_properties = $entity->field_test_text->getPropertyDefinitions();
|
||||
$this->assertEqual($textfield_properties['value']['type'], 'string', 'String value property of the test-text field found.');
|
||||
$this->assertEqual($textfield_properties['format']['type'], 'string', 'String format field of the test-text field found.');
|
||||
$this->assertEqual($textfield_properties['processed']['type'], 'string', 'String processed property of the test-text field found.');
|
||||
|
||||
// @todo: Once the user entity has definitions, continue testing getting
|
||||
// them from the $userref_values['entity'] property.
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests iterating over properties.
|
||||
*/
|
||||
public function testIterator() {
|
||||
$entity = $this->createTestEntity();
|
||||
|
||||
foreach ($entity as $name => $field) {
|
||||
$this->assertTrue($field instanceof FieldInterface, "Field $name implements interface.");
|
||||
|
||||
foreach ($field as $delta => $item) {
|
||||
$this->assertTrue($field[0] instanceof FieldItemInterface, "Item $delta of field $name implements interface.");
|
||||
|
||||
foreach ($item as $value_name => $value_property) {
|
||||
$this->assertTrue($value_property instanceof TypedDataInterface, "Value $value_name of item $delta of field $name implements interface.");
|
||||
|
||||
$value = $value_property->getValue();
|
||||
$this->assertTrue(!isset($value) || is_scalar($value) || $value instanceof EntityInterface, "Value $value_name of item $delta of field $name is a primitive or an entity.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$properties = $entity->getProperties();
|
||||
$this->assertEqual(array_keys($properties), array_keys($entity->getPropertyDefinitions()), 'All properties returned.');
|
||||
$this->assertEqual($properties, iterator_to_array($entity->getIterator()), 'Entity iterator iterates over all properties.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests working with entity properties based upon data structure and data
|
||||
* list interfaces.
|
||||
*/
|
||||
public function testDataStructureInterfaces() {
|
||||
$entity = $this->createTestEntity();
|
||||
$entity->save();
|
||||
$entity_definition = array(
|
||||
'type' => 'entity',
|
||||
'constraints' => array(
|
||||
'entity type' => 'entity_test',
|
||||
),
|
||||
'label' => t('Test entity'),
|
||||
);
|
||||
$wrapped_entity = typed_data()->create($entity_definition, $entity);
|
||||
|
||||
// For the test we navigate through the tree of contained properties and get
|
||||
// all contained strings, limited by a certain depth.
|
||||
$strings = array();
|
||||
$this->getContainedStrings($wrapped_entity, 0, $strings);
|
||||
|
||||
// @todo: Once the user entity has defined properties this should contain
|
||||
// the user name and other user entity strings as well.
|
||||
$target_strings = array(
|
||||
$entity->uuid->value,
|
||||
LANGUAGE_NOT_SPECIFIED,
|
||||
$this->entity_name,
|
||||
$this->entity_field_text,
|
||||
// Field format.
|
||||
NULL,
|
||||
);
|
||||
$this->assertEqual($strings, $target_strings, 'All contained strings found.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive helper for getting all contained strings,
|
||||
* i.e. properties of type string.
|
||||
*/
|
||||
public function getContainedStrings(TypedDataInterface $wrapper, $depth, array &$strings) {
|
||||
|
||||
if ($wrapper->getType() == 'string') {
|
||||
$strings[] = $wrapper->getValue();
|
||||
}
|
||||
|
||||
// Recurse until a certain depth is reached if possible.
|
||||
if ($depth < 7) {
|
||||
if ($wrapper instanceof \Drupal\Core\TypedData\ListInterface) {
|
||||
foreach ($wrapper as $item) {
|
||||
$this->getContainedStrings($item, $depth + 1, $strings);
|
||||
}
|
||||
}
|
||||
elseif ($wrapper instanceof \Drupal\Core\TypedData\ComplexDataInterface) {
|
||||
foreach ($wrapper as $name => $property) {
|
||||
$this->getContainedStrings($property, $depth + 1, $strings);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests getting processed property values via a computed property.
|
||||
*/
|
||||
public function testComputedProperties() {
|
||||
// Make the test text field processed.
|
||||
$instance = field_info_instance('entity_test', 'field_test_text', 'entity_test');
|
||||
$instance['settings']['text_processing'] = 1;
|
||||
field_update_instance($instance);
|
||||
|
||||
$entity = $this->createTestEntity();
|
||||
$entity->field_test_text->value = "The <strong>text</strong> text to filter.";
|
||||
$entity->field_test_text->format = filter_default_format();
|
||||
|
||||
$target = "<p>The <strong>text</strong> text to filter.</p>\n";
|
||||
$this->assertEqual($entity->field_test_text->processed, $target, 'Text is processed with the default filter.');
|
||||
|
||||
// Save and load entity and make sure it still works.
|
||||
$entity->save();
|
||||
$entity = entity_load('entity_test', $entity->id());
|
||||
$this->assertEqual($entity->field_test_text->processed, $target, 'Text is processed with the default filter.');
|
||||
}
|
||||
}
|
|
@ -45,7 +45,7 @@ class EntityFormTest extends WebTestBase {
|
|||
|
||||
$edit = array(
|
||||
'name' => $name1,
|
||||
'uid' => mt_rand(0, 128),
|
||||
'user_id' => mt_rand(0, 128),
|
||||
"field_test_text[$langcode][0][value]" => $this->randomName(16),
|
||||
);
|
||||
|
||||
|
@ -59,7 +59,7 @@ class EntityFormTest extends WebTestBase {
|
|||
$this->assertFalse($entity, 'The entity has been modified.');
|
||||
$entity = $this->loadEntityByName($name2);
|
||||
$this->assertTrue($entity, 'Modified entity found in the database.');
|
||||
$this->assertNotEqual($entity->get('name'), $name1, 'The entity name has been modified.');
|
||||
$this->assertNotEqual($entity->name->value, $name1, 'The entity name has been modified.');
|
||||
|
||||
$this->drupalPost('entity-test/manage/' . $entity->id() . '/edit', array(), t('Delete'));
|
||||
$entity = $this->loadEntityByName($name2);
|
||||
|
|
|
@ -113,7 +113,7 @@ class EntityTranslationFormTest extends WebTestBase {
|
|||
|
||||
// Create a body translation and check the form language.
|
||||
$langcode2 = $this->langcodes[1];
|
||||
$node->set('body', array(array('value' => $this->randomName(16))), $langcode2);
|
||||
$node->body[$langcode2][0]['value'] = $this->randomName(16);
|
||||
$node->save();
|
||||
$this->drupalGet($langcode2 . '/node/' . $node->nid . '/edit');
|
||||
$form_langcode = variable_get('entity_form_langcode', FALSE);
|
||||
|
|
|
@ -78,76 +78,99 @@ class EntityTranslationTest extends WebTestBase {
|
|||
function testEntityLanguageMethods() {
|
||||
$entity = entity_create('entity_test', array(
|
||||
'name' => 'test',
|
||||
'uid' => $GLOBALS['user']->uid,
|
||||
'user_id' => $GLOBALS['user']->uid,
|
||||
));
|
||||
$this->assertEqual($entity->language()->langcode, LANGUAGE_NOT_SPECIFIED, 'Entity language not specified.');
|
||||
$this->assertFalse($entity->translations(), 'No translations are available');
|
||||
$this->assertFalse($entity->getTranslationLanguages(FALSE), 'No translations are available');
|
||||
|
||||
// Set the value in default language.
|
||||
$entity->set($this->field_name, array(0 => array('value' => 'default value')));
|
||||
// Get the value.
|
||||
$value = $entity->get($this->field_name);
|
||||
$this->assertEqual($value, array(0 => array('value' => 'default value')), 'Untranslated value retrieved.');
|
||||
|
||||
$message = "An exception is thrown when trying to set a field with an invalid language";
|
||||
$this->assertEqual($entity->getTranslation(LANGUAGE_DEFAULT)->get($this->field_name)->value, 'default value', 'Untranslated value retrieved.');
|
||||
|
||||
// Set the value in a certain language. As the entity is not
|
||||
// language-specific it will throw an exception.
|
||||
try {
|
||||
$entity->set($this->field_name, array(0 => array('value' => 'default value2')), $this->langcodes[1]);
|
||||
$this->fail($message);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$this->assertTrue($e instanceof InvalidArgumentException, $message);
|
||||
}
|
||||
// language-specific it should use the default language and so ignore the
|
||||
// specified language.
|
||||
$entity->getTranslation($this->langcodes[1])->set($this->field_name, array(0 => array('value' => 'default value2')));
|
||||
$this->assertEqual($entity->get($this->field_name)->value, 'default value2', 'Untranslated value updated.');
|
||||
$this->assertFalse($entity->getTranslationLanguages(FALSE), 'No translations are available');
|
||||
|
||||
// Test getting a field value using a specific language for a not
|
||||
// language-specific entity.
|
||||
$value = $entity->get($this->field_name, $this->langcodes[1]);
|
||||
$this->assertNull($value, 'Returned NULL for getter with invalid language.');
|
||||
$this->assertEqual($entity->getTranslation($this->langcodes[1])->get($this->field_name)->value, 'default value2', 'Untranslated value retrieved.');
|
||||
|
||||
// Now, make the entity language-specific by assigning a language and test
|
||||
// translating it.
|
||||
$entity->setLangcode($this->langcodes[0]);
|
||||
$entity->langcode->value = $this->langcodes[0];
|
||||
$entity->{$this->field_name} = array();
|
||||
$this->assertEqual($entity->language(), language_load($this->langcodes[0]), 'Entity language retrieved.');
|
||||
$this->assertFalse($entity->translations(), 'No translations are available');
|
||||
$this->assertFalse($entity->getTranslationLanguages(FALSE), 'No translations are available');
|
||||
|
||||
// Set the value in default language.
|
||||
$entity->set($this->field_name, array(0 => array('value' => 'default value')));
|
||||
// Get the value.
|
||||
$value = $entity->get($this->field_name);
|
||||
$this->assertEqual($value, array(0 => array('value' => 'default value')), 'Untranslated value retrieved.');
|
||||
$this->assertEqual($entity->get($this->field_name)->value, 'default value', 'Untranslated value retrieved.');
|
||||
|
||||
// Set a translation.
|
||||
$entity->set($this->field_name, array(0 => array('value' => 'translation 1')), $this->langcodes[1]);
|
||||
$value = $entity->get($this->field_name, $this->langcodes[1]);
|
||||
$this->assertEqual($value, array(0 => array('value' => 'translation 1')), 'Translated value set.');
|
||||
$entity->getTranslation($this->langcodes[1])->set($this->field_name, array(0 => array('value' => 'translation 1')));
|
||||
$this->assertEqual($entity->getTranslation($this->langcodes[1])->{$this->field_name}->value, 'translation 1', 'Translated value set.');
|
||||
|
||||
// Make sure the untranslated value stays.
|
||||
$value = $entity->get($this->field_name);
|
||||
$this->assertEqual($value, array(0 => array('value' => 'default value')), 'Untranslated value stays.');
|
||||
$this->assertEqual($entity->get($this->field_name)->value, 'default value', 'Untranslated value stays.');
|
||||
|
||||
$translations[$this->langcodes[1]] = language_load($this->langcodes[1]);
|
||||
$this->assertEqual($entity->translations(), $translations, 'Translations retrieved.');
|
||||
$this->assertEqual($entity->getTranslationLanguages(FALSE), $translations, 'Translations retrieved.');
|
||||
|
||||
// Try to get a not available translation.
|
||||
$value = $entity->get($this->field_name, $this->langcodes[2]);
|
||||
$this->assertNull($value, 'A translation that is not available is NULL.');
|
||||
$this->assertNull($entity->getTranslation($this->langcodes[2])->get($this->field_name)->value, 'A translation that is not available is NULL.');
|
||||
|
||||
// Try to get a value using an invalid language code.
|
||||
$value = $entity->get($this->field_name, 'invalid');
|
||||
$this->assertNull($value, 'A translation for an invalid language is NULL.');
|
||||
try {
|
||||
$entity->getTranslation('invalid')->get($this->field_name)->value;
|
||||
$this->fail('Getting a translation for an invalid language is NULL.');
|
||||
}
|
||||
catch (InvalidArgumentException $e) {
|
||||
$this->pass('A translation for an invalid language is NULL.');
|
||||
}
|
||||
|
||||
// Try to get an unstranslatable value from a translation in strict mode.
|
||||
try {
|
||||
$field_name = 'field_test_text';
|
||||
$value = $entity->getTranslation($this->langcodes[1])->get($field_name);
|
||||
$this->fail('Getting an unstranslatable value from a translation in strict mode throws an exception.');
|
||||
}
|
||||
catch (InvalidArgumentException $e) {
|
||||
$this->pass('Getting an unstranslatable value from a translation in strict mode throws an exception.');
|
||||
}
|
||||
|
||||
// Try to get an unstranslatable value from a translation in non-strict
|
||||
// mode.
|
||||
$entity->set($field_name, array(0 => array('value' => 'default value')));
|
||||
$value = $entity->getTranslation($this->langcodes[1], FALSE)->get($field_name)->value;
|
||||
$this->assertEqual($value, 'default value', 'Untranslated value retrieved from translation in non-strict mode.');
|
||||
|
||||
// Try to set a value using an invalid language code.
|
||||
$message = "An exception is thrown when trying to set an invalid translation.";
|
||||
try {
|
||||
$entity->set($this->field_name, NULL, 'invalid');
|
||||
// This line is not expected to be executed unless something goes wrong.
|
||||
$this->fail($message);
|
||||
$entity->getTranslation('invalid')->set($this->field_name, NULL);
|
||||
$this->fail("Setting a translation for an invalid language throws an exception.");
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$this->assertTrue($e instanceof InvalidArgumentException, $message);
|
||||
catch (InvalidArgumentException $e) {
|
||||
$this->pass("Setting a translation for an invalid language throws an exception.");
|
||||
}
|
||||
|
||||
// Try to set an unstranslatable value into a translation in strict mode.
|
||||
try {
|
||||
$entity->getTranslation($this->langcodes[1])->set($field_name, NULL);
|
||||
$this->fail("Setting an unstranslatable value into a translation in strict mode throws an exception.");
|
||||
}
|
||||
catch (InvalidArgumentException $e) {
|
||||
$this->pass("Setting an unstranslatable value into a translation in strict mode throws an exception.");
|
||||
}
|
||||
|
||||
// Set the value in default language.
|
||||
$entity->getTranslation($this->langcodes[1], FALSE)->set($field_name, array(0 => array('value' => 'default value2')));
|
||||
// Get the value.
|
||||
$this->assertEqual($entity->get($field_name)->value, 'default value2', 'Untranslated value set into a translation in non-strict mode.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -160,29 +183,33 @@ class EntityTranslationTest extends WebTestBase {
|
|||
|
||||
// Create a language neutral entity and check that properties are stored
|
||||
// as language neutral.
|
||||
$entity = entity_create('entity_test', array('name' => $name, 'uid' => $uid));
|
||||
$entity = entity_create('entity_test', array('name' => $name, 'user_id' => $uid));
|
||||
$entity->save();
|
||||
$entity = entity_test_load($entity->id());
|
||||
$this->assertEqual($entity->language()->langcode, LANGUAGE_NOT_SPECIFIED, 'Entity created as language neutral.');
|
||||
$this->assertEqual($name, $entity->get('name', LANGUAGE_NOT_SPECIFIED), 'The entity name has been correctly stored as language neutral.');
|
||||
$this->assertEqual($uid, $entity->get('uid', LANGUAGE_NOT_SPECIFIED), 'The entity author has been correctly stored as language neutral.');
|
||||
$this->assertNull($entity->get('name', $langcode), 'The entity name is not available as a language-aware property.');
|
||||
$this->assertNull($entity->get('uid', $langcode), 'The entity author is not available as a language-aware property.');
|
||||
$this->assertEqual($name, $entity->get('name'), 'The entity name can be retrieved without specifying a language.');
|
||||
$this->assertEqual($uid, $entity->get('uid'), 'The entity author can be retrieved without specifying a language.');
|
||||
$this->assertEqual($name, $entity->getTranslation(LANGUAGE_DEFAULT)->get('name')->value, 'The entity name has been correctly stored as language neutral.');
|
||||
$this->assertEqual($uid, $entity->getTranslation(LANGUAGE_DEFAULT)->get('user_id')->value, 'The entity author has been correctly stored as language neutral.');
|
||||
// As fields, translatable properties should ignore the given langcode and
|
||||
// use neutral language if the entity is not translatable.
|
||||
$this->assertEqual($name, $entity->getTranslation($langcode)->get('name')->value, 'The entity name defaults to neutral language.');
|
||||
$this->assertEqual($uid, $entity->getTranslation($langcode)->get('user_id')->value, 'The entity author defaults to neutral language.');
|
||||
$this->assertEqual($name, $entity->get('name')->value, 'The entity name can be retrieved without specifying a language.');
|
||||
$this->assertEqual($uid, $entity->get('user_id')->value, 'The entity author can be retrieved without specifying a language.');
|
||||
|
||||
// Create a language-aware entity and check that properties are stored
|
||||
// as language-aware.
|
||||
$entity = entity_create('entity_test', array('name' => $name, 'uid' => $uid, 'langcode' => $langcode));
|
||||
$entity = entity_create('entity_test', array('name' => $name, 'user_id' => $uid, 'langcode' => $langcode));
|
||||
$entity->save();
|
||||
$entity = entity_test_load($entity->id());
|
||||
$this->assertEqual($entity->language()->langcode, $langcode, 'Entity created as language specific.');
|
||||
$this->assertEqual($name, $entity->get('name', $langcode), 'The entity name has been correctly stored as a language-aware property.');
|
||||
$this->assertEqual($uid, $entity->get('uid', $langcode), 'The entity author has been correctly stored as a language-aware property.');
|
||||
$this->assertNull($entity->get('name', LANGUAGE_NOT_SPECIFIED), 'The entity name is not available as a language neutral property.');
|
||||
$this->assertNull($entity->get('uid', LANGUAGE_NOT_SPECIFIED), 'The entity author is not available as a language neutral property.');
|
||||
$this->assertEqual($name, $entity->get('name'), 'The entity name can be retrieved without specifying a language.');
|
||||
$this->assertEqual($uid, $entity->get('uid'), 'The entity author can be retrieved without specifying a language.');
|
||||
$this->assertEqual($name, $entity->getTranslation($langcode)->get('name')->value, 'The entity name has been correctly stored as a language-aware property.');
|
||||
$this->assertEqual($uid, $entity->getTranslation($langcode)->get('user_id')->value, 'The entity author has been correctly stored as a language-aware property.');
|
||||
// Translatable properties on a translatable entity should use default
|
||||
// language if LANGUAGE_NOT_SPECIFIED is passed.
|
||||
$this->assertEqual($name, $entity->getTranslation(LANGUAGE_NOT_SPECIFIED)->get('name')->value, 'The entity name defaults to the default language.');
|
||||
$this->assertEqual($uid, $entity->getTranslation(LANGUAGE_NOT_SPECIFIED)->get('user_id')->value, 'The entity author defaults to the default language.');
|
||||
$this->assertEqual($name, $entity->get('name')->value, 'The entity name can be retrieved without specifying a language.');
|
||||
$this->assertEqual($uid, $entity->get('user_id')->value, 'The entity author can be retrieved without specifying a language.');
|
||||
|
||||
// Create property translations.
|
||||
$properties = array();
|
||||
|
@ -190,17 +217,17 @@ class EntityTranslationTest extends WebTestBase {
|
|||
foreach ($this->langcodes as $langcode) {
|
||||
if ($langcode != $default_langcode) {
|
||||
$properties[$langcode] = array(
|
||||
'name' => $this->randomName(),
|
||||
'uid' => mt_rand(0, 127),
|
||||
'name' => array(0 => $this->randomName()),
|
||||
'user_id' => array(0 => mt_rand(0, 127)),
|
||||
);
|
||||
}
|
||||
else {
|
||||
$properties[$langcode] = array(
|
||||
'name' => $name,
|
||||
'uid' => $uid,
|
||||
'name' => array(0 => $name),
|
||||
'user_id' => array(0 => $uid),
|
||||
);
|
||||
}
|
||||
$entity->setProperties($properties[$langcode], $langcode);
|
||||
$entity->getTranslation($langcode)->setPropertyValues($properties[$langcode]);
|
||||
}
|
||||
$entity->save();
|
||||
|
||||
|
@ -208,8 +235,8 @@ class EntityTranslationTest extends WebTestBase {
|
|||
$entity = entity_test_load($entity->id());
|
||||
foreach ($this->langcodes as $langcode) {
|
||||
$args = array('%langcode' => $langcode);
|
||||
$this->assertEqual($properties[$langcode]['name'], $entity->get('name', $langcode), format_string('The entity name has been correctly stored for language %langcode.', $args));
|
||||
$this->assertEqual($properties[$langcode]['uid'], $entity->get('uid', $langcode), format_string('The entity author has been correctly stored for language %langcode.', $args));
|
||||
$this->assertEqual($properties[$langcode]['name'][0], $entity->getTranslation($langcode)->get('name')->value, format_string('The entity name has been correctly stored for language %langcode.', $args));
|
||||
$this->assertEqual($properties[$langcode]['user_id'][0], $entity->getTranslation($langcode)->get('user_id')->value, format_string('The entity author has been correctly stored for language %langcode.', $args));
|
||||
}
|
||||
|
||||
// Test query conditions (cache is reset at each call).
|
||||
|
@ -217,7 +244,11 @@ class EntityTranslationTest extends WebTestBase {
|
|||
// Create an additional entity with only the uid set. The uid for the
|
||||
// original language is the same of one used for a translation.
|
||||
$langcode = $this->langcodes[1];
|
||||
entity_create('entity_test', array('uid' => $properties[$langcode]['uid']))->save();
|
||||
entity_create('entity_test', array(
|
||||
'user_id' => $properties[$langcode]['user_id'],
|
||||
'name' => 'some name',
|
||||
))->save();
|
||||
|
||||
$entities = entity_test_load_multiple();
|
||||
$this->assertEqual(count($entities), 3, 'Three entities were created.');
|
||||
$entities = entity_test_load_multiple(array($translated_id));
|
||||
|
@ -226,15 +257,16 @@ class EntityTranslationTest extends WebTestBase {
|
|||
$this->assertEqual(count($entities), 2, 'Two entities correctly loaded by name.');
|
||||
// @todo The default language condition should go away in favor of an
|
||||
// explicit parameter.
|
||||
$entities = entity_load_multiple_by_properties('entity_test', array('name' => $properties[$langcode]['name'], 'default_langcode' => 0));
|
||||
$entities = entity_load_multiple_by_properties('entity_test', array('name' => $properties[$langcode]['name'][0], 'default_langcode' => 0));
|
||||
$this->assertEqual(count($entities), 1, 'One entity correctly loaded by name translation.');
|
||||
$entities = entity_load_multiple_by_properties('entity_test', array('langcode' => $default_langcode, 'name' => $name));
|
||||
$this->assertEqual(count($entities), 1, 'One entity correctly loaded by name and language.');
|
||||
$entities = entity_load_multiple_by_properties('entity_test', array('langcode' => $langcode, 'name' => $properties[$langcode]['name']));
|
||||
|
||||
$entities = entity_load_multiple_by_properties('entity_test', array('langcode' => $langcode, 'name' => $properties[$langcode]['name'][0]));
|
||||
$this->assertEqual(count($entities), 0, 'No entity loaded by name translation specifying the translation language.');
|
||||
$entities = entity_load_multiple_by_properties('entity_test', array('langcode' => $langcode, 'name' => $properties[$langcode]['name'], 'default_langcode' => 0));
|
||||
$entities = entity_load_multiple_by_properties('entity_test', array('langcode' => $langcode, 'name' => $properties[$langcode]['name'][0], 'default_langcode' => 0));
|
||||
$this->assertEqual(count($entities), 1, 'One entity loaded by name translation and language specifying to look for translations.');
|
||||
$entities = entity_load_multiple_by_properties('entity_test', array('uid' => $properties[$langcode]['uid'], 'default_langcode' => NULL));
|
||||
$entities = entity_load_multiple_by_properties('entity_test', array('user_id' => $properties[$langcode]['user_id'][0], 'default_langcode' => NULL));
|
||||
$this->assertEqual(count($entities), 2, 'Two entities loaded by uid without caring about property translatability.');
|
||||
|
||||
// Test property conditions and orders with multiple languages in the same
|
||||
|
@ -242,7 +274,7 @@ class EntityTranslationTest extends WebTestBase {
|
|||
$query = new EntityFieldQuery();
|
||||
$query->entityCondition('entity_type', 'entity_test');
|
||||
$query->entityCondition('langcode', $default_langcode);
|
||||
$query->propertyCondition('uid', $properties[$default_langcode]['uid'], NULL, 'original');
|
||||
$query->propertyCondition('user_id', $properties[$default_langcode]['user_id'], NULL, 'original');
|
||||
$query->propertyCondition('name', $properties[$default_langcode]['name'], NULL, 'original');
|
||||
$query->propertyLanguageCondition($default_langcode, NULL, 'original');
|
||||
$query->propertyCondition('name', $properties[$langcode]['name'], NULL, 'translation');
|
||||
|
@ -254,12 +286,12 @@ class EntityTranslationTest extends WebTestBase {
|
|||
// Test mixed property and field conditions.
|
||||
$entity = entity_load('entity_test', key($result['entity_test']), TRUE);
|
||||
$field_value = $this->randomString();
|
||||
$entity->set($this->field_name, array(array('value' => $field_value)), $langcode);
|
||||
$entity->getTranslation($langcode)->set($this->field_name, array(array('value' => $field_value)));
|
||||
$entity->save();
|
||||
$query = new EntityFieldQuery();
|
||||
$query->entityCondition('entity_type', 'entity_test');
|
||||
$query->entityCondition('langcode', $default_langcode);
|
||||
$query->propertyCondition('uid', $properties[$default_langcode]['uid'], NULL, 'original');
|
||||
$query->propertyCondition('user_id', $properties[$default_langcode]['user_id'], NULL, 'original');
|
||||
$query->propertyCondition('name', $properties[$default_langcode]['name'], NULL, 'original');
|
||||
$query->propertyLanguageCondition($default_langcode, NULL, 'original');
|
||||
$query->propertyCondition('name', $properties[$langcode]['name'], NULL, 'translation');
|
||||
|
|
|
@ -64,7 +64,7 @@ class EntityUUIDTest extends WebTestBase {
|
|||
// Verify that entity_load_by_uuid() loads the same entity.
|
||||
$entity_loaded_by_uuid = entity_load_by_uuid('entity_test', $uuid, TRUE);
|
||||
$this->assertIdentical($entity_loaded_by_uuid->uuid(), $uuid);
|
||||
$this->assertEqual($entity_loaded_by_uuid, $entity_loaded);
|
||||
$this->assertEqual($entity_loaded_by_uuid->id(), $entity_loaded->id());
|
||||
|
||||
// Creating a duplicate needs to result in a new UUID.
|
||||
$entity_duplicate = $entity->createDuplicate();
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\system\Tests\TypedData\TypedDataTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\TypedData;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use DateTime;
|
||||
use DateInterval;
|
||||
|
||||
/**
|
||||
* Tests primitive data types.
|
||||
*/
|
||||
class TypedDataTest extends WebTestBase {
|
||||
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Test typed data objects',
|
||||
'description' => 'Tests the functionality of all core data types.',
|
||||
'group' => 'Typed Data API',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the basics around constructing and working with data wrappers.
|
||||
*/
|
||||
public function testGetAndSet() {
|
||||
// Boolean type.
|
||||
$wrapper = $this->createTypedData(array('type' => 'boolean'), TRUE);
|
||||
$this->assertTrue($wrapper->getValue() === TRUE, 'Boolean value was fetched.');
|
||||
$wrapper->setValue(FALSE);
|
||||
$this->assertTrue($wrapper->getValue() === FALSE, 'Boolean value was changed.');
|
||||
$this->assertTrue(is_string($wrapper->getString()), 'Boolean value was converted to string');
|
||||
$wrapper->setValue(NULL);
|
||||
$this->assertNull($wrapper->getValue(), 'Boolean wrapper is null-able.');
|
||||
|
||||
// String type.
|
||||
$value = $this->randomString();
|
||||
$wrapper = $this->createTypedData(array('type' => 'string'), $value);
|
||||
$this->assertTrue($wrapper->getValue() === $value, 'String value was fetched.');
|
||||
$new_value = $this->randomString();
|
||||
$wrapper->setValue($new_value);
|
||||
$this->assertTrue($wrapper->getValue() === $new_value, 'String value was changed.');
|
||||
// Funky test.
|
||||
$this->assertTrue(is_string($wrapper->getString()), 'String value was converted to string');
|
||||
$wrapper->setValue(NULL);
|
||||
$this->assertNull($wrapper->getValue(), 'String wrapper is null-able.');
|
||||
|
||||
// Integer type.
|
||||
$value = rand();
|
||||
$wrapper = $this->createTypedData(array('type' => 'integer'), $value);
|
||||
$this->assertTrue($wrapper->getValue() === $value, 'Integer value was fetched.');
|
||||
$new_value = rand();
|
||||
$wrapper->setValue($new_value);
|
||||
$this->assertTrue($wrapper->getValue() === $new_value, 'Integer value was changed.');
|
||||
$this->assertTrue(is_string($wrapper->getString()), 'Integer value was converted to string');
|
||||
$wrapper->setValue(NULL);
|
||||
$this->assertNull($wrapper->getValue(), 'Integer wrapper is null-able.');
|
||||
|
||||
// Float type.
|
||||
$value = 123.45;
|
||||
$wrapper = $this->createTypedData(array('type' => 'float'), $value);
|
||||
$this->assertTrue($wrapper->getValue() === $value, 'Float value was fetched.');
|
||||
$new_value = 678.90;
|
||||
$wrapper->setValue($new_value);
|
||||
$this->assertTrue($wrapper->getValue() === $new_value, 'Float value was changed.');
|
||||
$this->assertTrue(is_string($wrapper->getString()), 'Float value was converted to string');
|
||||
$wrapper->setValue(NULL);
|
||||
$this->assertNull($wrapper->getValue(), 'Float wrapper is null-able.');
|
||||
|
||||
// Date type.
|
||||
$value = new DateTime('@' . REQUEST_TIME);
|
||||
$wrapper = $this->createTypedData(array('type' => 'date'), $value);
|
||||
$this->assertTrue($wrapper->getValue() === $value, 'Date value was fetched.');
|
||||
$new_value = REQUEST_TIME + 1;
|
||||
$wrapper->setValue($new_value);
|
||||
$this->assertTrue($wrapper->getValue()->getTimestamp() === $new_value, 'Date value was changed and set by timestamp.');
|
||||
$wrapper->setValue('2000-01-01');
|
||||
$this->assertTrue($wrapper->getValue()->format('Y-m-d') == '2000-01-01', 'Date value was changed and set by date string.');
|
||||
$this->assertTrue(is_string($wrapper->getString()), 'Date value was converted to string');
|
||||
$wrapper->setValue(NULL);
|
||||
$this->assertNull($wrapper->getValue(), 'Date wrapper is null-able.');
|
||||
|
||||
// Duration type.
|
||||
$value = new DateInterval('PT20S');
|
||||
$wrapper = $this->createTypedData(array('type' => 'duration'), $value);
|
||||
$this->assertTrue($wrapper->getValue() === $value, 'Duration value was fetched.');
|
||||
$wrapper->setValue(10);
|
||||
$this->assertTrue($wrapper->getValue()->s == 10, 'Duration value was changed and set by time span in seconds.');
|
||||
$wrapper->setValue('P40D');
|
||||
$this->assertTrue($wrapper->getValue()->d == 40, 'Duration value was changed and set by duration string.');
|
||||
$this->assertTrue(is_string($wrapper->getString()), 'Duration value was converted to string');
|
||||
// Test getting the string and passing it back as value.
|
||||
$duration = $wrapper->getString();
|
||||
$wrapper->setValue($duration);
|
||||
$this->assertEqual($wrapper->getString(), $duration, 'Duration formatted as string can be used to set the duration value.');
|
||||
$wrapper->setValue(NULL);
|
||||
$this->assertNull($wrapper->getValue(), 'Duration wrapper is null-able.');
|
||||
|
||||
// Generate some files that will be used to test the URI and the binary
|
||||
// data types.
|
||||
$files = $this->drupalGetTestFiles('image');
|
||||
|
||||
// URI type.
|
||||
$wrapper = $this->createTypedData(array('type' => 'uri'), $files[0]->uri);
|
||||
$this->assertTrue($wrapper->getValue() === $files[0]->uri, 'URI value was fetched.');
|
||||
$wrapper->setValue($files[1]->uri);
|
||||
$this->assertTrue($wrapper->getValue() === $files[1]->uri, 'URI value was changed.');
|
||||
$this->assertTrue(is_string($wrapper->getString()), 'URI value was converted to string');
|
||||
$wrapper->setValue(NULL);
|
||||
$this->assertNull($wrapper->getValue(), 'URI wrapper is null-able.');
|
||||
|
||||
// Binary type.
|
||||
$wrapper = $this->createTypedData(array('type' => 'binary'), $files[0]->uri);
|
||||
$this->assertTrue(is_resource($wrapper->getValue()), 'Binary value was fetched.');
|
||||
// Try setting by URI.
|
||||
$wrapper->setValue($files[1]->uri);
|
||||
$this->assertEqual(is_resource($wrapper->getValue()), fopen($files[1]->uri, 'r'), 'Binary value was changed.');
|
||||
$this->assertTrue(is_string($wrapper->getString()), 'Binary value was converted to string');
|
||||
// Try setting by resource.
|
||||
$wrapper->setValue(fopen($files[2]->uri, 'r'));
|
||||
$this->assertEqual(is_resource($wrapper->getValue()), fopen($files[2]->uri, 'r'), 'Binary value was changed.');
|
||||
$this->assertTrue(is_string($wrapper->getString()), 'Binary value was converted to string');
|
||||
$wrapper->setValue(NULL);
|
||||
$this->assertNull($wrapper->getValue(), 'Binary wrapper is null-able.');
|
||||
}
|
||||
}
|
|
@ -146,6 +146,65 @@ function hook_cron() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines available data types for the typed data API.
|
||||
*
|
||||
* The typed data API allows modules to support any kind of data based upon
|
||||
* pre-defined primitive types and interfaces for complex data and lists.
|
||||
*
|
||||
* Defined data types may map to one of the pre-defined primitive types in
|
||||
* \Drupal\Core\TypedData\Primitive or may be complex data types, containing one
|
||||
* or more data properties. Typed data objects for complex data types have to
|
||||
* implement the \Drupal\Core\TypedData\ComplexDataInterface. Further interfaces
|
||||
* that may be implemented are:
|
||||
* - \Drupal\Core\TypedData\AccessibleInterface
|
||||
* - \Drupal\Core\TypedData\TranslatableInterface
|
||||
*
|
||||
* Furthermore, lists of data items are represented by objects implementing
|
||||
* the \Drupal\Core\TypedData\ListInterface. A list contains items of the same
|
||||
* data type, is ordered and may contain duplicates. The classed used for a list
|
||||
* of items of a certain type may be specified using the 'list class' key.
|
||||
*
|
||||
* @return array
|
||||
* An associative array where the key is the data type name and the value is
|
||||
* again an associative array. Supported keys are:
|
||||
* - label: The human readable label of the data type.
|
||||
* - class: The associated typed data class. Must implement the
|
||||
* \Drupal\Core\TypedData\TypedDataInterface.
|
||||
* - list class: (optional) A typed data class used for wrapping multiple
|
||||
* data items of the type. Must implement the
|
||||
* \Drupal\Core\TypedData\ListInterface.
|
||||
* - primitive type: (optional) Maps the data type to one of the pre-defined
|
||||
* primitive types in \Drupal\Core\TypedData\Primitive. If set, it must be
|
||||
* a constant defined by \Drupal\Core\TypedData\Primitive such as
|
||||
* \Drupal\Core\TypedData\Primitive::String.
|
||||
*
|
||||
* @see typed_data()
|
||||
* @see Drupal\Core\TypedData\TypedDataManager::create()
|
||||
* @see hook_data_type_info_alter()
|
||||
*/
|
||||
function hook_data_type_info() {
|
||||
return array(
|
||||
'email' => array(
|
||||
'label' => t('Email'),
|
||||
'class' => '\Drupal\email\Type\Email',
|
||||
'primitive type' => \Drupal\Core\TypedData\Primitive::String,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter available data types for typed data wrappers.
|
||||
*
|
||||
* @param array $data_types
|
||||
* An array of data type information.
|
||||
*
|
||||
* @see hook_data_type_info()
|
||||
*/
|
||||
function hook_data_type_info_alter(&$data_types) {
|
||||
$data_types['email']['class'] = '\Drupal\mymodule\Type\Email';
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare queues holding items that need to be run periodically.
|
||||
*
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
use Drupal\Core\Utility\ModuleInfo;
|
||||
use Drupal\Core\TypedData\Primitive;
|
||||
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
@ -1941,6 +1942,105 @@ function system_stream_wrappers() {
|
|||
return $wrappers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_data_type_info().
|
||||
*/
|
||||
function system_data_type_info() {
|
||||
return array(
|
||||
'boolean' => array(
|
||||
'label' => t('Boolean'),
|
||||
'class' => '\Drupal\Core\TypedData\Type\Boolean',
|
||||
'primitive type' => Primitive::BOOLEAN,
|
||||
),
|
||||
'string' => array(
|
||||
'label' => t('String'),
|
||||
'class' => '\Drupal\Core\TypedData\Type\String',
|
||||
'primitive type' => Primitive::STRING,
|
||||
),
|
||||
'integer' => array(
|
||||
'label' => t('Integer'),
|
||||
'class' => '\Drupal\Core\TypedData\Type\Integer',
|
||||
'primitive type' => Primitive::INTEGER,
|
||||
),
|
||||
'float' => array(
|
||||
'label' => t('Float'),
|
||||
'class' => '\Drupal\Core\TypedData\Type\Float',
|
||||
'primitive type' => Primitive::FLOAT,
|
||||
),
|
||||
'date' => array(
|
||||
'label' => t('Date'),
|
||||
'class' => '\Drupal\Core\TypedData\Type\Date',
|
||||
'primitive type' => Primitive::DATE,
|
||||
),
|
||||
'duration' => array(
|
||||
'label' => t('Duration'),
|
||||
'class' => '\Drupal\Core\TypedData\Type\Duration',
|
||||
'primitive type' => Primitive::DURATION,
|
||||
),
|
||||
'uri' => array(
|
||||
'label' => t('URI'),
|
||||
'class' => '\Drupal\Core\TypedData\Type\Uri',
|
||||
'primitive type' => Primitive::URI,
|
||||
),
|
||||
'binary' => array(
|
||||
'label' => t('Binary'),
|
||||
'class' => '\Drupal\Core\TypedData\Type\Binary',
|
||||
'primitive type' => Primitive::BINARY,
|
||||
),
|
||||
'language' => array(
|
||||
'label' => t('Language'),
|
||||
'description' => t('A language object.'),
|
||||
'class' => '\Drupal\Core\TypedData\Type\Language',
|
||||
),
|
||||
'entity' => array(
|
||||
'label' => t('Entity'),
|
||||
'description' => t('All kind of entities, e.g. nodes, comments or users.'),
|
||||
'class' => '\Drupal\Core\Entity\Field\Type\EntityWrapper',
|
||||
),
|
||||
'entity_translation' => array(
|
||||
'label' => t('Entity translation'),
|
||||
'description' => t('A translation of an entity'),
|
||||
'class' => '\Drupal\Core\Entity\Field\Type\EntityTranslation',
|
||||
),
|
||||
'boolean_field' => array(
|
||||
'label' => t('Boolean field item'),
|
||||
'description' => t('An entity field containing a boolean value.'),
|
||||
'class' => '\Drupal\Core\Entity\Field\Type\BooleanItem',
|
||||
'list class' => '\Drupal\Core\Entity\Field\Type\Field',
|
||||
),
|
||||
'string_field' => array(
|
||||
'label' => t('String field item'),
|
||||
'description' => t('An entity field containing a string value.'),
|
||||
'class' => '\Drupal\Core\Entity\Field\Type\StringItem',
|
||||
'list class' => '\Drupal\Core\Entity\Field\Type\Field',
|
||||
),
|
||||
'integer_field' => array(
|
||||
'label' => t('Integer field item'),
|
||||
'description' => t('An entity field containing an integer value.'),
|
||||
'class' => '\Drupal\Core\Entity\Field\Type\IntegerItem',
|
||||
'list class' => '\Drupal\Core\Entity\Field\Type\Field',
|
||||
),
|
||||
'date_field' => array(
|
||||
'label' => t('Date field item'),
|
||||
'description' => t('An entity field containing a date value.'),
|
||||
'class' => '\Drupal\Core\Entity\Field\Type\DateItem',
|
||||
'list class' => '\Drupal\Core\Entity\Field\Type\Field',
|
||||
),
|
||||
'language_field' => array(
|
||||
'label' => t('Language field item'),
|
||||
'description' => t('An entity field referencing a language.'),
|
||||
'class' => '\Drupal\Core\Entity\Field\Type\LanguageItem',
|
||||
'list class' => '\Drupal\Core\Entity\Field\Type\Field',
|
||||
),
|
||||
'entityreference_field' => array(
|
||||
'label' => t('Entity reference field item'),
|
||||
'description' => t('An entity field containing an entity reference.'),
|
||||
'class' => '\Drupal\Core\Entity\Field\Type\EntityReferenceItem',
|
||||
'list class' => '\Drupal\Core\Entity\Field\Type\Field',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu item access callback - only enabled themes can be accessed.
|
||||
*/
|
||||
|
|
|
@ -3,4 +3,5 @@ description = Provides entity types based upon the CRUD API.
|
|||
package = Testing
|
||||
version = VERSION
|
||||
core = 8.x
|
||||
dependencies[] = field
|
||||
hidden = TRUE
|
||||
|
|
|
@ -91,7 +91,7 @@ function entity_test_schema() {
|
|||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
),
|
||||
'uid' => array(
|
||||
'user_id' => array(
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => FALSE,
|
||||
|
@ -100,10 +100,10 @@ function entity_test_schema() {
|
|||
),
|
||||
),
|
||||
'indexes' => array(
|
||||
'uid' => array('uid'),
|
||||
'user_id' => array('user_id'),
|
||||
),
|
||||
'foreign keys' => array(
|
||||
'uid' => array('users' => 'uid'),
|
||||
'user_id' => array('users' => 'uid'),
|
||||
'id' => array('entity_test' => 'id'),
|
||||
),
|
||||
'primary key' => array('id', 'langcode'),
|
||||
|
|
|
@ -86,7 +86,7 @@ function entity_test_add() {
|
|||
* Menu callback: displays the 'Edit existing entity_test' form.
|
||||
*/
|
||||
function entity_test_edit($entity) {
|
||||
drupal_set_title(t('entity_test @id', array('@id' => $entity->id)), PASS_THROUGH);
|
||||
drupal_set_title(t('entity_test @id', array('@id' => $entity->id())), PASS_THROUGH);
|
||||
return entity_get_form($entity);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,137 +7,52 @@
|
|||
|
||||
namespace Drupal\entity_test;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
use Drupal\Core\Entity\Entity;
|
||||
use Drupal\Core\Entity\EntityNG;
|
||||
|
||||
/**
|
||||
* Defines the test entity class.
|
||||
*/
|
||||
class EntityTest extends Entity {
|
||||
class EntityTest extends EntityNG {
|
||||
|
||||
/**
|
||||
* An array keyed by language code where the entity properties are stored.
|
||||
* The entity ID.
|
||||
*
|
||||
* @var array
|
||||
* @var \Drupal\Core\Entity\Field\FieldInterface
|
||||
*/
|
||||
protected $properties;
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* An array of allowed language codes.
|
||||
* The entity UUID.
|
||||
*
|
||||
* @var array
|
||||
* @var \Drupal\Core\Entity\Field\FieldInterface
|
||||
*/
|
||||
protected static $langcodes;
|
||||
public $uuid;
|
||||
|
||||
/**
|
||||
* Constructs a new entity object.
|
||||
* The name of the test entity.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\Field\FieldInterface
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* The associated user.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\Field\FieldInterface
|
||||
*/
|
||||
public $user_id;
|
||||
|
||||
/**
|
||||
* Overrides Entity::__construct().
|
||||
*/
|
||||
public function __construct(array $values, $entity_type) {
|
||||
parent::__construct($values, $entity_type);
|
||||
|
||||
if (!isset(self::$langcodes)) {
|
||||
// The allowed languages are simply all the available ones in the system.
|
||||
self::$langcodes = drupal_map_assoc(array_keys(language_list(LANGUAGE_ALL)));
|
||||
}
|
||||
|
||||
// Initialize the original entity language with the provided value or fall
|
||||
// back to LANGUAGE_NOT_SPECIFIED if none was specified. We do not check
|
||||
// against allowed languages here, since throwing an exception would make an
|
||||
// entity created in a subsequently uninstalled language not instantiable.
|
||||
$this->langcode = !empty($values['langcode']) ? $values['langcode'] : LANGUAGE_NOT_SPECIFIED;
|
||||
|
||||
// Set initial values ensuring that only real properties are stored.
|
||||
// @todo For now we have no way to mark a property as multlingual hence we
|
||||
// just assume that all of them are.
|
||||
unset($values['id'], $values['uuid'], $values['default_langcode']);
|
||||
$this->setProperties($values, $this->langcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the entity original langcode.
|
||||
*
|
||||
* @param $langcode
|
||||
*/
|
||||
public function setLangcode($langcode) {
|
||||
// If the original language is changed the related properties must change
|
||||
// their language accordingly.
|
||||
$prev_langcode = $this->langcode;
|
||||
if (isset($this->properties[$prev_langcode])) {
|
||||
$this->properties[$langcode] = $this->properties[$prev_langcode];
|
||||
unset($this->properties[$prev_langcode]);
|
||||
}
|
||||
$this->langcode = $langcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides EntityInterface::get().
|
||||
*/
|
||||
public function get($property_name, $langcode = NULL) {
|
||||
$langcode = !empty($langcode) ? $langcode : $this->langcode;
|
||||
$entity_info = $this->entityInfo();
|
||||
if ($entity_info['fieldable'] && field_info_instance($this->entityType, $property_name, $this->bundle())) {
|
||||
return parent::get($property_name, $langcode);
|
||||
}
|
||||
elseif (isset($this->properties[$langcode][$property_name])) {
|
||||
return $this->properties[$langcode][$property_name];
|
||||
}
|
||||
else {
|
||||
// @todo Remove this. All properties should be stored in the $properties
|
||||
// array once we have a Property API in place.
|
||||
return property_exists($this, $property_name) ? $this->{$property_name} : NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides EntityInterface::set().
|
||||
*/
|
||||
public function set($property_name, $value, $langcode = NULL) {
|
||||
$langcode = !empty($langcode) ? $langcode : $this->langcode;
|
||||
if (!isset(self::$langcodes[$langcode])) {
|
||||
throw new InvalidArgumentException("Detected an invalid language '$langcode' while setting '$property_name' to '$value'.");
|
||||
}
|
||||
$entity_info = $this->entityInfo();
|
||||
if ($entity_info['fieldable'] && field_info_instance($this->entityType, $property_name, $this->bundle())) {
|
||||
parent::set($property_name, $value, $langcode);
|
||||
}
|
||||
else {
|
||||
$this->properties[$langcode][$property_name] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides EntityInterface::translations().
|
||||
*/
|
||||
public function translations() {
|
||||
$translations = !empty($this->properties) ? $this->properties : array();
|
||||
$languages = array_intersect_key(self::$langcodes, $translations);
|
||||
unset($languages[$this->langcode]);
|
||||
return $languages + parent::translations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the property array for the given language.
|
||||
*
|
||||
* @param string $langcode
|
||||
* (optional) The language code to be used to retrieve the properties.
|
||||
*/
|
||||
public function getProperties($langcode = NULL) {
|
||||
$langcode = !empty($langcode) ? $langcode : $this->langcode;
|
||||
return isset($this->properties[$langcode]) ? $this->properties[$langcode] : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the property array for the given language.
|
||||
*
|
||||
* @param array $properties
|
||||
* A keyed array of properties to be set with their 'langcode' as one of the
|
||||
* keys. If no language is provided the entity language is used.
|
||||
* @param string $langcode
|
||||
* (optional) The language code to be used to set the properties.
|
||||
*/
|
||||
public function setProperties(array $properties, $langcode = NULL) {
|
||||
$langcode = !empty($langcode) ? $langcode : $this->langcode;
|
||||
$this->properties[$langcode] = $properties;
|
||||
// We unset all defined properties, so magic getters apply.
|
||||
unset($this->id);
|
||||
unset($this->langcode);
|
||||
unset($this->uuid);
|
||||
unset($this->name);
|
||||
unset($this->user_id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,12 +7,12 @@
|
|||
namespace Drupal\entity_test;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityFormController;
|
||||
use Drupal\Core\Entity\EntityFormControllerNG;
|
||||
|
||||
/**
|
||||
* Form controller for the test entity edit forms.
|
||||
*/
|
||||
class EntityTestFormController extends EntityFormController {
|
||||
class EntityTestFormController extends EntityFormControllerNG {
|
||||
|
||||
/**
|
||||
* Overrides Drupal\Core\Entity\EntityFormController::form().
|
||||
|
@ -21,23 +21,22 @@ class EntityTestFormController extends EntityFormController {
|
|||
$form = parent::form($form, $form_state, $entity);
|
||||
|
||||
$langcode = $this->getFormLangcode($form_state);
|
||||
$name = $entity->get('name', $langcode);
|
||||
$uid = $entity->get('uid', $langcode);
|
||||
$translation = $entity->getTranslation($langcode);
|
||||
|
||||
$form['name'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Name'),
|
||||
'#default_value' => !empty($name) ? $name : '',
|
||||
'#default_value' => $translation->name->value,
|
||||
'#size' => 60,
|
||||
'#maxlength' => 128,
|
||||
'#required' => TRUE,
|
||||
'#weight' => -10,
|
||||
);
|
||||
|
||||
$form['uid'] = array(
|
||||
$form['user_id'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => 'UID',
|
||||
'#default_value' => !empty($uid) ? $uid : '',
|
||||
'#default_value' => $translation->user_id->value,
|
||||
'#size' => 60,
|
||||
'#maxlength' => 128,
|
||||
'#required' => TRUE,
|
||||
|
@ -53,12 +52,11 @@ class EntityTestFormController extends EntityFormController {
|
|||
public function submit(array $form, array &$form_state) {
|
||||
$entity = parent::submit($form, $form_state);
|
||||
$langcode = $this->getFormLangcode($form_state);
|
||||
|
||||
// Updates multilingual properties.
|
||||
foreach (array('name', 'uid') as $property) {
|
||||
$entity->set($property, $form_state['values'][$property], $langcode);
|
||||
$translation = $entity->getTranslation($langcode);
|
||||
foreach (array('name', 'user_id') as $name) {
|
||||
$translation->$name->setValue($form_state['values'][$name]);
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,9 +9,9 @@ namespace Drupal\entity_test;
|
|||
|
||||
use PDO;
|
||||
|
||||
use Drupal\Core\Entity\DatabaseStorageController;
|
||||
use Drupal\Core\Entity\EntityFieldQuery;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\DatabaseStorageControllerNG;
|
||||
use Drupal\Core\Entity\EntityFieldQuery;
|
||||
|
||||
/**
|
||||
* Defines the controller class for the test entity.
|
||||
|
@ -19,10 +19,10 @@ use Drupal\Core\Entity\EntityInterface;
|
|||
* This extends the Drupal\Core\Entity\DatabaseStorageController class, adding
|
||||
* required special handling for test entities.
|
||||
*/
|
||||
class EntityTestStorageController extends DatabaseStorageController {
|
||||
class EntityTestStorageController extends DatabaseStorageControllerNG {
|
||||
|
||||
/**
|
||||
* Overrides Drupal\Core\Entity\DatabaseStorageController::loadByProperties().
|
||||
* Overrides Drupal\Core\Entity\DatabaseStorageController::buildPropertyQuery().
|
||||
*/
|
||||
protected function buildPropertyQuery(EntityFieldQuery $entity_query, array $values) {
|
||||
// @todo We should not be using a condition to specify whether conditions
|
||||
|
@ -43,9 +43,23 @@ class EntityTestStorageController extends DatabaseStorageController {
|
|||
}
|
||||
|
||||
/**
|
||||
* Overrides Drupal\Core\Entity\DatabaseStorageController::attachLoad().
|
||||
* Maps from storage records to entity objects.
|
||||
*
|
||||
* @return array
|
||||
* An array of entity objects implementing the EntityInterface.
|
||||
*/
|
||||
protected function attachLoad(&$queried_entities, $load_revision = FALSE) {
|
||||
protected function mapFromStorageRecords(array $records) {
|
||||
$records = parent::mapFromStorageRecords($records);
|
||||
|
||||
// Load data of translatable properties.
|
||||
$this->attachPropertyData($records);
|
||||
return $records;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches property data in all languages for translatable properties.
|
||||
*/
|
||||
protected function attachPropertyData(&$queried_entities) {
|
||||
$data = db_select('entity_test_property_data', 'data', array('fetch' => PDO::FETCH_ASSOC))
|
||||
->fields('data')
|
||||
->condition('id', array_keys($queried_entities))
|
||||
|
@ -53,35 +67,34 @@ class EntityTestStorageController extends DatabaseStorageController {
|
|||
->execute();
|
||||
|
||||
foreach ($data as $values) {
|
||||
$entity = $queried_entities[$values['id']];
|
||||
$langcode = $values['langcode'];
|
||||
if (!empty($values['default_langcode'])) {
|
||||
$entity->setLangcode($langcode);
|
||||
}
|
||||
// Make sure only real properties are stored.
|
||||
unset($values['id'], $values['default_langcode']);
|
||||
$entity->setProperties($values, $langcode);
|
||||
}
|
||||
$id = $values['id'];
|
||||
// Field values in default language are stored with
|
||||
// LANGUAGE_DEFAULT as key.
|
||||
$langcode = empty($values['default_langcode']) ? $values['langcode'] : LANGUAGE_DEFAULT;
|
||||
|
||||
parent::attachLoad($queried_entities, $load_revision);
|
||||
$queried_entities[$id]->name[$langcode][0]['value'] = $values['name'];
|
||||
$queried_entities[$id]->user_id[$langcode][0]['value'] = $values['user_id'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides Drupal\Core\Entity\DatabaseStorageController::postSave().
|
||||
*
|
||||
* Stores values of translatable properties.
|
||||
*/
|
||||
protected function postSave(EntityInterface $entity, $update) {
|
||||
$default_langcode = ($language = $entity->language()) ? $language->langcode : LANGUAGE_NOT_SPECIFIED;
|
||||
$langcodes = array_keys($entity->translations());
|
||||
$langcodes[] = $default_langcode;
|
||||
$default_langcode = $entity->language()->langcode;
|
||||
|
||||
foreach ($langcodes as $langcode) {
|
||||
$properties = $entity->getProperties($langcode);
|
||||
foreach ($entity->getTranslationLanguages() as $langcode => $language) {
|
||||
$translation = $entity->getTranslation($langcode);
|
||||
|
||||
$values = array(
|
||||
'id' => $entity->id(),
|
||||
'langcode' => $langcode,
|
||||
'default_langcode' => intval($default_langcode == $langcode),
|
||||
) + $properties;
|
||||
'name' => $translation->name->value,
|
||||
'user_id' => $translation->user_id->value,
|
||||
);
|
||||
|
||||
db_merge('entity_test_property_data')
|
||||
->fields($values)
|
||||
|
@ -99,4 +112,40 @@ class EntityTestStorageController extends DatabaseStorageController {
|
|||
->condition('id', array_keys($entities))
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Drupal\Core\Entity\DataBaseStorageControllerNG::baseFieldDefinitions().
|
||||
*/
|
||||
public function baseFieldDefinitions() {
|
||||
$fields['id'] = array(
|
||||
'label' => t('ID'),
|
||||
'description' => t('The ID of the test entity.'),
|
||||
'type' => 'integer_field',
|
||||
'read-only' => TRUE,
|
||||
);
|
||||
$fields['uuid'] = array(
|
||||
'label' => t('UUID'),
|
||||
'description' => t('The UUID of the test entity.'),
|
||||
'type' => 'string_field',
|
||||
);
|
||||
$fields['langcode'] = array(
|
||||
'label' => t('Language code'),
|
||||
'description' => t('The language code of the test entity.'),
|
||||
'type' => 'language_field',
|
||||
);
|
||||
$fields['name'] = array(
|
||||
'label' => t('Name'),
|
||||
'description' => t('The name of the test entity.'),
|
||||
'type' => 'string_field',
|
||||
'translatable' => TRUE,
|
||||
);
|
||||
$fields['user_id'] = array(
|
||||
'label' => t('User ID'),
|
||||
'description' => t('The ID of the associated user.'),
|
||||
'type' => 'entityreference_field',
|
||||
'settings' => array('entity type' => 'user'),
|
||||
'translatable' => TRUE,
|
||||
);
|
||||
return $fields;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue