Issue #1969728 by yched, effulgentsia, swentel, fago, plach: Implement Field API 'field types' as TypedData Plugins.
parent
377521e267
commit
a2c2367bf8
|
|
@ -30,6 +30,13 @@ services:
|
|||
factory_method: get
|
||||
factory_service: cache_factory
|
||||
arguments: [cache]
|
||||
cache.entity:
|
||||
class: Drupal\Core\Cache\CacheBackendInterface
|
||||
tags:
|
||||
- { name: cache.bin }
|
||||
factory_method: get
|
||||
factory_service: cache_factory
|
||||
arguments: [entity]
|
||||
cache.menu:
|
||||
class: Drupal\Core\Cache\CacheBackendInterface
|
||||
tags:
|
||||
|
|
@ -151,6 +158,9 @@ services:
|
|||
plugin.manager.entity:
|
||||
class: Drupal\Core\Entity\EntityManager
|
||||
arguments: ['@container.namespaces', '@service_container', '@module_handler', '@cache.cache', '@language_manager']
|
||||
plugin.manager.entity.field.field_type:
|
||||
class: Drupal\Core\Entity\Field\FieldTypePluginManager
|
||||
arguments: ['@container.namespaces', '@cache.entity', '@language_manager', '@module_handler']
|
||||
plugin.manager.archiver:
|
||||
class: Drupal\Core\Archiver\ArchiverManager
|
||||
arguments: ['@container.namespaces', '@cache.cache', '@language_manager', '@module_handler']
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ function entity_info_cache_clear() {
|
|||
drupal_static_reset('entity_get_bundles');
|
||||
// Clear all languages.
|
||||
Drupal::entityManager()->clearCachedDefinitions();
|
||||
Drupal::entityManager()->clearCachedFieldDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,121 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Entity\Annotation\FieldType.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Entity\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
|
||||
/**
|
||||
* Defines a FieldType annotation object.
|
||||
*
|
||||
* Additional annotation keys for field types can be defined in
|
||||
* hook_field_info_alter().
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class FieldType extends Plugin {
|
||||
|
||||
/**
|
||||
* The plugin ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* The name of the module providing the field type plugin.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $module;
|
||||
|
||||
/**
|
||||
* The human-readable name of the field type.
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*/
|
||||
public $label;
|
||||
|
||||
/**
|
||||
* A short human readable description for the field type.
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*/
|
||||
public $description;
|
||||
|
||||
/**
|
||||
* An array of field-level settings available for the field type.
|
||||
*
|
||||
* Keys are the names of the settings, and values are the default values for
|
||||
* those settings.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $settings;
|
||||
|
||||
/**
|
||||
* An array of instance-level settings available for the field type.
|
||||
*
|
||||
* Keys are the names of the settings, and values are the default values for
|
||||
* those settings.
|
||||
*
|
||||
* Instance-level settings can have different values on each field instance,
|
||||
* and thus allow greater flexibility than field-level settings. It is
|
||||
* recommended to put settings at the instance level whenever possible.
|
||||
* Notable exceptions: settings acting on the storage schema, or settings that
|
||||
* Views needs to use across field instances (for example, settings defining
|
||||
* the list of allowed values for the field).
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $instance_settings;
|
||||
|
||||
/**
|
||||
* The plugin_id of the default widget for this field type.
|
||||
*
|
||||
* This widget must be available whenever the field type is available (i.e.
|
||||
* provided by the field type module, or by a module the field type module
|
||||
* depends on).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $default_widget;
|
||||
|
||||
/**
|
||||
* The plugin_id of the default formatter for this field type.
|
||||
*
|
||||
* This formatter must be available whenever the field type is available (i.e.
|
||||
* provided by the field type module, or by a module the field type module
|
||||
* depends on).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $default_formatter;
|
||||
|
||||
/**
|
||||
* A boolean stating that fields of this type are configurable.
|
||||
*
|
||||
* @todo: Make field module respect this.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $configurable = TRUE;
|
||||
|
||||
/**
|
||||
* A boolean stating that fields of this type cannot be created through the UI.
|
||||
*
|
||||
* If TRUE, fields of this type can only be created programmatically.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $no_ui = FALSE;
|
||||
|
||||
}
|
||||
|
|
@ -214,6 +214,7 @@ class DatabaseStorageController extends EntityStorageControllerBase {
|
|||
$this->database->delete($this->revisionTable)
|
||||
->condition($this->revisionKey, $revision->getRevisionId())
|
||||
->execute();
|
||||
$this->invokeFieldMethod('deleteRevision', $revision);
|
||||
$this->invokeHook('revision_delete', $revision);
|
||||
}
|
||||
}
|
||||
|
|
@ -406,6 +407,7 @@ class DatabaseStorageController extends EntityStorageControllerBase {
|
|||
|
||||
$entity_class::postDelete($this, $entities);
|
||||
foreach ($entities as $id => $entity) {
|
||||
$this->invokeFieldMethod('delete', $entity);
|
||||
$this->invokeHook('delete', $entity);
|
||||
}
|
||||
// Ignore slave server temporarily.
|
||||
|
|
@ -430,6 +432,7 @@ class DatabaseStorageController extends EntityStorageControllerBase {
|
|||
}
|
||||
|
||||
$entity->preSave($this);
|
||||
$this->invokeFieldMethod('preSave', $entity);
|
||||
$this->invokeHook('presave', $entity);
|
||||
|
||||
if (!$entity->isNew()) {
|
||||
|
|
@ -446,6 +449,7 @@ class DatabaseStorageController extends EntityStorageControllerBase {
|
|||
}
|
||||
$this->resetCache(array($entity->id()));
|
||||
$entity->postSave($this, TRUE);
|
||||
$this->invokeFieldMethod('update', $entity);
|
||||
$this->invokeHook('update', $entity);
|
||||
}
|
||||
else {
|
||||
|
|
@ -458,6 +462,7 @@ class DatabaseStorageController extends EntityStorageControllerBase {
|
|||
|
||||
$entity->enforceIsNew(FALSE);
|
||||
$entity->postSave($this, FALSE);
|
||||
$this->invokeFieldMethod('insert', $entity);
|
||||
$this->invokeHook('insert', $entity);
|
||||
}
|
||||
|
||||
|
|
@ -518,7 +523,8 @@ class DatabaseStorageController extends EntityStorageControllerBase {
|
|||
* Invokes a hook on behalf of the entity.
|
||||
*
|
||||
* @param $hook
|
||||
* One of 'presave', 'insert', 'update', 'predelete', or 'delete'.
|
||||
* One of 'presave', 'insert', 'update', 'predelete', 'delete', or
|
||||
* 'revision_delete'.
|
||||
* @param $entity
|
||||
* The entity object.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -219,28 +219,7 @@ class DatabaseStorageControllerNG extends DatabaseStorageController {
|
|||
protected function attachLoad(&$queried_entities, $load_revision = FALSE) {
|
||||
// Map the loaded stdclass records into entity objects and according fields.
|
||||
$queried_entities = $this->mapFromStorageRecords($queried_entities, $load_revision);
|
||||
|
||||
if ($this->entityInfo['fieldable']) {
|
||||
if ($load_revision) {
|
||||
field_attach_load_revision($this->entityType, $queried_entities);
|
||||
}
|
||||
else {
|
||||
field_attach_load($this->entityType, $queried_entities);
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
parent::attachLoad($queried_entities, $load_revision);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -363,6 +342,7 @@ class DatabaseStorageControllerNG extends DatabaseStorageController {
|
|||
}
|
||||
|
||||
$entity->preSave($this);
|
||||
$this->invokeFieldMethod('preSave', $entity);
|
||||
$this->invokeHook('presave', $entity);
|
||||
|
||||
// Create the storage record to be saved.
|
||||
|
|
@ -385,6 +365,7 @@ class DatabaseStorageControllerNG extends DatabaseStorageController {
|
|||
}
|
||||
$this->resetCache(array($entity->id()));
|
||||
$entity->postSave($this, TRUE);
|
||||
$this->invokeFieldMethod('update', $entity);
|
||||
$this->invokeHook('update', $entity);
|
||||
}
|
||||
else {
|
||||
|
|
@ -403,6 +384,7 @@ class DatabaseStorageControllerNG extends DatabaseStorageController {
|
|||
|
||||
$entity->enforceIsNew(FALSE);
|
||||
$entity->postSave($this, FALSE);
|
||||
$this->invokeFieldMethod('insert', $entity);
|
||||
$this->invokeHook('insert', $entity);
|
||||
}
|
||||
|
||||
|
|
@ -503,28 +485,6 @@ class DatabaseStorageControllerNG extends DatabaseStorageController {
|
|||
$query->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides DatabaseStorageController::invokeHook().
|
||||
*
|
||||
* Invokes field API attachers with a BC entity.
|
||||
*/
|
||||
protected function invokeHook($hook, EntityInterface $entity) {
|
||||
$function = 'field_attach_' . $hook;
|
||||
// @todo: field_attach_delete_revision() is named the wrong way round,
|
||||
// consider renaming it.
|
||||
if ($function == 'field_attach_revision_delete') {
|
||||
$function = 'field_attach_delete_revision';
|
||||
}
|
||||
if (!empty($this->entityInfo['fieldable']) && function_exists($function)) {
|
||||
$function($entity);
|
||||
}
|
||||
|
||||
// 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.
|
||||
*
|
||||
|
|
@ -638,6 +598,7 @@ class DatabaseStorageControllerNG extends DatabaseStorageController {
|
|||
|
||||
$entity_class::postDelete($this, $entities);
|
||||
foreach ($entities as $id => $entity) {
|
||||
$this->invokeFieldMethod('delete', $entity);
|
||||
$this->invokeHook('delete', $entity);
|
||||
}
|
||||
// Ignore slave server temporarily.
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ class EntityBCDecorator implements IteratorAggregate, EntityInterface {
|
|||
foreach ($this->decorated->fields[$name] as $langcode => $field) {
|
||||
// Only set if it's not empty, otherwise there can be ghost values.
|
||||
if (!$field->isEmpty()) {
|
||||
$this->decorated->values[$name][$langcode] = $field->getValue();
|
||||
$this->decorated->values[$name][$langcode] = $field->getValue(TRUE);
|
||||
}
|
||||
}
|
||||
// The returned values might be changed by reference, so we need to remove
|
||||
|
|
@ -124,11 +124,10 @@ class EntityBCDecorator implements IteratorAggregate, EntityInterface {
|
|||
$this->decorated->values[$name][Language::LANGCODE_DEFAULT][0]['value'] = NULL;
|
||||
}
|
||||
if (is_array($this->decorated->values[$name][Language::LANGCODE_DEFAULT])) {
|
||||
// This will work with all defined properties that have a single value.
|
||||
// We need to ensure the key doesn't matter. Mostly it's 'value' but
|
||||
// e.g. EntityReferenceItem uses target_id.
|
||||
if (isset($this->decorated->values[$name][Language::LANGCODE_DEFAULT][0]) && count($this->decorated->values[$name][Language::LANGCODE_DEFAULT][0]) == 1) {
|
||||
return $this->decorated->values[$name][Language::LANGCODE_DEFAULT][0][key($this->decorated->values[$name][Language::LANGCODE_DEFAULT][0])];
|
||||
// e.g. EntityReferenceItem uses target_id - so just take the first one.
|
||||
if (isset($this->decorated->values[$name][Language::LANGCODE_DEFAULT][0]) && is_array($this->decorated->values[$name][Language::LANGCODE_DEFAULT][0])) {
|
||||
return $this->decorated->values[$name][Language::LANGCODE_DEFAULT][0][current(array_keys($this->decorated->values[$name][Language::LANGCODE_DEFAULT][0]))];
|
||||
}
|
||||
}
|
||||
return $this->decorated->values[$name][Language::LANGCODE_DEFAULT];
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
namespace Drupal\Core\Entity;
|
||||
|
||||
use Drupal\entity\EntityFormDisplayInterface;
|
||||
|
||||
use Drupal\Core\Language\Language;
|
||||
|
||||
/**
|
||||
|
|
@ -241,13 +240,49 @@ class EntityFormController implements EntityFormControllerInterface {
|
|||
* Implements \Drupal\Core\Entity\EntityFormControllerInterface::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();
|
||||
$entity_langcode = $entity->language()->langcode;
|
||||
|
||||
if (!empty($info['fieldable'])) {
|
||||
field_attach_form_validate($entity, $form, $form_state);
|
||||
$violations = array();
|
||||
|
||||
// @todo Simplify when all entity types are converted to EntityNG.
|
||||
if ($entity instanceof EntityNG) {
|
||||
foreach ($entity as $field_name => $field) {
|
||||
$field_violations = $field->validate();
|
||||
if (count($field_violations)) {
|
||||
$violations[$field_name] = $field_violations;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// For BC entities, iterate through each field instance and
|
||||
// instantiate NG items objects manually.
|
||||
$definitions = \Drupal::entityManager()->getFieldDefinitions($entity->entityType(), $entity->bundle());
|
||||
foreach (field_info_instances($entity->entityType(), $entity->bundle()) as $field_name => $instance) {
|
||||
$langcode = field_is_translatable($entity->entityType(), $instance->getField()) ? $entity_langcode : Language::LANGCODE_NOT_SPECIFIED;
|
||||
|
||||
// Create the field object.
|
||||
$items = isset($entity->{$field_name}[$langcode]) ? $entity->{$field_name}[$langcode] : array();
|
||||
// @todo Exception : calls setValue(), tries to set the 'formatted'
|
||||
// property.
|
||||
$field = \Drupal::typedData()->create($definitions[$field_name], $items, $field_name, $entity);
|
||||
$field_violations = $field->validate();
|
||||
if (count($field_violations)) {
|
||||
$violations[$field->getName()] = $field_violations;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Map errors back to form elements.
|
||||
if ($violations) {
|
||||
foreach ($violations as $field_name => $field_violations) {
|
||||
$langcode = field_is_translatable($entity->entityType(), field_info_field($field_name)) ? $entity_langcode : Language::LANGCODE_NOT_SPECIFIED;
|
||||
$field_state = field_form_get_state($form['#parents'], $field_name, $langcode, $form_state);
|
||||
$field_state['constraint_violations'] = $field_violations;
|
||||
field_form_set_state($form['#parents'], $field_name, $langcode, $form_state, $field_state);
|
||||
}
|
||||
|
||||
field_invoke_method('flagErrors', _field_invoke_widget_target($form_state['form_display']), $entity, $form, $form_state);
|
||||
}
|
||||
|
||||
// @todo Remove this.
|
||||
|
|
|
|||
|
|
@ -41,25 +41,6 @@ class EntityFormControllerNG extends EntityFormController {
|
|||
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 fields.
|
||||
$entity = $this->buildEntity($form, $form_state);
|
||||
$info = $entity->entityInfo();
|
||||
|
||||
if (!empty($info['fieldable'])) {
|
||||
field_attach_form_validate($entity, $form, $form_state);
|
||||
}
|
||||
|
||||
// @todo Remove this.
|
||||
// Execute legacy global validation handlers.
|
||||
unset($form_state['validate_handlers']);
|
||||
form_execute_handlers('validate', $form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides EntityFormController::submitEntityLanguage().
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -60,6 +60,13 @@ class EntityNG extends Entity {
|
|||
*/
|
||||
protected $bcEntity;
|
||||
|
||||
/**
|
||||
* Local cache for the entity language.
|
||||
*
|
||||
* @var \Drupal\Core\Language\Language
|
||||
*/
|
||||
protected $language;
|
||||
|
||||
/**
|
||||
* Local cache for field definitions.
|
||||
*
|
||||
|
|
@ -326,15 +333,30 @@ class EntityNG extends Entity {
|
|||
* Implements \Drupal\Core\TypedData\TranslatableInterface::language().
|
||||
*/
|
||||
public function language() {
|
||||
// Get the language code if the property exists.
|
||||
if ($this->getPropertyDefinition('langcode')) {
|
||||
$language = $this->get('langcode')->language;
|
||||
// Keep a local cache of the language object and clear it if the langcode
|
||||
// gets changed, see EntityNG::onChange().
|
||||
if (!isset($this->language)) {
|
||||
// Get the language code if the property exists.
|
||||
if ($this->getPropertyDefinition('langcode')) {
|
||||
$this->language = $this->get('langcode')->language;
|
||||
}
|
||||
if (empty($this->language)) {
|
||||
// Make sure we return a proper language object.
|
||||
$this->language = new Language(array('langcode' => Language::LANGCODE_NOT_SPECIFIED));
|
||||
}
|
||||
}
|
||||
if (empty($language)) {
|
||||
// Make sure we return a proper language object.
|
||||
$language = new Language(array('langcode' => Language::LANGCODE_NOT_SPECIFIED));
|
||||
return $this->language;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onChange($property_name) {
|
||||
if ($property_name == 'langcode') {
|
||||
// Avoid using unset as this unnecessarily triggers magic methods later
|
||||
// on.
|
||||
$this->language = NULL;
|
||||
}
|
||||
return $language;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -386,18 +408,20 @@ class EntityNG extends Entity {
|
|||
$definitions = $this->getPropertyDefinitions();
|
||||
// Build an array with the translation langcodes set as keys. Empty
|
||||
// translations should not be included and must be skipped.
|
||||
foreach ($this->getProperties() as $name => $property) {
|
||||
foreach ($this->fields[$name] as $langcode => $field) {
|
||||
if (!$field->isEmpty()) {
|
||||
$translations[$langcode] = TRUE;
|
||||
foreach ($definitions as $name => $definition) {
|
||||
if (isset($this->fields[$name])) {
|
||||
foreach ($this->fields[$name] as $langcode => $field) {
|
||||
if (!$field->isEmpty()) {
|
||||
$translations[$langcode] = TRUE;
|
||||
}
|
||||
}
|
||||
if (isset($this->values[$name])) {
|
||||
foreach ($this->values[$name] as $langcode => $values) {
|
||||
// If a value is there but the field object is empty, it has been
|
||||
// unset, so we need to skip the field also.
|
||||
if ($values && !empty($definitions[$name]['translatable']) && !(isset($this->fields[$name][$langcode]) && $this->fields[$name][$langcode]->isEmpty())) {
|
||||
$translations[$langcode] = TRUE;
|
||||
}
|
||||
}
|
||||
if (isset($this->values[$name])) {
|
||||
foreach ($this->values[$name] as $langcode => $values) {
|
||||
// If a value is there but the field object is empty, it has been
|
||||
// unset, so we need to skip the field also.
|
||||
if ($values && !empty($definition['translatable']) && !(isset($this->fields[$name][$langcode]) && $this->fields[$name][$langcode]->isEmpty())) {
|
||||
$translations[$langcode] = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,4 +137,101 @@ abstract class EntityStorageControllerBase implements EntityStorageControllerInt
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function invokeFieldMethod($method, EntityInterface $entity) {
|
||||
foreach (array_keys($entity->getTranslationLanguages()) as $langcode) {
|
||||
// @todo getTranslation() only works on NG entities. Remove the condition
|
||||
// and the second code branch when all core entity types are converted.
|
||||
if ($translation = $entity->getTranslation($langcode)) {
|
||||
foreach ($translation as $field_name => $field) {
|
||||
$field->$method();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// For BC entities, iterate through fields and instantiate NG items
|
||||
// objects manually.
|
||||
$definitions = \Drupal::entityManager()->getFieldDefinitions($entity->entityType(), $entity->bundle());
|
||||
foreach ($definitions as $field_name => $definition) {
|
||||
if (!empty($definition['configurable'])) {
|
||||
// Create the items object.
|
||||
$itemsBC = isset($entity->{$field_name}[$langcode]) ? $entity->{$field_name}[$langcode] : array();
|
||||
// @todo Exception : this calls setValue(), tries to set the
|
||||
// 'formatted' property. For now, this is worked around by
|
||||
// commenting out the Exception in TextProcessed::setValue().
|
||||
$items = \Drupal::typedData()->create($definition, $itemsBC, $field_name, $entity);
|
||||
$items->$method();
|
||||
|
||||
// Put back the items values in the entity.
|
||||
$itemsBC = $items->getValue(TRUE);
|
||||
if ($itemsBC !== array() || isset($entity->{$field_name}[$langcode])) {
|
||||
$entity->{$field_name}[$langcode] = $itemsBC;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function invokeFieldItemPrepareCache(EntityInterface $entity) {
|
||||
foreach (array_keys($entity->getTranslationLanguages()) as $langcode) {
|
||||
// @todo getTranslation() only works on NG entities. Remove the condition
|
||||
// and the second code branch when all core entity types are converted.
|
||||
if ($translation = $entity->getTranslation($langcode)) {
|
||||
foreach ($translation->getPropertyDefinitions() as $property => $definition) {
|
||||
$type_definition = \Drupal::typedData()->getDefinition($definition['type']);
|
||||
// Only create the item objects if needed.
|
||||
if (is_subclass_of($type_definition['class'], '\Drupal\Core\Entity\Field\PrepareCacheInterface')
|
||||
// Prevent legacy field types from skewing performance too much by
|
||||
// checking the existence of the legacy function directly, instead
|
||||
// of making LegacyConfigFieldItem implement PrepareCacheInterface.
|
||||
// @todo Remove once all core field types have been converted (see
|
||||
// http://drupal.org/node/2014671).
|
||||
|| (is_subclass_of($type_definition['class'], '\Drupal\field\Plugin\field\field_type\LegacyConfigFieldItem') && function_exists($type_definition['module'] . '_field_load'))) {
|
||||
|
||||
// Call the prepareCache() method directly on each item
|
||||
// individually.
|
||||
foreach ($translation->get($property) as $item) {
|
||||
$item->prepareCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// For BC entities, iterate through the fields and instantiate NG items
|
||||
// objects manually.
|
||||
$definitions = \Drupal::entityManager()->getFieldDefinitions($entity->entityType(), $entity->bundle());
|
||||
foreach ($definitions as $field_name => $definition) {
|
||||
if (!empty($definition['configurable'])) {
|
||||
$type_definition = \Drupal::typedData()->getDefinition($definition['type']);
|
||||
// Only create the item objects if needed.
|
||||
if (is_subclass_of($type_definition['class'], '\Drupal\Core\Entity\Field\PrepareCacheInterface')
|
||||
// @todo Remove once all core field types have been converted
|
||||
// (see http://drupal.org/node/2014671).
|
||||
|| (is_subclass_of($type_definition['class'], '\Drupal\field\Plugin\field\field_type\LegacyConfigFieldItem') && function_exists($type_definition['module'] . '_field_load'))) {
|
||||
|
||||
// Create the items object.
|
||||
$items = isset($entity->{$field_name}[$langcode]) ? $entity->{$field_name}[$langcode] : array();
|
||||
$itemsNG = \Drupal::typedData()->create($definition, $items, $field_name, $entity);
|
||||
|
||||
foreach ($itemsNG as $item) {
|
||||
$item->prepareCache();
|
||||
}
|
||||
|
||||
// Put back the items values in the entity.
|
||||
$items = $itemsNG->getValue(TRUE);
|
||||
if ($items !== array() || isset($entity->{$field_name}[$langcode])) {
|
||||
$entity->{$field_name}[$langcode] = $items;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,4 +144,22 @@ interface EntityStorageControllerInterface {
|
|||
*/
|
||||
public function getQueryServicename();
|
||||
|
||||
/**
|
||||
* Invokes a method on the Field objects within an entity.
|
||||
*
|
||||
* @param string $method
|
||||
* The method name.
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity object.
|
||||
*/
|
||||
public function invokeFieldMethod($method, EntityInterface $entity);
|
||||
|
||||
/**
|
||||
* Invokes the prepareCache() method on all the relevant FieldItem objects.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity object.
|
||||
*/
|
||||
public function invokeFieldItemPrepareCache(EntityInterface $entity);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,4 +80,46 @@ interface FieldInterface extends ListInterface, AccessibleInterface {
|
|||
* @see \Drupal\Core\Entity\Field\FieldItemInterface::getPropertyDefinitions()
|
||||
*/
|
||||
public function getPropertyDefinitions();
|
||||
|
||||
/**
|
||||
* Defines custom presave behavior for field values.
|
||||
*
|
||||
* This method is called before either insert() or update() methods, and
|
||||
* before values are written into storage.
|
||||
*/
|
||||
public function preSave();
|
||||
|
||||
/**
|
||||
* Defines custom insert behavior for field values.
|
||||
*
|
||||
* This method is called after the save() method, and before values are
|
||||
* written into storage.
|
||||
*/
|
||||
public function insert();
|
||||
|
||||
/**
|
||||
* Defines custom update behavior for field values.
|
||||
*
|
||||
* This method is called after the save() method, and before values are
|
||||
* written into storage.
|
||||
*/
|
||||
public function update();
|
||||
|
||||
/**
|
||||
* Defines custom delete behavior for field values.
|
||||
*
|
||||
* This method is called during the process of deleting an entity, just before
|
||||
* values are deleted from storage.
|
||||
*/
|
||||
public function delete();
|
||||
|
||||
/**
|
||||
* Defines custom revision delete behavior for field values.
|
||||
*
|
||||
* This method is called from during the process of deleting an entity
|
||||
* revision, just before the field values are deleted from storage. It is only
|
||||
* called for entity types that support revisioning.
|
||||
*/
|
||||
public function deleteRevision();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -152,4 +152,34 @@ abstract class FieldItemBase extends Map implements FieldItemInterface {
|
|||
return $constraints;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function preSave() { }
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function insert() { }
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function update() { }
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete() { }
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteRevision() { }
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function prepareView(array $entities_items) { }
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,4 +70,46 @@ interface FieldItemInterface extends ComplexDataInterface {
|
|||
* The name of the property to get; e.g., 'title' or 'name'.
|
||||
*/
|
||||
public function __unset($property_name);
|
||||
|
||||
/**
|
||||
* Defines custom presave behavior for field values.
|
||||
*
|
||||
* This method is called before either insert() or update() methods, and
|
||||
* before values are written into storage.
|
||||
*/
|
||||
public function preSave();
|
||||
|
||||
/**
|
||||
* Defines custom insert behavior for field values.
|
||||
*
|
||||
* This method is called after the save() method, and before values are
|
||||
* written into storage.
|
||||
*/
|
||||
public function insert();
|
||||
|
||||
/**
|
||||
* Defines custom update behavior for field values.
|
||||
*
|
||||
* This method is called after the save() method, and before values are
|
||||
* written into storage.
|
||||
*/
|
||||
public function update();
|
||||
|
||||
/**
|
||||
* Defines custom delete behavior for field values.
|
||||
*
|
||||
* This method is called during the process of deleting an entity, just before
|
||||
* values are deleted from storage.
|
||||
*/
|
||||
public function delete();
|
||||
|
||||
/**
|
||||
* Defines custom revision delete behavior for field values.
|
||||
*
|
||||
* This method is called from during the process of deleting an entity
|
||||
* revision, just before the field values are deleted from storage. It is only
|
||||
* called for entity types that support revisioning.
|
||||
*/
|
||||
public function deleteRevision();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* Contains \Drupal\Core\Entity\Field\FieldTypePluginManager.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Entity\Field;
|
||||
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Language\LanguageManager;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
use Drupal\field\Plugin\Type\FieldType\LegacyFieldTypeDiscoveryDecorator;
|
||||
|
||||
/**
|
||||
* Plugin manager for 'field type' plugins.
|
||||
*/
|
||||
class FieldTypePluginManager extends DefaultPluginManager {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaults = array(
|
||||
'settings' => array(),
|
||||
'instance_settings' => array(),
|
||||
'list_class' => '\Drupal\field\Plugin\Type\FieldType\ConfigField',
|
||||
);
|
||||
|
||||
/**
|
||||
* Constructs the FieldTypePluginManager object
|
||||
*
|
||||
* @param \Traversable $namespaces
|
||||
* An object that implements \Traversable which contains the root paths
|
||||
* keyed by the corresponding namespace to look for plugin implementations.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
||||
* Cache backend instance to use.
|
||||
* @param \Drupal\Core\Language\LanguageManager $language_manager
|
||||
* The language manager.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
* The module handler.
|
||||
*/
|
||||
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, LanguageManager $language_manager, ModuleHandlerInterface $module_handler) {
|
||||
$annotation_namespaces = array(
|
||||
'Drupal\Core\Entity\Annotation' => DRUPAL_ROOT . '/core/lib',
|
||||
);
|
||||
parent::__construct('field/field_type', $namespaces, $annotation_namespaces, 'Drupal\Core\Entity\Annotation\FieldType');
|
||||
$this->alterInfo($module_handler, 'field_info');
|
||||
$this->setCacheBackend($cache_backend, $language_manager, 'field_types');
|
||||
|
||||
// @todo Remove once all core field types have been converted (see
|
||||
// http://drupal.org/node/2014671).
|
||||
$this->discovery = new LegacyFieldTypeDiscoveryDecorator($this->discovery, $module_handler);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Entity\Field\PrepareCacheInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Entity\Field;
|
||||
|
||||
/**
|
||||
* Interface for preparing field values before they enter cache.
|
||||
*
|
||||
* If a field type implements this interface, the prepareCache() method will be
|
||||
* invoked before field values get cached.
|
||||
*/
|
||||
interface PrepareCacheInterface {
|
||||
|
||||
/**
|
||||
* Massages loaded field values before they enter the field cache.
|
||||
*
|
||||
* You should never load fieldable entities within this method, since this is
|
||||
* likely to cause infinite recursions. Use the prepareView() method instead.
|
||||
*
|
||||
* Also note that the method is not called on field values displayed during
|
||||
* entity preview. If the method adds elements that might be needed during
|
||||
* display, you might want to also use prepareView() to add those elements in
|
||||
* case they are not present.
|
||||
*/
|
||||
public function prepareCache();
|
||||
|
||||
}
|
||||
|
|
@ -7,12 +7,12 @@
|
|||
|
||||
namespace Drupal\Core\Entity\Field\Type;
|
||||
|
||||
use Drupal\Core\Entity\Field\FieldItemBase;
|
||||
use Drupal\field\Plugin\field\field_type\LegacyConfigFieldItem;
|
||||
|
||||
/**
|
||||
* Defines the 'email_field' entity field item.
|
||||
*/
|
||||
class EmailItem extends FieldItemBase {
|
||||
class EmailItem extends LegacyConfigFieldItem {
|
||||
|
||||
/**
|
||||
* Definitions of the contained properties.
|
||||
|
|
@ -36,4 +36,11 @@ class EmailItem extends FieldItemBase {
|
|||
}
|
||||
return static::$propertyDefinitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isEmpty() {
|
||||
return !isset($this->values['value']) || $this->values['value'] === '';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,6 +59,20 @@ class Field extends ItemList implements FieldInterface {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @todo Revisit the need when all entity types are converted to NG entities.
|
||||
*/
|
||||
public function getValue($include_computed = FALSE) {
|
||||
if (isset($this->list)) {
|
||||
$values = array();
|
||||
foreach ($this->list as $delta => $item) {
|
||||
$values[$delta] = $item->getValue($include_computed);
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides \Drupal\Core\TypedData\ItemList::setValue().
|
||||
*/
|
||||
|
|
@ -226,4 +240,57 @@ class Field extends ItemList implements FieldInterface {
|
|||
}
|
||||
return $constraints;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function preSave() {
|
||||
// Filter out empty items.
|
||||
$this->filterEmptyValues();
|
||||
|
||||
$this->delegateMethod('presave');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function insert() {
|
||||
$this->delegateMethod('insert');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function update() {
|
||||
$this->delegateMethod('update');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete() {
|
||||
$this->delegateMethod('delete');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteRevision() {
|
||||
$this->delegateMethod('deleteRevision');
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a method on each FieldItem.
|
||||
*
|
||||
* @param string $method
|
||||
* The name of the method.
|
||||
*/
|
||||
protected function delegateMethod($method) {
|
||||
if (isset($this->list)) {
|
||||
foreach ($this->list as $item) {
|
||||
$item->{$method}();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Entity\Plugin\DataType\FieldDataTypeDerivative.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Entity\Plugin\DataType;
|
||||
|
||||
use Drupal\Component\Plugin\Derivative\DerivativeInterface;
|
||||
|
||||
/**
|
||||
* Provides data type plugins for each existing field type plugin.
|
||||
*/
|
||||
class FieldDataTypeDerivative implements DerivativeInterface {
|
||||
|
||||
/**
|
||||
* List of derivative definitions.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $derivatives = array();
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinition($derivative_id, array $base_plugin_definition) {
|
||||
if (!isset($this->derivatives)) {
|
||||
$this->getDerivativeDefinitions($base_plugin_definition);
|
||||
}
|
||||
if (isset($this->derivatives[$derivative_id])) {
|
||||
return $this->derivatives[$derivative_id];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinitions(array $base_plugin_definition) {
|
||||
foreach (\Drupal::service('plugin.manager.entity.field.field_type')->getDefinitions() as $plugin_id => $definition) {
|
||||
// Typed data API expects a 'list class' property, but annotations do not
|
||||
// support spaces in property names.
|
||||
$definition['list class'] = $definition['list_class'];
|
||||
unset($definition['list_class']);
|
||||
|
||||
$this->derivatives[$plugin_id] = $definition;
|
||||
}
|
||||
return $this->derivatives;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -53,11 +53,11 @@ class Map extends TypedData implements \IteratorAggregate, ComplexDataInterface
|
|||
/**
|
||||
* Overrides \Drupal\Core\TypedData\TypedData::getValue().
|
||||
*/
|
||||
public function getValue() {
|
||||
public function getValue($include_computed = FALSE) {
|
||||
// Update the values and return them.
|
||||
foreach ($this->properties as $name => $property) {
|
||||
$definition = $property->getDefinition();
|
||||
if (empty($definition['computed'])) {
|
||||
if ($include_computed || empty($definition['computed'])) {
|
||||
$value = $property->getValue();
|
||||
// Only write NULL values if the whole map is not NULL.
|
||||
if (isset($this->values) || isset($value)) {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ namespace Drupal\Core\TypedData;
|
|||
|
||||
use InvalidArgumentException;
|
||||
use Drupal\Component\Plugin\Discovery\ProcessDecorator;
|
||||
use Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator;
|
||||
use Drupal\Component\Plugin\PluginManagerBase;
|
||||
use Drupal\Core\Plugin\Discovery\CacheDecorator;
|
||||
use Drupal\Core\Plugin\Discovery\HookDiscovery;
|
||||
|
|
@ -57,6 +58,7 @@ class TypedDataManager extends PluginManagerBase {
|
|||
|
||||
public function __construct() {
|
||||
$this->discovery = new HookDiscovery('data_type_info');
|
||||
$this->discovery = new DerivativeDiscoveryDecorator($this->discovery);
|
||||
$this->discovery = new ProcessDecorator($this->discovery, array($this, 'processDefinition'));
|
||||
$this->discovery = new CacheDecorator($this->discovery, 'typed_data:types');
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Validation\Plugin\Validation\Constraint\CountConstraint.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Validation\Plugin\Validation\Constraint;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
use Drupal\Core\Annotation\Translation;
|
||||
use Symfony\Component\Validator\Constraints\Count;
|
||||
|
||||
/**
|
||||
* Count constraint.
|
||||
*
|
||||
* Overrides the symfony constraint to use Drupal-style replacement patterns.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = "Count",
|
||||
* label = @Translation("Count", context = "Validation"),
|
||||
* type = { "list" }
|
||||
* )
|
||||
*/
|
||||
class CountConstraint extends Count {
|
||||
|
||||
public $minMessage = 'This collection should contain %limit element or more.|This collection should contain %limit elements or more.';
|
||||
public $maxMessage = 'This collection should contain %limit element or less.|This collection should contain %limit elements or less.';
|
||||
public $exactMessage = 'This collection should contain exactly %limit element.|This collection should contain exactly %limit elements.';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validatedBy() {
|
||||
return '\Symfony\Component\Validator\Constraints\CountValidator';
|
||||
}
|
||||
}
|
||||
|
|
@ -106,8 +106,7 @@ function datetime_field_info() {
|
|||
),
|
||||
'default_widget' => 'datetime_default',
|
||||
'default_formatter' => 'datetime_default',
|
||||
'default_token_formatter' => 'datetime_plain',
|
||||
'field item class' => '\Drupal\datetime\Type\DateTimeItem',
|
||||
'class' => '\Drupal\datetime\Type\DateTimeItem',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@
|
|||
|
||||
namespace Drupal\datetime\Type;
|
||||
|
||||
use Drupal\Core\Entity\Field\FieldItemBase;
|
||||
use Drupal\field\Plugin\field\field_type\LegacyConfigFieldItem;
|
||||
|
||||
/**
|
||||
* Defines the 'datetime' entity field item.
|
||||
*/
|
||||
class DateTimeItem extends FieldItemBase {
|
||||
class DateTimeItem extends LegacyConfigFieldItem {
|
||||
|
||||
/**
|
||||
* Field definitions of the contained properties.
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ function email_field_info() {
|
|||
'description' => t('This field stores an e-mail address in the database.'),
|
||||
'default_widget' => 'email_default',
|
||||
'default_formatter' => 'email_mailto',
|
||||
'field item class' => 'Drupal\Core\Entity\Field\Type\EmailItem',
|
||||
'class' => 'Drupal\Core\Entity\Field\Type\EmailItem',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -42,13 +42,6 @@ function email_field_info_alter(&$info) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_is_empty().
|
||||
*/
|
||||
function email_field_is_empty($item, $field_type) {
|
||||
return !isset($item['value']) || $item['value'] === '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_formatter_info_alter().
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -29,8 +29,7 @@ function entity_reference_field_info() {
|
|||
),
|
||||
'default_widget' => 'entity_reference_autocomplete',
|
||||
'default_formatter' => 'entity_reference_label',
|
||||
'data_type' => 'entity_reference_configurable_field',
|
||||
'field item class' => '\Drupal\entity_reference\Type\ConfigurableEntityReferenceItem',
|
||||
'class' => '\Drupal\entity_reference\Type\ConfigurableEntityReferenceItem',
|
||||
);
|
||||
return $field_info;
|
||||
}
|
||||
|
|
@ -89,17 +88,6 @@ function entity_reference_get_selection_handler(FieldDefinitionInterface $field_
|
|||
return Drupal::service('plugin.manager.entity_reference.selection')->getInstance($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_is_empty().
|
||||
*/
|
||||
function entity_reference_field_is_empty($item, $field_type) {
|
||||
if (empty($item['target_id']) && !empty($item['entity']) && $item['entity']->isNew()) {
|
||||
// Allow auto-create entities.
|
||||
return FALSE;
|
||||
}
|
||||
return !isset($item['target_id']) || !is_numeric($item['target_id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_presave().
|
||||
*
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ namespace Drupal\entity_reference\Plugin\field\widget;
|
|||
use Drupal\Component\Annotation\Plugin;
|
||||
use Drupal\Core\Annotation\Translation;
|
||||
use Drupal\field\Plugin\Type\Widget\WidgetBase;
|
||||
use Symfony\Component\Validator\ConstraintViolationInterface;
|
||||
|
||||
/**
|
||||
* Parent plugin for entity reference autocomplete widgets.
|
||||
|
|
@ -86,7 +87,7 @@ abstract class AutocompleteWidgetBase extends WidgetBase {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function errorElement(array $element, array $error, array $form, array &$form_state) {
|
||||
public function errorElement(array $element, ConstraintViolationInterface $error, array $form, array &$form_state) {
|
||||
return $element['target_id'];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
namespace Drupal\entity_reference\Type;
|
||||
|
||||
use Drupal\Core\Entity\Field\Type\EntityReferenceItem;
|
||||
use Drupal\field\Plugin\Type\FieldType\ConfigEntityReferenceItemBase;
|
||||
use Drupal\field\Plugin\Type\FieldType\ConfigFieldItemInterface;
|
||||
|
||||
/**
|
||||
* Defines the 'entity_reference_configurable' entity field item.
|
||||
|
|
@ -18,48 +20,6 @@ use Drupal\Core\Entity\Field\Type\EntityReferenceItem;
|
|||
* Required settings (below the definition's 'settings' key) are:
|
||||
* - target_type: The entity type to reference.
|
||||
*/
|
||||
class ConfigurableEntityReferenceItem extends EntityReferenceItem {
|
||||
|
||||
/**
|
||||
* Definitions of the contained properties.
|
||||
*
|
||||
* @see ConfigurableEntityReferenceItem::getPropertyDefinitions()
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
static $propertyDefinitions;
|
||||
|
||||
/**
|
||||
* Overrides \Drupal\Core\Entity\Field\Type\EntityReferenceItem::getPropertyDefinitions().
|
||||
*/
|
||||
public function getPropertyDefinitions() {
|
||||
// Definitions vary by entity type, so key them by entity type.
|
||||
$target_type = $this->definition['settings']['target_type'];
|
||||
|
||||
if (!isset(self::$propertyDefinitions[$target_type])) {
|
||||
// Call the parent to define the target_id and entity properties.
|
||||
parent::getPropertyDefinitions();
|
||||
|
||||
static::$propertyDefinitions[$target_type]['revision_id'] = array(
|
||||
// @todo: Lookup the entity type's ID data type and use it here.
|
||||
'type' => 'integer',
|
||||
'label' => t('Revision ID'),
|
||||
'constraints' => array(
|
||||
'Range' => array('min' => 0),
|
||||
),
|
||||
);
|
||||
static::$propertyDefinitions[$target_type]['label'] = array(
|
||||
'type' => 'string',
|
||||
'label' => t('Label (auto-create)'),
|
||||
'computed' => TRUE,
|
||||
);
|
||||
static::$propertyDefinitions[$target_type]['access'] = array(
|
||||
'type' => 'boolean',
|
||||
'label' => t('Access'),
|
||||
'computed' => TRUE,
|
||||
);
|
||||
}
|
||||
return static::$propertyDefinitions[$target_type];
|
||||
}
|
||||
class ConfigurableEntityReferenceItem extends ConfigEntityReferenceItemBase implements ConfigFieldItemInterface {
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,9 +105,7 @@ function hook_field_extra_fields_alter(&$info) {
|
|||
* In the Field API, each field has a type, which determines what kind of data
|
||||
* (integer, string, date, etc.) the field can hold, which settings it provides,
|
||||
* and so on. The data type(s) accepted by a field are defined in
|
||||
* hook_field_schema(); other basic properties of a field are defined in
|
||||
* hook_field_info(). The other hooks below are called by the Field Attach API
|
||||
* to perform field-type-specific actions.
|
||||
* hook_field_schema().
|
||||
*
|
||||
* The Field Types API also defines two kinds of pluggable handlers: widgets
|
||||
* and formatters. @link field_widget Widgets @endlink specify how the field
|
||||
|
|
@ -121,76 +119,13 @@ function hook_field_extra_fields_alter(&$info) {
|
|||
* the Field API.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Define Field API field types.
|
||||
*
|
||||
* @return
|
||||
* An array whose keys are field type names and whose values are arrays
|
||||
* describing the field type, with the following key/value pairs:
|
||||
* - label: The human-readable name of the field type.
|
||||
* - description: A short description for the field type.
|
||||
* - settings: An array whose keys are the names of the settings available
|
||||
* for the field type, and whose values are the default values for those
|
||||
* settings.
|
||||
* - instance_settings: An array whose keys are the names of the settings
|
||||
* available for instances of the field type, and whose values are the
|
||||
* default values for those settings. Instance-level settings can have
|
||||
* different values on each field instance, and thus allow greater
|
||||
* flexibility than field-level settings. It is recommended to put settings
|
||||
* at the instance level whenever possible. Notable exceptions: settings
|
||||
* acting on the schema definition, or settings that Views needs to use
|
||||
* across field instances (for example, the list of allowed values).
|
||||
* - default_widget: The machine name of the default widget to be used by
|
||||
* instances of this field type, when no widget is specified in the instance
|
||||
* definition. This widget must be available whenever the field type is
|
||||
* available (i.e. provided by the field type module, or by a module the
|
||||
* field type module depends on).
|
||||
* - default_formatter: The machine name of the default formatter to be used
|
||||
* by instances of this field type, when no formatter is specified in the
|
||||
* instance definition. This formatter must be available whenever the field
|
||||
* type is available (i.e. provided by the field type module, or by a module
|
||||
* the field type module depends on).
|
||||
* - no_ui: (optional) A boolean specifying that users should not be allowed
|
||||
* to create fields and instances of this field type through the UI. Such
|
||||
* fields can only be created programmatically. Defaults to FALSE.
|
||||
*
|
||||
* @see hook_field_info_alter()
|
||||
*/
|
||||
function hook_field_info() {
|
||||
return array(
|
||||
'text' => array(
|
||||
'label' => t('Text'),
|
||||
'description' => t('This field stores varchar text in the database.'),
|
||||
'settings' => array('max_length' => 255),
|
||||
'instance_settings' => array('text_processing' => 0),
|
||||
'default_widget' => 'text_textfield',
|
||||
'default_formatter' => 'text_default',
|
||||
),
|
||||
'text_long' => array(
|
||||
'label' => t('Long text'),
|
||||
'description' => t('This field stores long text in the database.'),
|
||||
'settings' => array('max_length' => ''),
|
||||
'instance_settings' => array('text_processing' => 0),
|
||||
'default_widget' => 'text_textarea',
|
||||
'default_formatter' => 'text_default',
|
||||
),
|
||||
'text_with_summary' => array(
|
||||
'label' => t('Long text and summary'),
|
||||
'description' => t('This field stores long text in the database along with optional summary text.'),
|
||||
'settings' => array('max_length' => ''),
|
||||
'instance_settings' => array('text_processing' => 1, 'display_summary' => 0),
|
||||
'default_widget' => 'text_textarea_with_summary',
|
||||
'default_formatter' => 'text_summary_or_trimmed',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform alterations on Field API field types.
|
||||
*
|
||||
* @param $info
|
||||
* Array of information on field types exposed by hook_field_info()
|
||||
* implementations.
|
||||
* Array of information on field types as collected by the "field type" plugin
|
||||
* manager.
|
||||
*/
|
||||
function hook_field_info_alter(&$info) {
|
||||
// Add a setting to all field types.
|
||||
|
|
@ -206,466 +141,6 @@ function hook_field_info_alter(&$info) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the Field API schema for a field structure.
|
||||
*
|
||||
* This hook MUST be defined in .install for it to be detected during
|
||||
* installation and upgrade.
|
||||
*
|
||||
* @param $field
|
||||
* A field structure.
|
||||
*
|
||||
* @return
|
||||
* An associative array with the following keys:
|
||||
* - columns: An array of Schema API column specifications, keyed by column
|
||||
* name. This specifies what comprises a value for a given field. For
|
||||
* example, a value for a number field is simply 'value', while a value for
|
||||
* a formatted text field is the combination of 'value' and 'format'. It is
|
||||
* recommended to avoid having the column definitions depend on field
|
||||
* settings when possible. No assumptions should be made on how storage
|
||||
* engines internally use the original column name to structure their
|
||||
* storage.
|
||||
* - indexes: (optional) An array of Schema API index definitions. Only
|
||||
* columns that appear in the 'columns' array are allowed. Those indexes
|
||||
* will be used as default indexes. Individual field definitions can
|
||||
* specify additional indexes or modify, at their own risk, the indexes
|
||||
* specified by the field type. Some storage engines might not support
|
||||
* indexes.
|
||||
* - foreign keys: (optional) An array of Schema API foreign key definitions.
|
||||
* Note, however, that the field data is not necessarily stored in SQL.
|
||||
* Also, the possible usage is limited, as you cannot specify another field
|
||||
* as related, only existing SQL tables, such as {taxonomy_term_data}.
|
||||
*/
|
||||
function hook_field_schema($field) {
|
||||
if ($field['type'] == 'text_long') {
|
||||
$columns = array(
|
||||
'value' => array(
|
||||
'type' => 'text',
|
||||
'size' => 'big',
|
||||
'not null' => FALSE,
|
||||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
$columns = array(
|
||||
'value' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => $field['settings']['max_length'],
|
||||
'not null' => FALSE,
|
||||
),
|
||||
);
|
||||
}
|
||||
$columns += array(
|
||||
'format' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => FALSE,
|
||||
),
|
||||
);
|
||||
return array(
|
||||
'columns' => $columns,
|
||||
'indexes' => array(
|
||||
'format' => array('format'),
|
||||
),
|
||||
'foreign keys' => array(
|
||||
'format' => array(
|
||||
'table' => 'filter_format',
|
||||
'columns' => array('format' => 'format'),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define custom load behavior for this module's field types.
|
||||
*
|
||||
* Unlike most other field hooks, this hook operates on multiple entities. The
|
||||
* $entities, $instances and $items parameters are arrays keyed by entity ID.
|
||||
* For performance reasons, information for all available entity should be
|
||||
* loaded in a single query where possible.
|
||||
*
|
||||
* Note that the changes made to the field values get cached by the field cache
|
||||
* for subsequent loads. You should never use this hook to load fieldable
|
||||
* entities, since this is likely to cause infinite recursions when
|
||||
* hook_field_load() is run on those as well. Use
|
||||
* hook_field_formatter_prepare_view() instead.
|
||||
*
|
||||
* Make changes or additions to field values by altering the $items parameter by
|
||||
* reference. There is no return value.
|
||||
*
|
||||
* @param $entity_type
|
||||
* The type of $entity.
|
||||
* @param $entities
|
||||
* Array of entities being loaded, keyed by entity ID.
|
||||
* @param $field
|
||||
* The field structure for the operation.
|
||||
* @param $instances
|
||||
* Array of instance structures for $field for each entity, keyed by entity
|
||||
* ID.
|
||||
* @param $langcode
|
||||
* The language code associated with $items.
|
||||
* @param $items
|
||||
* Array of field values already loaded for the entities, keyed by entity ID.
|
||||
* Store your changes in this parameter (passed by reference).
|
||||
* @param $age
|
||||
* FIELD_LOAD_CURRENT to load the most recent revision for all fields, or
|
||||
* FIELD_LOAD_REVISION to load the version indicated by each entity.
|
||||
*/
|
||||
function hook_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) {
|
||||
// Sample code from text.module: precompute sanitized strings so they are
|
||||
// stored in the field cache.
|
||||
foreach ($entities as $id => $entity) {
|
||||
foreach ($items[$id] as $delta => $item) {
|
||||
// Only process items with a cacheable format, the rest will be handled
|
||||
// by formatters if needed.
|
||||
if (empty($instances[$id]['settings']['text_processing']) || filter_format_allowcache($item['format'])) {
|
||||
$items[$id][$delta]['safe_value'] = isset($item['value']) ? text_sanitize($instances[$id]['settings']['text_processing'], $langcode, $item, 'value') : '';
|
||||
if ($field['type'] == 'text_with_summary') {
|
||||
$items[$id][$delta]['safe_summary'] = isset($item['summary']) ? text_sanitize($instances[$id]['settings']['text_processing'], $langcode, $item, 'summary') : '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare field values prior to display.
|
||||
*
|
||||
* This hook is invoked before the field values are handed to formatters for
|
||||
* display, and runs before the formatters' own
|
||||
* hook_field_formatter_prepare_view().
|
||||
*
|
||||
* Unlike most other field hooks, this hook operates on multiple entities. The
|
||||
* $entities, $instances and $items parameters are arrays keyed by entity ID.
|
||||
* For performance reasons, information for all available entities should be
|
||||
* loaded in a single query where possible.
|
||||
*
|
||||
* Make changes or additions to field values by altering the $items parameter by
|
||||
* reference. There is no return value.
|
||||
*
|
||||
* @param $entity_type
|
||||
* The type of $entity.
|
||||
* @param $entities
|
||||
* Array of entities being displayed, keyed by entity ID.
|
||||
* @param $field
|
||||
* The field structure for the operation.
|
||||
* @param $instances
|
||||
* Array of instance structures for $field for each entity, keyed by entity
|
||||
* ID.
|
||||
* @param $langcode
|
||||
* The language associated with $items.
|
||||
* @param $items
|
||||
* $entity->{$field['field_name']}, or an empty array if unset.
|
||||
*/
|
||||
function hook_field_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items) {
|
||||
// Sample code from image.module: if there are no images specified at all,
|
||||
// use the default image.
|
||||
foreach ($entities as $id => $entity) {
|
||||
if (empty($items[$id]) && $field['settings']['default_image']) {
|
||||
if ($file = file_load($field['settings']['default_image'])) {
|
||||
$items[$id][0] = (array) $file + array(
|
||||
'is_default' => TRUE,
|
||||
'alt' => '',
|
||||
'title' => '',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate this module's field data.
|
||||
*
|
||||
* If there are validation problems, add to the $errors array (passed by
|
||||
* reference). There is no return value.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity for the operation.
|
||||
* @param $field
|
||||
* The field structure for the operation.
|
||||
* @param $instance
|
||||
* The instance structure for $field on $entity's bundle.
|
||||
* @param $langcode
|
||||
* The language associated with $items.
|
||||
* @param $items
|
||||
* $entity->{$field['field_name']}[$langcode], or an empty array if unset.
|
||||
* @param $errors
|
||||
* The array of errors (keyed by field name, language code, and delta) that
|
||||
* have already been reported for the entity. The function should add its
|
||||
* errors to this array. Each error is an associative array with the following
|
||||
* keys and values:
|
||||
* - error: An error code (should be a string prefixed with the module name).
|
||||
* - message: The human-readable message to be displayed.
|
||||
*/
|
||||
function hook_field_validate(\Drupal\Core\Entity\EntityInterface $entity = NULL, $field, $instance, $langcode, $items, &$errors) {
|
||||
foreach ($items as $delta => $item) {
|
||||
if (!empty($item['value'])) {
|
||||
if (!empty($field['settings']['max_length']) && drupal_strlen($item['value']) > $field['settings']['max_length']) {
|
||||
$errors[$field['field_name']][$langcode][$delta][] = array(
|
||||
'error' => 'text_max_length',
|
||||
'message' => t('%name: the value may not be longer than %max characters.', array('%name' => $instance['label'], '%max' => $field['settings']['max_length'])),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Define custom presave behavior for this module's field types.
|
||||
*
|
||||
* Make changes or additions to field values by altering the $items parameter by
|
||||
* reference. There is no return value.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity for the operation.
|
||||
* @param $field
|
||||
* The field structure for the operation.
|
||||
* @param $instance
|
||||
* The instance structure for $field on $entity's bundle.
|
||||
* @param $langcode
|
||||
* The language associated with $items.
|
||||
* @param $items
|
||||
* $entity->{$field['field_name']}[$langcode], or an empty array if unset.
|
||||
*/
|
||||
function hook_field_presave(\Drupal\Core\Entity\EntityInterface $entity, $field, $instance, $langcode, &$items) {
|
||||
if ($field['type'] == 'number_decimal') {
|
||||
// Let PHP round the value to ensure consistent behavior across storage
|
||||
// backends.
|
||||
foreach ($items as $delta => $item) {
|
||||
if (isset($item['value'])) {
|
||||
$items[$delta]['value'] = round($item['value'], $field['settings']['scale']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Define custom insert behavior for this module's field data.
|
||||
*
|
||||
* This hook is invoked from field_attach_insert() on the module that defines a
|
||||
* field, during the process of inserting an entity object (node, taxonomy term,
|
||||
* etc.). It is invoked just before the data for this field on the particular
|
||||
* entity object is inserted into field storage. Only field modules that are
|
||||
* storing or tracking information outside the standard field storage mechanism
|
||||
* need to implement this hook.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity for the operation.
|
||||
* @param $field
|
||||
* The field structure for the operation.
|
||||
* @param $instance
|
||||
* The instance structure for $field on $entity's bundle.
|
||||
* @param $langcode
|
||||
* The language associated with $items.
|
||||
* @param $items
|
||||
* $entity->{$field['field_name']}[$langcode], or an empty array if unset.
|
||||
*
|
||||
* @see hook_field_update()
|
||||
* @see hook_field_delete()
|
||||
*/
|
||||
function hook_field_insert(\Drupal\Core\Entity\EntityInterface $entity, $field, $instance, $langcode, &$items) {
|
||||
if (config('taxonomy.settings')->get('maintain_index_table') && $field['storage']['type'] == 'field_sql_storage' && $entity->entityType() == 'node' && $entity->status) {
|
||||
$query = db_insert('taxonomy_index')->fields(array('nid', 'tid', 'sticky', 'created', ));
|
||||
foreach ($items as $item) {
|
||||
$query->values(array(
|
||||
'nid' => $entity->nid,
|
||||
'tid' => $item['tid'],
|
||||
'sticky' => $entity->sticky,
|
||||
'created' => $entity->created,
|
||||
));
|
||||
}
|
||||
$query->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Define custom update behavior for this module's field data.
|
||||
*
|
||||
* This hook is invoked from field_attach_update() on the module that defines a
|
||||
* field, during the process of updating an entity object (node, taxonomy term,
|
||||
* etc.). It is invoked just before the data for this field on the particular
|
||||
* entity object is updated into field storage. Only field modules that are
|
||||
* storing or tracking information outside the standard field storage mechanism
|
||||
* need to implement this hook.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity for the operation.
|
||||
* @param $field
|
||||
* The field structure for the operation.
|
||||
* @param $instance
|
||||
* The instance structure for $field on $entity's bundle.
|
||||
* @param $langcode
|
||||
* The language associated with $items.
|
||||
* @param $items
|
||||
* $entity->{$field['field_name']}[$langcode], or an empty array if unset.
|
||||
*
|
||||
* @see hook_field_insert()
|
||||
* @see hook_field_delete()
|
||||
*/
|
||||
function hook_field_update(\Drupal\Core\Entity\EntityInterface $entity, $field, $instance, $langcode, &$items) {
|
||||
if (config('taxonomy.settings')->get('maintain_index_table') && $field['storage']['type'] == 'field_sql_storage' && $entity->entityType() == 'node') {
|
||||
$first_call = &drupal_static(__FUNCTION__, array());
|
||||
|
||||
// We don't maintain data for old revisions, so clear all previous values
|
||||
// from the table. Since this hook runs once per field, per object, make
|
||||
// sure we only wipe values once.
|
||||
if (!isset($first_call[$entity->nid])) {
|
||||
$first_call[$entity->nid] = FALSE;
|
||||
db_delete('taxonomy_index')->condition('nid', $entity->nid)->execute();
|
||||
}
|
||||
// Only save data to the table if the node is published.
|
||||
if ($entity->status) {
|
||||
$query = db_insert('taxonomy_index')->fields(array('nid', 'tid', 'sticky', 'created'));
|
||||
foreach ($items as $item) {
|
||||
$query->values(array(
|
||||
'nid' => $entity->nid,
|
||||
'tid' => $item['tid'],
|
||||
'sticky' => $entity->sticky,
|
||||
'created' => $entity->created,
|
||||
));
|
||||
}
|
||||
$query->execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the storage information for a field.
|
||||
*
|
||||
* This is invoked on the field's storage module when updating the field,
|
||||
* before the new definition is saved to the database. The field storage module
|
||||
* should update its storage tables according to the new field definition. If
|
||||
* there is a problem, the field storage module should throw an exception.
|
||||
*
|
||||
* @param $field
|
||||
* The updated field structure to be saved.
|
||||
* @param $prior_field
|
||||
* The previously-saved field structure.
|
||||
*/
|
||||
function hook_field_storage_update_field($field, $prior_field) {
|
||||
if (!$field->hasData()) {
|
||||
// There is no data. Re-create the tables completely.
|
||||
$prior_schema = _field_sql_storage_schema($prior_field);
|
||||
foreach ($prior_schema as $name => $table) {
|
||||
db_drop_table($name, $table);
|
||||
}
|
||||
$schema = _field_sql_storage_schema($field);
|
||||
foreach ($schema as $name => $table) {
|
||||
db_create_table($name, $table);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// There is data. See field_sql_storage_field_storage_update_field() for
|
||||
// an example of what to do to modify the schema in place, preserving the
|
||||
// old data as much as possible.
|
||||
}
|
||||
drupal_get_schema(NULL, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define custom delete behavior for this module's field data.
|
||||
*
|
||||
* This hook is invoked from field_attach_delete() on the module that defines a
|
||||
* field, during the process of deleting an entity object (node, taxonomy term,
|
||||
* etc.). It is invoked just before the data for this field on the particular
|
||||
* entity object is deleted from field storage. Only field modules that are
|
||||
* storing or tracking information outside the standard field storage mechanism
|
||||
* need to implement this hook.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity for the operation.
|
||||
* @param $field
|
||||
* The field structure for the operation.
|
||||
* @param $instance
|
||||
* The instance structure for $field on $entity's bundle.
|
||||
* @param $langcode
|
||||
* The language associated with $items.
|
||||
* @param $items
|
||||
* $entity->{$field['field_name']}[$langcode], or an empty array if unset.
|
||||
*
|
||||
* @see hook_field_insert()
|
||||
* @see hook_field_update()
|
||||
*/
|
||||
function hook_field_delete(\Drupal\Core\Entity\EntityInterface $entity, $field, $instance, $langcode, &$items) {
|
||||
// Delete all file usages within this entity.
|
||||
foreach ($items as $delta => $item) {
|
||||
file_usage()->delete(file_load($item['fid']), 'file', $entity->entityType(), $entity->id(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Define custom revision delete behavior for this module's field types.
|
||||
*
|
||||
* This hook is invoked just before the data is deleted from field storage in
|
||||
* field_attach_delete_revision(), and will only be called for fieldable types
|
||||
* that are versioned.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity for the operation.
|
||||
* @param $field
|
||||
* The field structure for the operation.
|
||||
* @param $instance
|
||||
* The instance structure for $field on $entity's bundle.
|
||||
* @param $langcode
|
||||
* The language associated with $items.
|
||||
* @param $items
|
||||
* $entity->{$field['field_name']}[$langcode], or an empty array if unset.
|
||||
*/
|
||||
function hook_field_delete_revision(\Drupal\Core\Entity\EntityInterface $entity, $field, $instance, $langcode, &$items) {
|
||||
foreach ($items as $delta => $item) {
|
||||
// Decrement the file usage count by 1.
|
||||
file_usage()->delete(file_load($item['fid']), 'file', $entity->entityType(), $entity->id());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Define custom prepare_translation behavior for this module's field types.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity for the operation.
|
||||
* @param $field
|
||||
* The field structure for the operation.
|
||||
* @param $instance
|
||||
* The instance structure for $field on $entity's bundle.
|
||||
* @param $langcode
|
||||
* The language associated with $items.
|
||||
* @param $items
|
||||
* $entity->{$field['field_name']}[$langcode], or an empty array if unset.
|
||||
* @param $source_entity
|
||||
* The source entity from which field values are being copied.
|
||||
* @param $source_langcode
|
||||
* The source language from which field values are being copied.
|
||||
*/
|
||||
function hook_field_prepare_translation(\Drupal\Core\Entity\EntityInterface $entity, $field, $instance, $langcode, &$items, $source_entity, $source_langcode) {
|
||||
// If the translating user is not permitted to use the assigned text format,
|
||||
// we must not expose the source values.
|
||||
$field_name = $field['field_name'];
|
||||
$formats = filter_formats();
|
||||
$format_id = $source_entity->{$field_name}[$source_langcode][0]['format'];
|
||||
if (!filter_access($formats[$format_id])) {
|
||||
$items = array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Define what constitutes an empty item for a field type.
|
||||
*
|
||||
* @param array $item
|
||||
* An item that may or may not be empty.
|
||||
* @param string $field_type
|
||||
* The field type to which $item belongs.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the field type considers $item not to contain any data; FALSE
|
||||
* otherwise.
|
||||
*/
|
||||
function hook_field_is_empty($item, $field_type) {
|
||||
if (empty($item['value']) && (string) $item['value'] !== '0') {
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "defgroup field_types".
|
||||
*/
|
||||
|
|
@ -875,25 +350,6 @@ function hook_field_attach_load($entity_type, $entities, $age, $options) {
|
|||
// @todo Needs function body.
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on field_attach_validate().
|
||||
*
|
||||
* This hook is invoked after the field module has performed the operation.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity with fields to validate.
|
||||
* @param $errors
|
||||
* The array of errors (keyed by field name, language code, and delta) that
|
||||
* have already been reported for the entity. The function should add its
|
||||
* errors to this array. Each error is an associative array with the following
|
||||
* keys and values:
|
||||
* - error: An error code (should be a string prefixed with the module name).
|
||||
* - message: The human-readable message to be displayed.
|
||||
*/
|
||||
function hook_field_attach_validate(\Drupal\Core\Entity\EntityInterface $entity, &$errors) {
|
||||
// @todo Needs function body.
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on field_attach_extract_form_values().
|
||||
*
|
||||
|
|
@ -919,42 +375,6 @@ function hook_field_attach_extract_form_values(\Drupal\Core\Entity\EntityInterfa
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on field_attach_presave().
|
||||
*
|
||||
* This hook is invoked after the field module has performed the operation.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* the entity with fields to process.
|
||||
*/
|
||||
function hook_field_attach_presave(\Drupal\Core\Entity\EntityInterface $entity) {
|
||||
// @todo Needs function body.
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on field_attach_insert().
|
||||
*
|
||||
* This hook is invoked after the field module has performed the operation.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* the entity with fields to process.
|
||||
*/
|
||||
function hook_field_attach_insert(\Drupal\Core\Entity\EntityInterface $entity) {
|
||||
// @todo Needs function body.
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on field_attach_update().
|
||||
*
|
||||
* This hook is invoked after the field module has performed the operation.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* the entity with fields to process.
|
||||
*/
|
||||
function hook_field_attach_update(\Drupal\Core\Entity\EntityInterface $entity) {
|
||||
// @todo Needs function body.
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter field_attach_preprocess() variables.
|
||||
*
|
||||
|
|
@ -973,30 +393,6 @@ function hook_field_attach_preprocess_alter(&$variables, $context) {
|
|||
// @todo Needs function body.
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on field_attach_delete().
|
||||
*
|
||||
* This hook is invoked after the field module has performed the operation.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* the entity with fields to process.
|
||||
*/
|
||||
function hook_field_attach_delete(\Drupal\Core\Entity\EntityInterface $entity) {
|
||||
// @todo Needs function body.
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on field_attach_delete_revision().
|
||||
*
|
||||
* This hook is invoked after the field module has performed the operation.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* the entity with fields to process.
|
||||
*/
|
||||
function hook_field_attach_delete_revision(\Drupal\Core\Entity\EntityInterface $entity) {
|
||||
// @todo Needs function body.
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on field_purge_data().
|
||||
*
|
||||
|
|
@ -1060,25 +456,6 @@ function hook_field_attach_view_alter(&$output, $context) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform alterations on field_attach_prepare_translation().
|
||||
*
|
||||
* This hook is invoked after the field module has performed the operation.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity being prepared for translation.
|
||||
* @param $context
|
||||
* An associative array containing:
|
||||
* - langcode: The language the entity will be translated to.
|
||||
* - source_entity: The entity holding the field values to be translated.
|
||||
* - source_langcode: The source language from which to translate.
|
||||
*/
|
||||
function hook_field_attach_prepare_translation_alter(\Drupal\Core\Entity\EntityInterface $entity, $context) {
|
||||
if ($entity->entityType() == 'custom_entity_type') {
|
||||
$entity->custom_field = $context['source_entity']->custom_field;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform alterations on field_language() values.
|
||||
*
|
||||
|
|
@ -1594,6 +971,39 @@ function hook_field_storage_create_field($field) {
|
|||
drupal_get_schema(NULL, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the storage information for a field.
|
||||
*
|
||||
* This is invoked on the field's storage module when updating the field,
|
||||
* before the new definition is saved to the database. The field storage module
|
||||
* should update its storage tables according to the new field definition. If
|
||||
* there is a problem, the field storage module should throw an exception.
|
||||
*
|
||||
* @param $field
|
||||
* The updated field structure to be saved.
|
||||
* @param $prior_field
|
||||
* The previously-saved field structure.
|
||||
*/
|
||||
function hook_field_storage_update_field($field, $prior_field) {
|
||||
if (!$field->hasData()) {
|
||||
// There is no data. Re-create the tables completely.
|
||||
$prior_schema = _field_sql_storage_schema($prior_field);
|
||||
foreach ($prior_schema as $name => $table) {
|
||||
db_drop_table($name, $table);
|
||||
}
|
||||
$schema = _field_sql_storage_schema($field);
|
||||
foreach ($schema as $name => $table) {
|
||||
db_create_table($name, $table);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// There is data. See field_sql_storage_field_storage_update_field() for
|
||||
// an example of what to do to modify the schema in place, preserving the
|
||||
// old data as much as possible.
|
||||
}
|
||||
drupal_get_schema(NULL, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on deletion of a field.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -5,10 +5,11 @@
|
|||
* Field attach API, allowing entities (nodes, users, ...) to be 'fieldable'.
|
||||
*/
|
||||
|
||||
use Drupal\field\FieldValidationException;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityNG;
|
||||
use Drupal\entity\Plugin\Core\Entity\EntityDisplay;
|
||||
use Drupal\entity\Plugin\Core\Entity\EntityFormDisplay;
|
||||
use Drupal\Core\Language\Language;
|
||||
|
||||
/**
|
||||
* @defgroup field_storage Field Storage API
|
||||
|
|
@ -116,9 +117,6 @@ const FIELD_STORAGE_INSERT = 'insert';
|
|||
/**
|
||||
* Invokes a method on all the fields of a given entity.
|
||||
*
|
||||
* @todo Remove _field_invoke() and friends when field types and formatters are
|
||||
* turned into plugins.
|
||||
*
|
||||
* @param string $method
|
||||
* The name of the method to invoke.
|
||||
* @param callable $target_function
|
||||
|
|
@ -336,332 +334,22 @@ function field_invoke_method_multiple($method, $target_function, array $entities
|
|||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke a field hook.
|
||||
*
|
||||
* @param $op
|
||||
* Possible operations include:
|
||||
* - form
|
||||
* - validate
|
||||
* - presave
|
||||
* - insert
|
||||
* - update
|
||||
* - delete
|
||||
* - delete revision
|
||||
* - view
|
||||
* - prepare translation
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity object.
|
||||
* @param $a
|
||||
* - The $form in the 'form' operation.
|
||||
* - The value of $view_mode in the 'view' operation.
|
||||
* - Otherwise NULL.
|
||||
* @param $b
|
||||
* - The $form_state in the 'submit' operation.
|
||||
* - Otherwise NULL.
|
||||
* @param $options
|
||||
* An associative array of additional options, with the following keys:
|
||||
* - 'field_name': The name of the field whose operation should be
|
||||
* invoked. By default, the operation is invoked on all the fields
|
||||
* in the entity's bundle. NOTE: This option is not compatible with
|
||||
* the 'deleted' option; the 'field_id' option should be used
|
||||
* instead.
|
||||
* - 'field_id': The id of the field whose operation should be
|
||||
* invoked. By default, the operation is invoked on all the fields
|
||||
* in the entity's' bundles.
|
||||
* - 'default': A boolean value, specifying which implementation of
|
||||
* the operation should be invoked.
|
||||
* - if FALSE (default), the field types implementation of the operation
|
||||
* will be invoked (hook_field_[op])
|
||||
* - If TRUE, the default field implementation of the field operation
|
||||
* will be invoked (field_default_[op])
|
||||
* Internal use only. Do not explicitely set to TRUE, but use
|
||||
* _field_invoke_default() instead.
|
||||
* - 'deleted': If TRUE, the function will operate on deleted fields
|
||||
* as well as non-deleted fields. If unset or FALSE, only
|
||||
* non-deleted fields are operated on.
|
||||
* - 'langcode': A language code or an array of language codes keyed by field
|
||||
* name. It will be used to narrow down to a single value the available
|
||||
* languages to act on.
|
||||
*/
|
||||
function _field_invoke($op, EntityInterface $entity, &$a = NULL, &$b = NULL, $options = array()) {
|
||||
// Merge default options.
|
||||
$default_options = array(
|
||||
'default' => FALSE,
|
||||
'deleted' => FALSE,
|
||||
'langcode' => NULL,
|
||||
);
|
||||
$options += $default_options;
|
||||
|
||||
// Determine the list of instances to iterate on.
|
||||
$instances = _field_invoke_get_instances($entity->entityType(), $entity->bundle(), $options);
|
||||
|
||||
// Iterate through the instances and collect results.
|
||||
$return = array();
|
||||
foreach ($instances as $instance) {
|
||||
// field_info_field() is not available for deleted fields, so use
|
||||
// field_info_field_by_id().
|
||||
$field = field_info_field_by_id($instance['field_id']);
|
||||
$field_name = $field['field_name'];
|
||||
$function = $options['default'] ? 'field_default_' . $op : $field['module'] . '_field_' . $op;
|
||||
if (function_exists($function)) {
|
||||
// Determine the list of languages to iterate on.
|
||||
$available_langcodes = field_available_languages($entity->entityType(), $field);
|
||||
$langcodes = _field_language_suggestion($available_langcodes, $options['langcode'], $field_name);
|
||||
|
||||
foreach ($langcodes as $langcode) {
|
||||
$items = isset($entity->{$field_name}[$langcode]) ? $entity->{$field_name}[$langcode] : array();
|
||||
$result = $function($entity, $field, $instance, $langcode, $items, $a, $b);
|
||||
if (isset($result)) {
|
||||
// For hooks with array results, we merge results together.
|
||||
// For hooks with scalar results, we collect results in an array.
|
||||
if (is_array($result)) {
|
||||
$return = array_merge($return, $result);
|
||||
}
|
||||
else {
|
||||
$return[] = $result;
|
||||
}
|
||||
}
|
||||
|
||||
// Populate $items back in the field values, but avoid replacing missing
|
||||
// fields with an empty array (those are not equivalent on update).
|
||||
if ($items !== array() || isset($entity->{$field_name}[$langcode])) {
|
||||
$entity->{$field_name}[$langcode] = $items;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes a field hook across fields on multiple entities.
|
||||
*
|
||||
* @param $op
|
||||
* Possible operations include:
|
||||
* - load
|
||||
* - prepare_view
|
||||
* For all other operations, use _field_invoke() / field_invoke_default()
|
||||
* instead.
|
||||
* @param $entity_type
|
||||
* The type of entities in $entities; e.g. 'node' or 'user'.
|
||||
* @param $entities
|
||||
* An array of entities, keyed by entity ID.
|
||||
* @param $a
|
||||
* - The $age parameter in the 'load' operation.
|
||||
* - Otherwise NULL.
|
||||
* @param $b
|
||||
* Currently always NULL.
|
||||
* @param $options
|
||||
* An associative array of additional options, with the following keys:
|
||||
* - field_name: The name of the field whose operation should be invoked. By
|
||||
* default, the operation is invoked on all the fields in the entity's
|
||||
* bundle. NOTE: This option is not compatible with the 'deleted' option;
|
||||
* the 'field_id' option should be used instead.
|
||||
* - field_id: The ID of the field whose operation should be invoked. By
|
||||
* default, the operation is invoked on all the fields in the entity's
|
||||
* bundles.
|
||||
* - default: A boolean value, specifying which implementation of the
|
||||
* operation should be invoked.
|
||||
* - if FALSE (default), the field types implementation of the operation
|
||||
* will be invoked (hook_field_[op])
|
||||
* - If TRUE, the default field implementation of the field operation will
|
||||
* be invoked (field_default_[op])
|
||||
* Internal use only. Do not explicitely set to TRUE, but use
|
||||
* _field_invoke_multiple_default() instead.
|
||||
* - deleted: If TRUE, the function will operate on deleted fields as well as
|
||||
* non-deleted fields. If unset or FALSE, only non-deleted fields are
|
||||
* operated on.
|
||||
* - langcode: A language code or an array of arrays of language codes keyed
|
||||
* by entity ID and field name. It will be used to narrow down to a single
|
||||
* value the available languages to act on.
|
||||
*
|
||||
* @return
|
||||
* An array of returned values keyed by entity ID.
|
||||
*/
|
||||
function _field_invoke_multiple($op, $entity_type, $entities, &$a = NULL, &$b = NULL, $options = array()) {
|
||||
// Merge default options.
|
||||
$default_options = array(
|
||||
'default' => FALSE,
|
||||
'deleted' => FALSE,
|
||||
'langcode' => NULL,
|
||||
);
|
||||
$options += $default_options;
|
||||
|
||||
$fields = array();
|
||||
$grouped_instances = array();
|
||||
$grouped_entities = array();
|
||||
$grouped_items = array();
|
||||
$return = array();
|
||||
|
||||
// Go through the entities and collect the fields on which the hook should be
|
||||
// invoked.
|
||||
//
|
||||
// We group fields by ID, not by name, because this function can operate on
|
||||
// deleted fields which may have non-unique names. However, entities can only
|
||||
// contain data for a single field for each name, even if that field
|
||||
// is deleted, so we reference field data via the
|
||||
// $entity->$field_name property.
|
||||
foreach ($entities as $entity) {
|
||||
// Determine the list of instances to iterate on.
|
||||
$instances = _field_invoke_get_instances($entity_type, $entity->bundle(), $options);
|
||||
$id = $entity->id();
|
||||
|
||||
foreach ($instances as $instance) {
|
||||
$field_id = $instance['field_id'];
|
||||
$field_name = $instance['field_name'];
|
||||
$field = field_info_field_by_id($field_id);
|
||||
$function = $options['default'] ? 'field_default_' . $op : $field['module'] . '_field_' . $op;
|
||||
if (function_exists($function)) {
|
||||
// Add the field to the list of fields to invoke the hook on.
|
||||
if (!isset($fields[$field_id])) {
|
||||
$fields[$field_id] = $field;
|
||||
}
|
||||
// Extract the field values into a separate variable, easily accessed
|
||||
// by hook implementations.
|
||||
// Unless a language code suggestion is provided we iterate on all the
|
||||
// available language codes.
|
||||
$available_langcodes = field_available_languages($entity_type, $field);
|
||||
$langcode = !empty($options['langcode'][$id]) ? $options['langcode'][$id] : $options['langcode'];
|
||||
$langcodes = _field_language_suggestion($available_langcodes, $langcode, $field_name);
|
||||
foreach ($langcodes as $langcode) {
|
||||
$grouped_items[$field_id][$langcode][$id] = isset($entity->{$field_name}[$langcode]) ? $entity->{$field_name}[$langcode] : array();
|
||||
// Group the instances and entities corresponding to the current
|
||||
// field.
|
||||
$grouped_instances[$field_id][$langcode][$id] = $instance;
|
||||
$grouped_entities[$field_id][$langcode][$id] = $entities[$id];
|
||||
}
|
||||
}
|
||||
}
|
||||
// Initialize the return value for each entity.
|
||||
$return[$id] = array();
|
||||
}
|
||||
|
||||
// For each field, invoke the field hook and collect results.
|
||||
foreach ($fields as $field_id => $field) {
|
||||
$field_name = $field['field_name'];
|
||||
$function = $options['default'] ? 'field_default_' . $op : $field['module'] . '_field_' . $op;
|
||||
// Iterate over all the field translations.
|
||||
foreach ($grouped_items[$field_id] as $langcode => &$items) {
|
||||
$entities = $grouped_entities[$field_id][$langcode];
|
||||
$instances = $grouped_instances[$field_id][$langcode];
|
||||
$results = $function($entity_type, $entities, $field, $instances, $langcode, $items, $a, $b);
|
||||
if (isset($results)) {
|
||||
// Collect results by entity.
|
||||
// For hooks with array results, we merge results together.
|
||||
// For hooks with scalar results, we collect results in an array.
|
||||
foreach ($results as $id => $result) {
|
||||
if (is_array($result)) {
|
||||
$return[$id] = array_merge($return[$id], $result);
|
||||
}
|
||||
else {
|
||||
$return[$id][] = $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Populate field values back in the entities, but avoid replacing missing
|
||||
// fields with an empty array (those are not equivalent on update).
|
||||
foreach ($grouped_entities[$field_id] as $langcode => $entities) {
|
||||
foreach ($entities as $id => $entity) {
|
||||
if ($grouped_items[$field_id][$langcode][$id] !== array() || isset($entity->{$field_name}[$langcode])) {
|
||||
$entity->{$field_name}[$langcode] = $grouped_items[$field_id][$langcode][$id];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke field.module's version of a field hook.
|
||||
*
|
||||
* This function invokes the field_default_[op]() function.
|
||||
* Use _field_invoke() to invoke the field type implementation,
|
||||
* hook_field_[op]().
|
||||
*
|
||||
* @see _field_invoke()
|
||||
*/
|
||||
function _field_invoke_default($op, EntityInterface $entity, &$a = NULL, &$b = NULL, $options = array()) {
|
||||
$options['default'] = TRUE;
|
||||
return _field_invoke($op, $entity, $a, $b, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke field.module's version of a field hook on multiple entities.
|
||||
*
|
||||
* This function invokes the field_default_[op]() function.
|
||||
* Use _field_invoke_multiple() to invoke the field type implementation,
|
||||
* hook_field_[op]().
|
||||
*
|
||||
* @param $op
|
||||
* Possible operations include:
|
||||
* - load
|
||||
* - prepare_view
|
||||
* For all other operations, use _field_invoke() / field_invoke_default()
|
||||
* instead.
|
||||
* @param $entity_type
|
||||
* The type of entities in $entities; e.g. 'node' or 'user'.
|
||||
* @param $entities
|
||||
* An array of entities, keyed by entity ID.
|
||||
* @param $a
|
||||
* - The $age parameter in the 'load' operation.
|
||||
* - Otherwise NULL.
|
||||
* @param $b
|
||||
* Currently always NULL.
|
||||
* @param $options
|
||||
* An associative array of additional options, with the following keys:
|
||||
* - field_name: The name of the field whose operation should be invoked. By
|
||||
* default, the operation is invoked on all the fields in the entity's
|
||||
* bundle. NOTE: This option is not compatible with the 'deleted' option;
|
||||
* the 'field_id' option should be used instead.
|
||||
* - field_id: The ID of the field whose operation should be invoked. By
|
||||
* default, the operation is invoked on all the fields in the entity's
|
||||
* bundles.
|
||||
* - default': A boolean value, specifying which implementation of the
|
||||
* operation should be invoked.
|
||||
* - if FALSE (default), the field types implementation of the operation
|
||||
* will be invoked (hook_field_[op])
|
||||
* - If TRUE, the default field implementation of the field operation will
|
||||
* be invoked (field_default_[op])
|
||||
* Internal use only. Do not explicitely set to TRUE, but use
|
||||
* _field_invoke_multiple_default() instead.
|
||||
* - deleted: If TRUE, the function will operate on deleted fields as well as
|
||||
* non-deleted fields. If unset or FALSE, only non-deleted fields are
|
||||
* operated on.
|
||||
* - language: A language code or an array of arrays of language codes keyed
|
||||
* by entity ID and field name. It will be used to narrow down to a single
|
||||
* value the available languages to act on.
|
||||
*
|
||||
* @return
|
||||
* An array of returned values keyed by entity ID.
|
||||
*
|
||||
* @see _field_invoke_multiple()
|
||||
*/
|
||||
function _field_invoke_multiple_default($op, $entity_type, $entities, &$a = NULL, &$b = NULL, $options = array()) {
|
||||
$options['default'] = TRUE;
|
||||
return _field_invoke_multiple($op, $entity_type, $entities, $a, $b, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of instances to operate on.
|
||||
*
|
||||
* Helper for _field_invoke().
|
||||
* Helper for field_invoke_method().
|
||||
*
|
||||
* @param $entity_type
|
||||
* The entity type.
|
||||
* @param $bundle
|
||||
* The bundle name.
|
||||
* @param $options
|
||||
* An associative array of options, as provided to _field_invoke(). Only the
|
||||
* following keys are considered:
|
||||
* An associative array of options, as provided to field_invoke_method(). Only
|
||||
* the following keys are considered:
|
||||
* - deleted
|
||||
* - field_name
|
||||
* - field_id
|
||||
* See _field_invoke() for details.
|
||||
* See field_invoke_method() for details.
|
||||
*
|
||||
* @return
|
||||
* The array of selected instance definitions.
|
||||
|
|
@ -732,8 +420,7 @@ function _field_invoke_widget_target($form_display) {
|
|||
* appear within the same $form element, or within the same '#parents' space.
|
||||
*
|
||||
* For each call to field_attach_form(), field values are processed by calling
|
||||
* field_attach_form_validate() and field_attach_extract_form_values() on the
|
||||
* same $form element.
|
||||
* field_attach_extract_form_values() on the same $form element.
|
||||
*
|
||||
* Sample resulting structure in $form:
|
||||
* @code
|
||||
|
|
@ -872,28 +559,30 @@ function field_attach_form(EntityInterface $entity, &$form, &$form_state, $langc
|
|||
* FIELD_LOAD_REVISION.
|
||||
* @param $options
|
||||
* An associative array of additional options, with the following keys:
|
||||
* - field_id: The field ID that should be loaded, instead of loading all
|
||||
* fields, for each entity. Note that returned entities may contain data for
|
||||
* other fields, for example if they are read from a cache.
|
||||
* - deleted: If TRUE, the function will operate on deleted fields as well as
|
||||
* non-deleted fields. If unset or FALSE, only non-deleted fields are
|
||||
* operated on.
|
||||
* - instance: A field instance entity, If provided, only values for the
|
||||
* corresponding field will be loaded, and no cache is written. This
|
||||
* option is only supported when all $entities are within the same bundle.
|
||||
*/
|
||||
function field_attach_load($entity_type, $entities, $age = FIELD_LOAD_CURRENT, $options = array()) {
|
||||
$load_current = $age == FIELD_LOAD_CURRENT;
|
||||
$load_deleted = !empty($options['instance']->deleted);
|
||||
|
||||
// Merge default options.
|
||||
$default_options = array(
|
||||
'deleted' => FALSE,
|
||||
$options += array('instance' => NULL);
|
||||
// Set options for hook invocations.
|
||||
$hook_options = array(
|
||||
'deleted' => $load_deleted,
|
||||
);
|
||||
$options += $default_options;
|
||||
if ($options['instance']) {
|
||||
$hook_options['field_id'] = $options['instance']->field_uuid;
|
||||
}
|
||||
|
||||
$info = entity_get_info($entity_type);
|
||||
// Only the most current revision of non-deleted fields for cacheable entity
|
||||
// types can be cached.
|
||||
$cache_read = $load_current && $info['field_cache'] && empty($options['deleted']);
|
||||
$cache_read = $load_current && $info['field_cache'] && !$load_deleted;
|
||||
// In addition, do not write to the cache when loading a single field.
|
||||
$cache_write = $cache_read && !isset($options['field_id']);
|
||||
$cache_write = $cache_read && !isset($options['instance']);
|
||||
|
||||
if (empty($entities)) {
|
||||
return;
|
||||
|
|
@ -934,7 +623,7 @@ function field_attach_load($entity_type, $entities, $age = FIELD_LOAD_CURRENT, $
|
|||
// The invoke order is:
|
||||
// - hook_field_storage_pre_load()
|
||||
// - storage backend's hook_field_storage_load()
|
||||
// - field-type module's hook_field_load()
|
||||
// - Field class's prepareCache() method.
|
||||
// - hook_field_attach_load()
|
||||
|
||||
// Invoke hook_field_storage_pre_load(): let any module load field
|
||||
|
|
@ -942,28 +631,31 @@ function field_attach_load($entity_type, $entities, $age = FIELD_LOAD_CURRENT, $
|
|||
$skip_fields = array();
|
||||
foreach (module_implements('field_storage_pre_load') as $module) {
|
||||
$function = $module . '_field_storage_pre_load';
|
||||
$function($entity_type, $queried_entities, $age, $skip_fields, $options);
|
||||
$function($entity_type, $queried_entities, $age, $skip_fields, $hook_options);
|
||||
}
|
||||
|
||||
$instances = array();
|
||||
|
||||
// Collect the storage backends used by the remaining fields in the entities.
|
||||
$storages = array();
|
||||
foreach ($queried_entities as $entity) {
|
||||
$instances = _field_invoke_get_instances($entity_type, $entity->bundle(), $options);
|
||||
$id = $entity->id();
|
||||
$vid = $entity->getRevisionId();
|
||||
|
||||
// Determine the list of field instances to work on.
|
||||
if ($options['instance']) {
|
||||
$instances = array($options['instance']);
|
||||
}
|
||||
else {
|
||||
$instances = field_info_instances($entity_type, $entity->bundle());
|
||||
}
|
||||
|
||||
foreach ($instances as $instance) {
|
||||
$field_name = $instance['field_name'];
|
||||
$field_id = $instance['field_id'];
|
||||
// Make sure all fields are present at least as empty arrays.
|
||||
$field = $instance->getField();
|
||||
$field_name = $field->id();
|
||||
if (!isset($queried_entities[$id]->{$field_name})) {
|
||||
$queried_entities[$id]->{$field_name} = array();
|
||||
}
|
||||
// Collect the storage backend if the field has not been loaded yet.
|
||||
if (!isset($skip_fields[$field_id])) {
|
||||
$field = field_info_field_by_id($field_id);
|
||||
$storages[$field['storage']['type']][$field_id][] = $load_current ? $id : $vid;
|
||||
if (!isset($skip_fields[$field->uuid])) {
|
||||
$storages[$field->storage['type']][$field->uuid][] = $load_current ? $id : $vid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -971,12 +663,36 @@ function field_attach_load($entity_type, $entities, $age = FIELD_LOAD_CURRENT, $
|
|||
// Invoke hook_field_storage_load() on the relevant storage backends.
|
||||
foreach ($storages as $storage => $fields) {
|
||||
$storage_info = field_info_storage_types($storage);
|
||||
module_invoke($storage_info['module'], 'field_storage_load', $entity_type, $queried_entities, $age, $fields, $options);
|
||||
module_invoke($storage_info['module'], 'field_storage_load', $entity_type, $queried_entities, $age, $fields, $hook_options);
|
||||
}
|
||||
|
||||
// Invoke field-type module's hook_field_load().
|
||||
$null = NULL;
|
||||
_field_invoke_multiple('load', $entity_type, $queried_entities, $age, $null, $options);
|
||||
// Invoke the field type's prepareCache() method.
|
||||
if (empty($options['instance'])) {
|
||||
foreach ($queried_entities as $entity) {
|
||||
\Drupal::entityManager()
|
||||
->getStorageController($entity_type)
|
||||
->invokeFieldItemPrepareCache($entity);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Do not rely on invokeFieldItemPrepareCache(), which only works on
|
||||
// fields listed in getFieldDefinitions(), and will fail if we are loading
|
||||
// values for a deleted field. Instead, generate FieldItem objects
|
||||
// directly, and call their prepareCache() method.
|
||||
foreach ($queried_entities as $entity) {
|
||||
$field = $options['instance']->getField();
|
||||
$field_name = $field->id();
|
||||
// Call the prepareCache() method on each item.
|
||||
foreach ($entity->{$field_name} as $langcode => $values) {
|
||||
$definition = _field_generate_entity_field_definition($field, $options['instance']);
|
||||
$items = \Drupal::typedData()->create($definition, $values, $field_name, $entity);
|
||||
foreach ($items as $item) {
|
||||
$item->prepareCache();
|
||||
}
|
||||
$entity->{$field_name}[$langcode] = $items->getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Invoke hook_field_attach_load(): let other modules act on loading the
|
||||
// entity.
|
||||
|
|
@ -1022,46 +738,6 @@ function field_attach_load_revision($entity_type, $entities, $options = array())
|
|||
return field_attach_load($entity_type, $entities, FIELD_LOAD_REVISION, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs field validation against the field data in an entity.
|
||||
*
|
||||
* This function does not perform field widget validation on form submissions.
|
||||
* It is intended to be called during API save operations. Use
|
||||
* field_attach_form_validate() to validate form submissions.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity with fields to validate.
|
||||
* @throws Drupal\field\FieldValidationException
|
||||
* If validation errors are found, a FieldValidationException is thrown. The
|
||||
* 'errors' property contains the array of errors, keyed by field name,
|
||||
* language and delta.
|
||||
* @param array $options
|
||||
* An associative array of additional options. See field_invoke_method() for
|
||||
* details.
|
||||
*/
|
||||
function field_attach_validate(EntityInterface $entity, array $options = array()) {
|
||||
// Ensure we are working with a BC mode entity.
|
||||
$entity = $entity->getBCEntity();
|
||||
|
||||
$errors = array();
|
||||
// Check generic, field-type-agnostic errors first.
|
||||
$null = NULL;
|
||||
_field_invoke_default('validate', $entity, $errors, $null, $options);
|
||||
// Check field-type specific errors.
|
||||
_field_invoke('validate', $entity, $errors, $null, $options);
|
||||
|
||||
// Let other modules validate the entity.
|
||||
// Avoid module_invoke_all() to let $errors be taken by reference.
|
||||
foreach (module_implements('field_attach_validate') as $module) {
|
||||
$function = $module . '_field_attach_validate';
|
||||
$function($entity, $errors);
|
||||
}
|
||||
|
||||
if ($errors) {
|
||||
throw new FieldValidationException($errors);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs field validation against form-submitted field values.
|
||||
*
|
||||
|
|
@ -1092,25 +768,31 @@ function field_attach_validate(EntityInterface $entity, array $options = array()
|
|||
* details.
|
||||
*/
|
||||
function field_attach_form_validate(EntityInterface $entity, $form, &$form_state, array $options = array()) {
|
||||
// Ensure we are working with a BC mode entity.
|
||||
$entity = $entity->getBCEntity();
|
||||
|
||||
// Perform field_level validation.
|
||||
try {
|
||||
field_attach_validate($entity, $options);
|
||||
// Only support NG entities.
|
||||
if (!($entity->getNGEntity() instanceof EntityNG)) {
|
||||
return;
|
||||
}
|
||||
catch (FieldValidationException $e) {
|
||||
// Pass field-level validation errors back to widgets for accurate error
|
||||
// flagging.
|
||||
foreach ($e->errors as $field_name => $field_errors) {
|
||||
foreach ($field_errors as $langcode => $errors) {
|
||||
|
||||
$has_violations = FALSE;
|
||||
foreach ($entity as $field_name => $field) {
|
||||
$definition = $field->getDefinition();
|
||||
if (!empty($definition['configurable']) && (empty($options['field_name']) || $options['field_name'] == $field_name)) {
|
||||
$field_violations = $field->validate();
|
||||
if (count($field_violations)) {
|
||||
$has_violations = TRUE;
|
||||
|
||||
// Place violations in $form_state.
|
||||
$langcode = field_is_translatable($entity->entityType(), field_info_field($field_name)) ? $entity->language()->langcode : Language::LANGCODE_NOT_SPECIFIED;
|
||||
$field_state = field_form_get_state($form['#parents'], $field_name, $langcode, $form_state);
|
||||
$field_state['errors'] = $errors;
|
||||
$field_state['constraint_violations'] = $field_violations;
|
||||
field_form_set_state($form['#parents'], $field_name, $langcode, $form_state, $field_state);
|
||||
}
|
||||
}
|
||||
$form_display = $form_state['form_display'];
|
||||
field_invoke_method('flagErrors', _field_invoke_widget_target($form_display), $entity, $form, $form_state, $options);
|
||||
}
|
||||
|
||||
if ($has_violations) {
|
||||
// Map errors back to form elements.
|
||||
field_invoke_method('flagErrors', _field_invoke_widget_target($form_state['form_display']), $entity, $form, $form_state, $options);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1148,25 +830,6 @@ function field_attach_extract_form_values(EntityInterface $entity, $form, &$form
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs necessary operations just before fields data get saved.
|
||||
*
|
||||
* We take no specific action here, we just give other modules the opportunity
|
||||
* to act.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity with fields to process.
|
||||
*/
|
||||
function field_attach_presave($entity) {
|
||||
// Ensure we are working with a BC mode entity.
|
||||
$entity = $entity->getBCEntity();
|
||||
|
||||
_field_invoke('presave', $entity);
|
||||
|
||||
// Let other modules act on presaving the entity.
|
||||
module_invoke_all('field_attach_presave', $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save field data for a new entity.
|
||||
*
|
||||
|
|
@ -1185,8 +848,6 @@ function field_attach_insert(EntityInterface $entity) {
|
|||
// Ensure we are working with a BC mode entity.
|
||||
$entity = $entity->getBCEntity();
|
||||
|
||||
_field_invoke('insert', $entity);
|
||||
|
||||
// Let any module insert field data before the storage engine, accumulating
|
||||
// saved fields along the way.
|
||||
$skip_fields = array();
|
||||
|
|
@ -1214,9 +875,6 @@ function field_attach_insert(EntityInterface $entity) {
|
|||
$storage_info = field_info_storage_types($storage);
|
||||
module_invoke($storage_info['module'], 'field_storage_write', $entity, FIELD_STORAGE_INSERT, $fields);
|
||||
}
|
||||
|
||||
// Let other modules act on inserting the entity.
|
||||
module_invoke_all('field_attach_insert', $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1229,8 +887,6 @@ function field_attach_update(EntityInterface $entity) {
|
|||
// Ensure we are working with a BC mode entity.
|
||||
$entity = $entity->getBCEntity();
|
||||
|
||||
_field_invoke('update', $entity);
|
||||
|
||||
// Let any module update field data before the storage engine, accumulating
|
||||
// saved fields along the way.
|
||||
$skip_fields = array();
|
||||
|
|
@ -1263,9 +919,6 @@ function field_attach_update(EntityInterface $entity) {
|
|||
module_invoke($storage_info['module'], 'field_storage_write', $entity, FIELD_STORAGE_UPDATE, $fields);
|
||||
}
|
||||
|
||||
// Let other modules act on updating the entity.
|
||||
module_invoke_all('field_attach_update', $entity);
|
||||
|
||||
$entity_info = $entity->entityInfo();
|
||||
if ($entity_info['field_cache']) {
|
||||
cache('field')->delete('field:' . $entity->entityType() . ':' . $entity->id());
|
||||
|
|
@ -1283,8 +936,6 @@ function field_attach_delete(EntityInterface $entity) {
|
|||
// Ensure we are working with a BC mode entity.
|
||||
$entity = $entity->getBCEntity();
|
||||
|
||||
_field_invoke('delete', $entity);
|
||||
|
||||
// Collect the storage backends used by the fields in the entities.
|
||||
$storages = array();
|
||||
foreach (field_info_instances($entity->entityType(), $entity->bundle()) as $instance) {
|
||||
|
|
@ -1299,9 +950,6 @@ function field_attach_delete(EntityInterface $entity) {
|
|||
module_invoke($storage_info['module'], 'field_storage_delete', $entity, $fields);
|
||||
}
|
||||
|
||||
// Let other modules act on deleting the entity.
|
||||
module_invoke_all('field_attach_delete', $entity);
|
||||
|
||||
$entity_info = $entity->entityInfo();
|
||||
if ($entity_info['field_cache']) {
|
||||
cache('field')->delete('field:' . $entity->entityType() . ':' . $entity->id());
|
||||
|
|
@ -1319,8 +967,6 @@ function field_attach_delete_revision(EntityInterface $entity) {
|
|||
// Ensure we are working with a BC mode entity.
|
||||
$entity = $entity->getBCEntity();
|
||||
|
||||
_field_invoke('delete_revision', $entity);
|
||||
|
||||
// Collect the storage backends used by the fields in the entities.
|
||||
$storages = array();
|
||||
foreach (field_info_instances($entity->entityType(), $entity->bundle()) as $instance) {
|
||||
|
|
@ -1334,9 +980,6 @@ function field_attach_delete_revision(EntityInterface $entity) {
|
|||
$storage_info = field_info_storage_types($storage);
|
||||
module_invoke($storage_info['module'], 'field_storage_delete_revision', $entity, $fields);
|
||||
}
|
||||
|
||||
// Let other modules act on deleting the revision.
|
||||
module_invoke_all('field_attach_delete_revision', $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1362,11 +1005,8 @@ function field_attach_delete_revision(EntityInterface $entity) {
|
|||
* @param $langcode
|
||||
* (Optional) The language the field values are to be shown in. If no language
|
||||
* is provided the current language is used.
|
||||
* @param array $options
|
||||
* An associative array of additional options. See field_invoke_method() for
|
||||
* details.
|
||||
*/
|
||||
function field_attach_prepare_view($entity_type, array $entities, array $displays, $langcode = NULL, array $options = array()) {
|
||||
function field_attach_prepare_view($entity_type, array $entities, array $displays, $langcode = NULL) {
|
||||
$options['langcode'] = array();
|
||||
|
||||
// To ensure hooks are only run once per entity, only process items without
|
||||
|
|
@ -1390,9 +1030,6 @@ function field_attach_prepare_view($entity_type, array $entities, array $display
|
|||
}
|
||||
}
|
||||
|
||||
$null = NULL;
|
||||
// First let the field types do their preparation.
|
||||
_field_invoke_multiple('prepare_view', $entity_type, $prepare, $null, $null, $options);
|
||||
// Then let the formatters do their own specific massaging. For each
|
||||
// instance, call the prepareView() method on the formatter object handed by
|
||||
// the entity display.
|
||||
|
|
@ -1401,7 +1038,8 @@ function field_attach_prepare_view($entity_type, array $entities, array $display
|
|||
return $displays[$instance['bundle']]->getFormatter($instance['field_name']);
|
||||
}
|
||||
};
|
||||
field_invoke_method_multiple('prepareView', $target_function, $prepare, $null, $null, $options);
|
||||
$null = NULL;
|
||||
field_invoke_method_multiple('prepareView', $target_function, $prepare, $null, $null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1500,45 +1138,6 @@ function field_attach_preprocess(EntityInterface $entity, $element, &$variables)
|
|||
drupal_alter('field_attach_preprocess', $variables, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares an entity for translation.
|
||||
*
|
||||
* This function is used to fill in the form default values for Field API fields
|
||||
* while performing entity translation. By default it copies all the source
|
||||
* values in the given source language to the new entity and assigns them the
|
||||
* target language.
|
||||
*
|
||||
* This is used as part of the 'per entity' translation pattern, which is
|
||||
* implemented only for nodes by translation.module. Other entity types may be
|
||||
* supported through contributed modules.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity to be prepared for translation.
|
||||
* @param $langcode
|
||||
* The language the entity has to be translated in.
|
||||
* @param $source_entity
|
||||
* The source entity holding the field values to be translated.
|
||||
* @param $source_langcode
|
||||
* The source language from which translate.
|
||||
*/
|
||||
function field_attach_prepare_translation(EntityInterface $entity, $langcode, EntityInterface $source_entity, $source_langcode) {
|
||||
// Ensure we are working with a BC mode entity.
|
||||
$entity = $entity->getBCEntity();
|
||||
|
||||
$options = array('langcode' => $langcode);
|
||||
// Copy source field values into the entity to be prepared.
|
||||
_field_invoke_default('prepare_translation', $entity, $source_entity, $source_langcode, $options);
|
||||
// Let field types handle their own advanced translation pattern if needed.
|
||||
_field_invoke('prepare_translation', $entity, $source_entity, $source_langcode, $options);
|
||||
// Let other modules alter the entity translation.
|
||||
$context = array(
|
||||
'langcode' => $langcode,
|
||||
'source_entity' => $source_entity,
|
||||
'source_langcode' => $source_langcode,
|
||||
);
|
||||
drupal_alter('field_attach_prepare_translation', $entity, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_bundle_create().
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -263,7 +263,7 @@ function field_purge_batch($batch_size) {
|
|||
$ids->entity_id = $entity_id;
|
||||
$entities[$entity_id] = _field_create_entity_from_ids($ids);
|
||||
}
|
||||
field_attach_load($entity_type, $entities, FIELD_LOAD_CURRENT, array('field_id' => $field['uuid'], 'deleted' => 1));
|
||||
field_attach_load($entity_type, $entities, FIELD_LOAD_CURRENT, array('instance' => $instance));
|
||||
foreach ($entities as $entity) {
|
||||
// Purge the data for the entity.
|
||||
field_purge_data($entity, $field, $instance);
|
||||
|
|
@ -301,10 +301,11 @@ function field_purge_batch($batch_size) {
|
|||
* The deleted field instance whose data is being purged.
|
||||
*/
|
||||
function field_purge_data(EntityInterface $entity, $field, $instance) {
|
||||
// Each field type's hook_field_delete() only expects to operate on a single
|
||||
// field at a time, so we can use it as-is for purging.
|
||||
$options = array('field_id' => $instance['field_id'], 'deleted' => TRUE);
|
||||
_field_invoke('delete', $entity, $dummy, $dummy, $options);
|
||||
foreach ($entity->{$field->id()} as $value) {
|
||||
$definition = _field_generate_entity_field_definition($field, $instance);
|
||||
$items = \Drupal::typedData()->create($definition, $value, $field->id(), $entity);
|
||||
$items->delete();
|
||||
}
|
||||
|
||||
// Tell the field storage system to purge the data.
|
||||
module_invoke($field['storage']['module'], 'field_storage_purge', $entity, $field, $instance);
|
||||
|
|
|
|||
|
|
@ -1,85 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Default 'implementations' of hook_field_*(): common field housekeeping.
|
||||
*
|
||||
* Those implementations are special, as field.module does not define any field
|
||||
* types. Those functions take care of default stuff common to all field types.
|
||||
* They are called through the _field_invoke_default() iterator, generally in
|
||||
* the corresponding field_attach_[operation]() function.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Language\Language;
|
||||
|
||||
/**
|
||||
* Generic field validation handler.
|
||||
*
|
||||
* Possible error codes:
|
||||
* - 'field_cardinality': The number of values exceeds the field cardinality.
|
||||
*
|
||||
* @see _hook_field_validate()
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity for the operation.
|
||||
* @param $field
|
||||
* The field structure for the operation.
|
||||
* @param $instance
|
||||
* The instance structure for $field in $entity's bundle.
|
||||
* @param $langcode
|
||||
* The language associated with $items.
|
||||
* @param $items
|
||||
* $entity->{$field['field_name']}[$langcode], or an empty array if unset.
|
||||
* @param $errors
|
||||
* The array of errors, keyed by field name and by value delta, that have
|
||||
* already been reported for the entity. The function should add its errors to
|
||||
* this array. Each error is an associative array, with the following keys and
|
||||
* values:
|
||||
* - error: An error code (should be a string, prefixed with the module name).
|
||||
* - message: The human readable message to be displayed.
|
||||
*/
|
||||
function field_default_validate(EntityInterface $entity, $field, $instance, $langcode, $items, &$errors) {
|
||||
// Filter out empty values.
|
||||
$items = _field_filter_items($field['type'], $items);
|
||||
|
||||
// Check that the number of values doesn't exceed the field cardinality.
|
||||
// For form submitted values, this can only happen with 'multiple value'
|
||||
// widgets.
|
||||
if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED && count($items) > $field['cardinality']) {
|
||||
$errors[$field['field_name']][$langcode][0][] = array(
|
||||
'error' => 'field_cardinality',
|
||||
'message' => t('%name: this field cannot hold more than @count values.', array('%name' => $instance['label'], '@count' => $field['cardinality'])),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies source field values into the entity to be prepared.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity to be prepared for translation.
|
||||
* @param $field
|
||||
* The field structure for the operation.
|
||||
* @param $instance
|
||||
* The instance structure for $field in $entity's bundle.
|
||||
* @param $langcode
|
||||
* The language the entity has to be translated to.
|
||||
* @param $items
|
||||
* $entity->{$field['field_name']}[$langcode], or an empty array if unset.
|
||||
* @param \Drupal\Core\Entity\EntityInterface $source_entity
|
||||
* The source entity holding the field values to be translated.
|
||||
* @param $source_langcode
|
||||
* The source language from which to translate.
|
||||
*/
|
||||
function field_default_prepare_translation(EntityInterface $entity, $field, $instance, $langcode, &$items, EntityInterface $source_entity, $source_langcode) {
|
||||
$field_name = $field['field_name'];
|
||||
// If the field is untranslatable keep using Language::LANGCODE_NOT_SPECIFIED.
|
||||
if ($langcode == Language::LANGCODE_NOT_SPECIFIED) {
|
||||
$source_langcode = Language::LANGCODE_NOT_SPECIFIED;
|
||||
}
|
||||
if (isset($source_entity->{$field_name}[$source_langcode])) {
|
||||
$items = $source_entity->{$field_name}[$source_langcode];
|
||||
}
|
||||
}
|
||||
|
|
@ -183,8 +183,8 @@ function field_add_more_js($form, $form_state) {
|
|||
* - items_count: The number of widgets to display for the field.
|
||||
* - array_parents: The location of the field's widgets within the $form
|
||||
* structure. This entry is populated at '#after_build' time.
|
||||
* - errors: The array of field validation errors reported on the field. This
|
||||
* entry is populated at field_attach_form_validate() time.
|
||||
* - constraint_violations: The array of validation errors reported on the
|
||||
* field. This entry is populated at form validate time.
|
||||
*
|
||||
* @see field_form_set_state()
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -37,6 +37,10 @@ function field_info_cache_clear() {
|
|||
// functions are moved to the entity API.
|
||||
entity_info_cache_clear();
|
||||
|
||||
// Clear typed data definitions.
|
||||
Drupal::typedData()->clearCachedDefinitions();
|
||||
Drupal::service('plugin.manager.entity.field.field_type')->clearCachedDefinitions();
|
||||
|
||||
_field_info_collate_types_reset();
|
||||
Field::fieldInfo()->flush();
|
||||
}
|
||||
|
|
@ -46,11 +50,6 @@ function field_info_cache_clear() {
|
|||
*
|
||||
* @return
|
||||
* An associative array containing:
|
||||
* - 'field types': Array of hook_field_info() results, keyed by field_type.
|
||||
* Each element has the following components: label, description, settings,
|
||||
* instance_settings, default_widget, default_formatter, and behaviors
|
||||
* from hook_field_info(), as well as module, giving the module that exposes
|
||||
* the field type.
|
||||
* - 'storage types': Array of hook_field_storage_info() results, keyed by
|
||||
* storage type names. Each element has the following components: label,
|
||||
* description, and settings from hook_field_storage_info(), as well as
|
||||
|
|
@ -79,25 +78,9 @@ function _field_info_collate_types() {
|
|||
}
|
||||
else {
|
||||
$info = array(
|
||||
'field types' => array(),
|
||||
'storage types' => array(),
|
||||
);
|
||||
|
||||
// Populate field types.
|
||||
foreach (module_implements('field_info') as $module) {
|
||||
$field_types = (array) module_invoke($module, 'field_info');
|
||||
foreach ($field_types as $name => $field_info) {
|
||||
// Provide defaults.
|
||||
$field_info += array(
|
||||
'settings' => array(),
|
||||
'instance_settings' => array(),
|
||||
);
|
||||
$info['field types'][$name] = $field_info;
|
||||
$info['field types'][$name]['module'] = $module;
|
||||
}
|
||||
}
|
||||
drupal_alter('field_info', $info['field types']);
|
||||
|
||||
// Populate storage types.
|
||||
foreach (module_implements('field_storage_info') as $module) {
|
||||
$storage_types = (array) module_invoke($module, 'field_storage_info');
|
||||
|
|
@ -178,25 +161,21 @@ function field_info_field_map() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns information about field types from hook_field_info().
|
||||
* Returns information about field types.
|
||||
*
|
||||
* @param $field_type
|
||||
* (optional) A field type name. If omitted, all field types will be returned.
|
||||
*
|
||||
* @return
|
||||
* Either a field type description, as provided by hook_field_info(), or an
|
||||
* array of all existing field types, keyed by field type name.
|
||||
* Either a field type definition, or an array of all existing field types,
|
||||
* keyed by field type name.
|
||||
*/
|
||||
function field_info_field_types($field_type = NULL) {
|
||||
$info = _field_info_collate_types();
|
||||
$field_types = $info['field types'];
|
||||
if ($field_type) {
|
||||
if (isset($field_types[$field_type])) {
|
||||
return $field_types[$field_type];
|
||||
}
|
||||
return Drupal::service('plugin.manager.entity.field.field_type')->getDefinition($field_type);
|
||||
}
|
||||
else {
|
||||
return $field_types;
|
||||
return Drupal::service('plugin.manager.entity.field.field_type')->getDefinitions();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -488,8 +467,8 @@ function field_info_extra_fields($entity_type, $bundle, $context) {
|
|||
* A field type name.
|
||||
*
|
||||
* @return
|
||||
* The field type's default settings, as provided by hook_field_info(), or an
|
||||
* empty array if type or settings are not defined.
|
||||
* The field type's default settings, or an empty array if type or settings
|
||||
* are not defined.
|
||||
*/
|
||||
function field_info_field_settings($type) {
|
||||
$info = field_info_field_types($type);
|
||||
|
|
@ -503,8 +482,8 @@ function field_info_field_settings($type) {
|
|||
* A field type name.
|
||||
*
|
||||
* @return
|
||||
* The field type's default instance settings, as provided by
|
||||
* hook_field_info(), or an empty array if type or settings are not defined.
|
||||
* The field type's default instance settings, or an empty array if type or
|
||||
* settings are not defined.
|
||||
*/
|
||||
function field_info_instance_settings($type) {
|
||||
$info = field_info_field_types($type);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ use Drupal\Core\Entity\EntityInterface;
|
|||
use Drupal\Core\Language\Language;
|
||||
use Drupal\Core\Template\Attribute;
|
||||
use Drupal\field\FieldInterface;
|
||||
use Drupal\field\FieldInstanceInterface;
|
||||
use Drupal\Core\Entity\EntityNG;
|
||||
|
||||
/*
|
||||
* Load all public Field API functions. Drupal currently has no
|
||||
|
|
@ -15,7 +17,6 @@ use Drupal\field\FieldInterface;
|
|||
* every page request.
|
||||
*/
|
||||
require_once __DIR__ . '/field.crud.inc';
|
||||
require_once __DIR__ . '/field.default.inc';
|
||||
require_once __DIR__ . '/field.info.inc';
|
||||
require_once __DIR__ . '/field.multilingual.inc';
|
||||
require_once __DIR__ . '/field.attach.inc';
|
||||
|
|
@ -228,25 +229,6 @@ 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.
|
||||
foreach ($field_types as $type_name => $type_info) {
|
||||
$data_type = isset($type_info['data_type']) ? $type_info['data_type'] : $type_name . '_field';
|
||||
$items[$data_type] = 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_create().
|
||||
*/
|
||||
|
|
@ -292,21 +274,12 @@ function field_populate_default_values(EntityInterface $entity, $langcode = NULL
|
|||
*/
|
||||
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);
|
||||
|
||||
// @todo: Allow for adding field type settings.
|
||||
$definition = array(
|
||||
'label' => t('Field !name', array('!name' => $field_name)),
|
||||
'type' => isset($field_types[$field['type']]['data_type']) ? $field_types[$field['type']]['data_type'] : $field['type'] . '_field',
|
||||
'configurable' => TRUE,
|
||||
'translatable' => !empty($field['translatable'])
|
||||
);
|
||||
$definition = _field_generate_entity_field_definition($instance->getField());
|
||||
|
||||
if ($optional) {
|
||||
$property_info['optional'][$field_name] = $definition;
|
||||
|
|
@ -321,6 +294,33 @@ function field_entity_field_info($entity_type) {
|
|||
return $property_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an entity field definition for a configurable field.
|
||||
*
|
||||
* @param \Drupal\field\FieldInterface $field
|
||||
* The field definition.
|
||||
* @param \Drupal\field\FieldInstanceInterface $instance
|
||||
* (Optional) The field instance definition.
|
||||
*
|
||||
* @return array
|
||||
* The entity field definition.
|
||||
*/
|
||||
function _field_generate_entity_field_definition(FieldInterface $field, FieldInstanceInterface $instance = NULL) {
|
||||
// @todo: Allow for adding field type settings.
|
||||
$definition = array(
|
||||
'label' => t('Field !name', array('!name' => $field->id())),
|
||||
'type' => 'field_item:' . $field->type,
|
||||
'list' => TRUE,
|
||||
'configurable' => TRUE,
|
||||
'translatable' => !empty($field->translatable),
|
||||
);
|
||||
if ($instance) {
|
||||
$definition['instance'] = $instance;
|
||||
}
|
||||
|
||||
return $definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_widget_info_alter().
|
||||
*/
|
||||
|
|
@ -498,64 +498,6 @@ function field_get_default_value(EntityInterface $entity, $field, $instance, $la
|
|||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters out empty field values.
|
||||
*
|
||||
* @param $field_type
|
||||
* The field type.
|
||||
* @param $items
|
||||
* The field values to filter.
|
||||
*
|
||||
* @return
|
||||
* The array of items without empty field values. The function also renumbers
|
||||
* the array keys to ensure sequential deltas.
|
||||
*/
|
||||
function _field_filter_items($field_type, $items) {
|
||||
$field_type_info = field_info_field_types($field_type);
|
||||
$function = $field_type_info['module'] . '_field_is_empty';
|
||||
foreach ((array) $items as $delta => $item) {
|
||||
// Explicitly break if the function is undefined.
|
||||
if ($function($item, $field_type)) {
|
||||
unset($items[$delta]);
|
||||
}
|
||||
}
|
||||
return array_values($items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts items in a field according to user drag-and-drop reordering.
|
||||
*
|
||||
* @param $field
|
||||
* The field definition.
|
||||
* @param $items
|
||||
* The field values to sort.
|
||||
*
|
||||
* @return
|
||||
* The sorted array of field items.
|
||||
*/
|
||||
function _field_sort_items($field, $items) {
|
||||
if (($field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED) && isset($items[0]['_weight'])) {
|
||||
usort($items, '_field_sort_items_helper');
|
||||
foreach ($items as $delta => $item) {
|
||||
if (is_array($items[$delta])) {
|
||||
unset($items[$delta]['_weight']);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for usort() within _field_sort_items().
|
||||
*
|
||||
* Copied form element_sort(), which acts on #weight keys.
|
||||
*/
|
||||
function _field_sort_items_helper($a, $b) {
|
||||
$a_weight = (is_array($a) ? $a['_weight'] : 0);
|
||||
$b_weight = (is_array($b) ? $b['_weight'] : 0);
|
||||
return $a_weight - $b_weight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for usort() within theme_field_multiple_value_form().
|
||||
*
|
||||
|
|
@ -768,11 +710,10 @@ function field_view_value(EntityInterface $entity, $field_name, $item, $display
|
|||
* implementation supports the values 'inline', 'above' and 'hidden'.
|
||||
* Defaults to 'above'.
|
||||
* - type: (string) The formatter to use. Defaults to the
|
||||
* 'default_formatter' for the field type, specified in hook_field_info().
|
||||
* The default formatter will also be used if the requested formatter is
|
||||
* not available.
|
||||
* 'default_formatter' for the field type. The default formatter will also
|
||||
* be used if the requested formatter is not available.
|
||||
* - settings: (array) Settings specific to the formatter. Defaults to the
|
||||
* formatter's default settings, specified in hook_field_formatter_info().
|
||||
* formatter's default settings.
|
||||
* - weight: (float) The weight to assign to the renderable element.
|
||||
* Defaults to 0.
|
||||
* @param $langcode
|
||||
|
|
@ -789,9 +730,10 @@ function field_view_value(EntityInterface $entity, $field_name, $item, $display
|
|||
function field_view_field(EntityInterface $entity, $field_name, $display_options = array(), $langcode = NULL) {
|
||||
$output = array();
|
||||
$bundle = $entity->bundle();
|
||||
$entity_type = $entity->entityType();
|
||||
|
||||
// Return nothing if the field doesn't exist.
|
||||
$instance = field_info_instance($entity->entityType(), $field_name, $bundle);
|
||||
$instance = field_info_instance($entity_type, $field_name, $bundle);
|
||||
if (!$instance) {
|
||||
return $output;
|
||||
}
|
||||
|
|
@ -818,33 +760,35 @@ function field_view_field(EntityInterface $entity, $field_name, $display_options
|
|||
|
||||
if ($formatter) {
|
||||
$display_langcode = field_language($entity, $field_name, $langcode);
|
||||
$items = array();
|
||||
// Ensure the BC entity is used.
|
||||
$entity = $entity->getBCEntity();
|
||||
if (isset($entity->{$field_name}[$display_langcode])) {
|
||||
$items = $entity->{$field_name}[$display_langcode];
|
||||
|
||||
// Get the items.
|
||||
if ($entity->getNGEntity() instanceof EntityNG) {
|
||||
$items = $entity->getTranslation($display_langcode)->get($field_name);
|
||||
$definition = $entity->getPropertyDefinition($field_name);
|
||||
}
|
||||
else {
|
||||
$definitions = \Drupal::entityManager()->getFieldDefinitions($entity_type, $bundle);
|
||||
$definition = $definitions[$field_name];
|
||||
$itemsBC = isset($entity->{$field_name}[$display_langcode]) ? $entity->{$field_name}[$display_langcode] : array();
|
||||
$items = \Drupal::typedData()->create($definitions[$field_name], $itemsBC, $field_name, $entity);
|
||||
}
|
||||
|
||||
// Invoke prepare_view steps if needed.
|
||||
if (empty($entity->_field_view_prepared)) {
|
||||
$id = $entity->id();
|
||||
$id = $entity->id();
|
||||
|
||||
// First let the field types do their preparation.
|
||||
$options = array('field_name' => $field_name, 'langcode' => $display_langcode);
|
||||
$null = NULL;
|
||||
_field_invoke_multiple('prepare_view', $entity->entityType(), array($id => $entity), $null, $null, $options);
|
||||
// First let the field type do its preparation. prepareView() is a static
|
||||
// method.
|
||||
$type_definition = \Drupal::typedData()->getDefinition($definition['type']);
|
||||
$class = $type_definition['class'];
|
||||
$class::prepareView(array($id => $items), $definition);
|
||||
|
||||
// Then let the formatter do its own specific massaging.
|
||||
$items_multi = array($id => array());
|
||||
if (isset($entity->{$field_name}[$display_langcode])) {
|
||||
$items_multi[$id] = $entity->{$field_name}[$display_langcode];
|
||||
}
|
||||
$formatter->prepareView(array($id => $entity), $display_langcode, $items_multi);
|
||||
$items = $items_multi[$id];
|
||||
}
|
||||
// Then let the formatter do its own specific massaging.
|
||||
$itemsBC_multi = array($id => $items->getValue());
|
||||
$formatter->prepareView(array($id => $entity), $display_langcode, $itemsBC_multi);
|
||||
$itemsBC = $itemsBC_multi[$id];
|
||||
|
||||
// Build the renderable array.
|
||||
$result = $formatter->view($entity, $display_langcode, $items);
|
||||
$result = $formatter->view($entity, $display_langcode, $itemsBC);
|
||||
|
||||
// Invoke hook_field_attach_view_alter() to let other modules alter the
|
||||
// renderable array, as in a full field_attach_view() execution.
|
||||
|
|
|
|||
|
|
@ -34,10 +34,9 @@ use Drupal\Core\Language\Language;
|
|||
* property returned by field_info_field() and whether the entity type the field
|
||||
* is attached to supports translation.
|
||||
*
|
||||
* By default, _field_invoke() and _field_invoke_multiple() process a field in
|
||||
* all available languages, unless they are given a language code suggestion.
|
||||
* Based on that suggestion, _field_language_suggestion() determines the
|
||||
* languages to act on.
|
||||
* By default, field_invoke_method() processes a field in all available
|
||||
* languages, unless they are given a language code suggestion. Based on that
|
||||
* suggestion, _field_language_suggestion() determines the languages to act on.
|
||||
*
|
||||
* Most field_attach_*() functions act on all available language codes, except
|
||||
* for the following:
|
||||
|
|
|
|||
|
|
@ -33,6 +33,17 @@ interface FieldInterface extends ConfigEntityInterface, FieldDefinitionInterface
|
|||
*/
|
||||
public function getSchema();
|
||||
|
||||
/**
|
||||
* Returns the field columns, as defined in the field schema.
|
||||
*
|
||||
* @return array
|
||||
* The array of field columns, keyed by column name, in the same format
|
||||
* returned by getSchema().
|
||||
*
|
||||
* @see \Drupal\field\Plugin\Core\Entity\FieldInterface::getSchema()
|
||||
*/
|
||||
public function getColumns();
|
||||
|
||||
/**
|
||||
* Returns information about how the storage backend stores the field data.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* @file
|
||||
* Definition of Drupal\field\FieldValidationExeption.
|
||||
*/
|
||||
|
||||
namespace Drupal\field;
|
||||
|
||||
/**
|
||||
* Exception thrown by field_attach_validate() on field validation errors.
|
||||
*/
|
||||
class FieldValidationException extends FieldException {
|
||||
|
||||
/**
|
||||
* An array of field validation errors.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $errors;
|
||||
|
||||
/**
|
||||
* Constructor for FieldValidationException.
|
||||
*
|
||||
* @param $errors
|
||||
* An array of field validation errors, keyed by field name and
|
||||
* delta that contains two keys:
|
||||
* - 'error': A machine-readable error code string, prefixed by
|
||||
* the field module name. A field widget may use this code to decide
|
||||
* how to report the error.
|
||||
* - 'message': A human-readable error message such as to be
|
||||
* passed to form_error() for the appropriate form element.
|
||||
*/
|
||||
function __construct($errors) {
|
||||
$this->errors = $errors;
|
||||
parent::__construct(t('Field validation errors'));
|
||||
}
|
||||
}
|
||||
|
|
@ -68,8 +68,6 @@ class Field extends ConfigEntityBase implements FieldInterface {
|
|||
/**
|
||||
* The field type.
|
||||
*
|
||||
* Field types are defined by modules that implement hook_field_info().
|
||||
*
|
||||
* Example: text, number_integer.
|
||||
*
|
||||
* @var string
|
||||
|
|
@ -94,7 +92,7 @@ class Field extends ConfigEntityBase implements FieldInterface {
|
|||
* Field-type specific settings.
|
||||
*
|
||||
* An array of key/value pairs, The keys and default values are defined by the
|
||||
* field type in the 'settings' entry of hook_field_info().
|
||||
* field type.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
|
|
@ -494,15 +492,12 @@ class Field extends ConfigEntityBase implements FieldInterface {
|
|||
*/
|
||||
public function getSchema() {
|
||||
if (!isset($this->schema)) {
|
||||
$module_handler = \Drupal::moduleHandler();
|
||||
|
||||
// Collect the schema from the field type.
|
||||
// @todo Use $module_handler->loadInclude() once
|
||||
// http://drupal.org/node/1941000 is fixed.
|
||||
module_load_install($this->module);
|
||||
// Invoke hook_field_schema() for the field.
|
||||
$schema = (array) $module_handler->invoke($this->module, 'field_schema', array($this));
|
||||
$schema += array('columns' => array(), 'indexes' => array(), 'foreign keys' => array());
|
||||
// Get the schema from the field item class.
|
||||
$definition = \Drupal::service('plugin.manager.entity.field.field_type')->getDefinition($this->type);
|
||||
$class = $definition['class'];
|
||||
$schema = $class::schema($this);
|
||||
// Fill in default values for optional entries.
|
||||
$schema += array('indexes' => array(), 'foreign keys' => array());
|
||||
|
||||
// Check that the schema does not include forbidden column names.
|
||||
if (array_intersect(array_keys($schema['columns']), static::getReservedColumns())) {
|
||||
|
|
@ -519,6 +514,20 @@ class Field extends ConfigEntityBase implements FieldInterface {
|
|||
return $this->schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getColumns() {
|
||||
$schema = $this->getSchema();
|
||||
// A typical use case for the method is to iterate on the columns, while
|
||||
// some other use cases rely on identifying the first column with the key()
|
||||
// function. Since the schema is persisted in the Field object, we take care
|
||||
// of resetting the array pointer so that the former does not interfere with
|
||||
// the latter.
|
||||
reset($schema['columns']);
|
||||
return $schema['columns'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ class FieldInstance extends ConfigEntityBase implements FieldInstanceInterface {
|
|||
* Field-type specific settings.
|
||||
*
|
||||
* An array of key/value pairs. The keys and default values are defined by the
|
||||
* field type in the 'instance_settings' entry of hook_field_info().
|
||||
* field type.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,170 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\field\Plugin\Type\FieldType\ConfigEntityReferenceItemBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\field\Plugin\Type\FieldType;
|
||||
|
||||
use Drupal\Core\Entity\Field\Type\EntityReferenceItem;
|
||||
use Drupal\field\Plugin\Type\FieldType\ConfigFieldItemInterface;
|
||||
use Drupal\field\Plugin\Core\Entity\Field;
|
||||
|
||||
/**
|
||||
* A common base class for configurable entity reference fields.
|
||||
*
|
||||
* Extends the Core 'entity_reference' entity field item with properties for
|
||||
* revision ids, labels (for autocreate) and access.
|
||||
*
|
||||
* Required settings (below the definition's 'settings' key) are:
|
||||
* - target_type: The entity type to reference.
|
||||
*/
|
||||
class ConfigEntityReferenceItemBase extends EntityReferenceItem implements ConfigFieldItemInterface {
|
||||
|
||||
/**
|
||||
* Definitions of the contained properties.
|
||||
*
|
||||
* @see ConfigurableEntityReferenceItem::getPropertyDefinitions()
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
static $propertyDefinitions;
|
||||
|
||||
/**
|
||||
* The Field instance definition.
|
||||
*
|
||||
* @var \Drupal\field\Plugin\Core\Entity\FieldInstance
|
||||
*/
|
||||
protected $instance;
|
||||
|
||||
/**
|
||||
* Returns the field instance definition.
|
||||
*
|
||||
* Copied from \Drupal\field\Plugin\Type\FieldType\ConfigFieldItemBase,
|
||||
* since we cannot extend it.
|
||||
*
|
||||
* @var \Drupal\field\Plugin\Core\Entity\FieldInstance
|
||||
*/
|
||||
public function getInstance() {
|
||||
if (!isset($this->instance) && $parent = $this->getParent()) {
|
||||
$this->instance = $parent->getInstance();
|
||||
}
|
||||
return $this->instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPropertyDefinitions() {
|
||||
// Definitions vary by entity type, so key them by entity type.
|
||||
$target_type = $this->definition['settings']['target_type'];
|
||||
|
||||
if (!isset(self::$propertyDefinitions[$target_type])) {
|
||||
// Call the parent to define the target_id and entity properties.
|
||||
parent::getPropertyDefinitions();
|
||||
|
||||
static::$propertyDefinitions[$target_type]['revision_id'] = array(
|
||||
// @todo: Lookup the entity type's ID data type and use it here.
|
||||
'type' => 'integer',
|
||||
'label' => t('Revision ID'),
|
||||
'constraints' => array(
|
||||
'Range' => array('min' => 0),
|
||||
),
|
||||
);
|
||||
static::$propertyDefinitions[$target_type]['label'] = array(
|
||||
'type' => 'string',
|
||||
'label' => t('Label (auto-create)'),
|
||||
'computed' => TRUE,
|
||||
);
|
||||
static::$propertyDefinitions[$target_type]['access'] = array(
|
||||
'type' => 'boolean',
|
||||
'label' => t('Access'),
|
||||
'computed' => TRUE,
|
||||
);
|
||||
}
|
||||
return static::$propertyDefinitions[$target_type];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Copied from \Drupal\field\Plugin\field\field_type\LegacyConfigFieldItem,
|
||||
* since we cannot extend it.
|
||||
*/
|
||||
public static function schema(Field $field) {
|
||||
$definition = \Drupal::typedData()->getDefinition('field_item:' . $field->type);
|
||||
$module = $definition['module'];
|
||||
module_load_install($module);
|
||||
$callback = "{$module}_field_schema";
|
||||
if (function_exists($callback)) {
|
||||
return $callback($field);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isEmpty() {
|
||||
// Avoid loading the entity by first checking the 'target_id'.
|
||||
$target_id = $this->get('target_id')->getValue();
|
||||
if (!empty($target_id) && is_numeric($target_id)) {
|
||||
return FALSE;
|
||||
}
|
||||
// Allow auto-create entities.
|
||||
if (empty($target_id) && ($entity = $this->get('entity')->getValue()) && $entity->isNew()) {
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Copied from \Drupal\field\Plugin\field\field_type\LegacyConfigFieldItem,
|
||||
* since we cannot extend it.
|
||||
*/
|
||||
public function settingsForm(array $form, array &$form_state) {
|
||||
if ($callback = $this->getLegacyCallback('settings_form')) {
|
||||
// hook_field_settings_form() used to receive the $instance (not actually
|
||||
// needed), and the value of field_has_data().
|
||||
return $callback($this->getInstance()->getField(), $this->getInstance(), $this->getInstance()->getField()->hasData());
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Copied from \Drupal\field\Plugin\field\field_type\LegacyConfigFieldItem,
|
||||
* since we cannot extend it.
|
||||
*/
|
||||
public function instanceSettingsForm(array $form, array &$form_state) {
|
||||
if ($callback = $this->getLegacyCallback('instance_settings_form')) {
|
||||
return $callback($this->getInstance()->getField(), $this->getInstance(), $form_state);
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the legacy callback for a given field type "hook".
|
||||
*
|
||||
* Copied from \Drupal\field\Plugin\field\field_type\LegacyConfigFieldItem,
|
||||
* since we cannot extend it.
|
||||
*
|
||||
* @param string $hook
|
||||
* The name of the hook, e.g. 'settings_form', 'is_empty'.
|
||||
*
|
||||
* @return string|null
|
||||
* The name of the legacy callback, or NULL if it does not exist.
|
||||
*/
|
||||
protected function getLegacyCallback($hook) {
|
||||
$definition = $this->getPluginDefinition();
|
||||
$module = $definition['module'];
|
||||
$callback = "{$module}_field_{$hook}";
|
||||
if (function_exists($callback)) {
|
||||
return $callback;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\field\Plugin\Type\FieldType\ConfigField.
|
||||
*/
|
||||
|
||||
namespace Drupal\field\Plugin\Type\FieldType;
|
||||
|
||||
use Drupal\Core\TypedData\TypedDataInterface;
|
||||
use Drupal\Core\Entity\Field\Type\Field;
|
||||
use Drupal\field\Field as FieldAPI;
|
||||
|
||||
/**
|
||||
* Represents a configurable entity field.
|
||||
*/
|
||||
class ConfigField extends Field implements ConfigFieldInterface {
|
||||
|
||||
/**
|
||||
* The Field instance definition.
|
||||
*
|
||||
* @var \Drupal\field\Plugin\Core\Entity\FieldInstance
|
||||
*/
|
||||
protected $instance;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $definition, $name = NULL, TypedDataInterface $parent = NULL) {
|
||||
parent::__construct($definition, $name, $parent);
|
||||
if (isset($definition['instance'])) {
|
||||
$this->instance = $definition['instance'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInstance() {
|
||||
if (!isset($this->instance) && $parent = $this->getParent()) {
|
||||
$instances = FieldAPI::fieldInfo()->getBundleInstances($parent->entityType(), $parent->bundle());
|
||||
$this->instance = $instances[$this->getName()];
|
||||
}
|
||||
return $this->instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConstraints() {
|
||||
$constraints = array();
|
||||
// Check that the number of values doesn't exceed the field cardinality. For
|
||||
// form submitted values, this can only happen with 'multiple value'
|
||||
// widgets.
|
||||
$cardinality = $this->getInstance()->getField()->cardinality;
|
||||
if ($cardinality != FIELD_CARDINALITY_UNLIMITED) {
|
||||
$constraints[] = \Drupal::typedData()
|
||||
->getValidationConstraintManager()
|
||||
->create('Count', array(
|
||||
'max' => $cardinality,
|
||||
'maxMessage' => t('%name: this field cannot hold more than @count values.', array('%name' => $this->getInstance()->label, '@count' => $cardinality)),
|
||||
));
|
||||
}
|
||||
|
||||
return $constraints;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\field\Plugin\Type\FieldType\ConfigFieldInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\field\Plugin\Type\FieldType;
|
||||
|
||||
use Drupal\Core\Entity\Field\FieldInterface;
|
||||
|
||||
/**
|
||||
* Interface definition for "configurable fields".
|
||||
*/
|
||||
interface ConfigFieldInterface extends FieldInterface {
|
||||
|
||||
/**
|
||||
* Returns the field instance definition.
|
||||
*
|
||||
* @var \Drupal\field\Plugin\Core\Entity\FieldInstance
|
||||
*/
|
||||
public function getInstance();
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\field\Plugin\Type\FieldType\ConfigFieldItemBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\field\Plugin\Type\FieldType;
|
||||
|
||||
use Drupal\Core\Entity\Field\FieldItemBase;
|
||||
|
||||
/**
|
||||
* Base class for 'configurable field type' plugin implementations.
|
||||
*/
|
||||
abstract class ConfigFieldItemBase extends FieldItemBase implements ConfigFieldItemInterface {
|
||||
|
||||
/**
|
||||
* The Field instance definition.
|
||||
*
|
||||
* @var \Drupal\field\Plugin\Core\Entity\FieldInstance
|
||||
*/
|
||||
public $instance;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInstance() {
|
||||
if (!isset($this->instance) && $parent = $this->getParent()) {
|
||||
$this->instance = $parent->getInstance();
|
||||
}
|
||||
return $this->instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsForm(array $form, array &$form_state) {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function instanceSettingsForm(array $form, array &$form_state) {
|
||||
return array();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\field\Plugin\Type\FieldType\ConfigFieldItemInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\field\Plugin\Type\FieldType;
|
||||
|
||||
use Drupal\Core\Entity\Field\FieldItemInterface;
|
||||
use Drupal\field\Plugin\Core\Entity\Field;
|
||||
|
||||
/**
|
||||
* Interface definition for 'configurable field type' plugins.
|
||||
*/
|
||||
interface ConfigFieldItemInterface extends FieldItemInterface {
|
||||
|
||||
/**
|
||||
* Returns the field instance definition.
|
||||
*
|
||||
* @var \Drupal\field\Plugin\Core\Entity\FieldInstance
|
||||
*/
|
||||
public function getInstance();
|
||||
|
||||
/**
|
||||
* Returns the schema for the field.
|
||||
*
|
||||
* This method is static, because the field schema information is needed on
|
||||
* creation of the field. No field instances exist by then, and it is not
|
||||
* possible to instantiate a FieldItemInterface object yet.
|
||||
*
|
||||
* @param \Drupal\field\Plugin\Core\Entity\Field $field
|
||||
* The field definition.
|
||||
*
|
||||
* @return array
|
||||
* An associative array with the following key/value pairs:
|
||||
* - columns: An array of Schema API column specifications, keyed by column
|
||||
* name. This specifies what comprises a value for a given field. For
|
||||
* example, a value for a number field is simply 'value', while a value
|
||||
* for a formatted text field is the combination of 'value' and 'format'.
|
||||
* It is recommended to avoid having the column definitions depend on
|
||||
* field settings when possible. No assumptions should be made on how
|
||||
* storage engines internally use the original column name to structure
|
||||
* their storage.
|
||||
* - indexes: (optional) An array of Schema API index definitions. Only
|
||||
* columns that appear in the 'columns' array are allowed. Those indexes
|
||||
* will be used as default indexes. Callers of field_create_field() can
|
||||
* specify additional indexes or, at their own risk, modify the default
|
||||
* indexes specified by the field-type module. Some storage engines might
|
||||
* not support indexes.
|
||||
* - foreign keys: (optional) An array of Schema API foreign key
|
||||
* definitions. Note, however, that the field data is not necessarily
|
||||
* stored in SQL. Also, the possible usage is limited, as you cannot
|
||||
* specify another field as related, only existing SQL tables,
|
||||
* such as {taxonomy_term_data}.
|
||||
*/
|
||||
public static function schema(Field $field);
|
||||
|
||||
/**
|
||||
* Returns a form for the field-level settings.
|
||||
*
|
||||
* Invoked from \Drupal\field_ui\Form\FieldEditForm to allow administrators to
|
||||
* configure field-level settings. If the field already has data, the form
|
||||
* should only include the settings that are safe to change.
|
||||
*
|
||||
* @param array $form
|
||||
* The form where the settings form is being included in.
|
||||
* @param array $form_state
|
||||
* The form state of the (entire) configuration form.
|
||||
*
|
||||
* @return
|
||||
* The form definition for the field settings.
|
||||
*/
|
||||
public function settingsForm(array $form, array &$form_state);
|
||||
|
||||
/**
|
||||
* Returns a form for the instance-level settings.
|
||||
*
|
||||
* Invoked from \Drupal\field_ui\Form\FieldInstanceEditForm to allow
|
||||
* administrators to configure instance-level settings.
|
||||
*
|
||||
* @param array $form
|
||||
* The form where the settings form is being included in.
|
||||
* @param array $form_state
|
||||
* The form state of the (entire) configuration form.
|
||||
*
|
||||
* @return array
|
||||
* The form definition for the field instance settings.
|
||||
*/
|
||||
public function instanceSettingsForm(array $form, array &$form_state);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\field\Plugin\Type\Widget\LegacyFieldTypeDiscoveryDecorator.
|
||||
*/
|
||||
|
||||
namespace Drupal\field\Plugin\Type\FieldType;
|
||||
|
||||
use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
|
||||
/**
|
||||
* Custom decorator to add legacy field types.
|
||||
*
|
||||
* Legacy field types are discovered through the old hook_field_info() hook,
|
||||
* and handled by the Drupal\field\Plugin\field\field_type\LegacyConfigFieldItem class.
|
||||
*
|
||||
* @todo Remove once all core field types have been converted (see
|
||||
* http://drupal.org/node/2014671).
|
||||
*/
|
||||
class LegacyFieldTypeDiscoveryDecorator implements DiscoveryInterface {
|
||||
|
||||
/**
|
||||
* The decorated discovery object.
|
||||
*
|
||||
* @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface
|
||||
*/
|
||||
protected $decorated;
|
||||
|
||||
/**
|
||||
* Creates a \Drupal\field\Plugin\Type\FieldType\LegacyFieldTypeDiscoveryDecorator object.
|
||||
*
|
||||
* @param \Drupal\Component\Plugin\Discovery\DiscoveryInterface $discovery
|
||||
* The parent object implementing DiscoveryInterface that is being
|
||||
* decorated.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
*/
|
||||
public function __construct(DiscoveryInterface $decorated, ModuleHandlerInterface $module_handler) {
|
||||
$this->decorated = $decorated;
|
||||
$this->moduleHandler = $module_handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition($plugin_id) {
|
||||
$definitions = $this->getDefinitions();
|
||||
return isset($definitions[$plugin_id]) ? $definitions[$plugin_id] : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinitions() {
|
||||
$definitions = $this->decorated->getDefinitions();
|
||||
|
||||
// We cannot use HookDiscovery, since it uses module_implements(), which
|
||||
// throws exceptions during upgrades.
|
||||
foreach (array_keys($this->moduleHandler->getModuleList()) as $module) {
|
||||
$function = $module . '_field_info';
|
||||
if (function_exists($function)) {
|
||||
foreach ($function() as $plugin_id => $definition) {
|
||||
$definition['id'] = $plugin_id;
|
||||
$definition['module'] = $module;
|
||||
$definition['list_class'] = '\Drupal\field\Plugin\field\field_type\LegacyConfigField';
|
||||
$definitions[$plugin_id] = $definition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $definitions;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -65,9 +65,8 @@ class FormatterPluginManager extends PluginManagerBase {
|
|||
* implementation supports the values 'inline', 'above' and 'hidden'.
|
||||
* Defaults to 'above'.
|
||||
* - type: (string) The formatter to use. Defaults to the
|
||||
* 'default_formatter' for the field type, specified in
|
||||
* hook_field_info(). The default formatter will also be used if the
|
||||
* requested formatter is not available.
|
||||
* 'default_formatter' for the field type, The default formatter will
|
||||
* also be used if the requested formatter is not available.
|
||||
* - settings: (array) Settings specific to the formatter. Each setting
|
||||
* defaults to the default value specified in the formatter definition.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ use Drupal\Core\Entity\EntityInterface;
|
|||
use Drupal\Core\Entity\Field\FieldDefinitionInterface;
|
||||
use Drupal\field\FieldInstanceInterface;
|
||||
use Drupal\field\Plugin\PluginSettingsBase;
|
||||
use Symfony\Component\Validator\ConstraintViolationInterface;
|
||||
|
||||
/**
|
||||
* Base class for 'Field widget' plugin implementations.
|
||||
|
|
@ -68,7 +69,7 @@ abstract class WidgetBase extends PluginSettingsBase implements WidgetInterface
|
|||
$field_state = array(
|
||||
'items_count' => count($items),
|
||||
'array_parents' => array(),
|
||||
'errors' => array(),
|
||||
'constraint_violations' => array(),
|
||||
);
|
||||
field_form_set_state($parents, $field_name, $langcode, $form_state, $field_state);
|
||||
}
|
||||
|
|
@ -315,7 +316,15 @@ abstract class WidgetBase extends PluginSettingsBase implements WidgetInterface
|
|||
$this->sortItems($items);
|
||||
|
||||
// Remove empty values.
|
||||
$items = _field_filter_items($this->fieldDefinition->getFieldType(), $items);
|
||||
if ($entity instanceof \Drupal\Core\Entity\EntityNG) {
|
||||
$itemsNG = \Drupal::typedData()->getPropertyInstance($entity, $field_name, $items);
|
||||
}
|
||||
else {
|
||||
$definitions = \Drupal::entityManager()->getFieldDefinitions($entity->entityType(), $entity->bundle());
|
||||
$itemsNG = \Drupal::typedData()->create($definitions[$field_name], $items, $field_name, $entity);
|
||||
}
|
||||
$itemsNG->filterEmptyValues();
|
||||
$items = $itemsNG->getValue(TRUE);
|
||||
|
||||
// Put delta mapping in $form_state, so that flagErrors() can use it.
|
||||
$field_state = field_form_get_state($form['#parents'], $field_name, $langcode, $form_state);
|
||||
|
|
@ -335,7 +344,7 @@ abstract class WidgetBase extends PluginSettingsBase implements WidgetInterface
|
|||
|
||||
$field_state = field_form_get_state($form['#parents'], $field_name, $langcode, $form_state);
|
||||
|
||||
if (!empty($field_state['errors'])) {
|
||||
if (!empty($field_state['constraint_violations'])) {
|
||||
// Locate the correct element in the the form.
|
||||
$element = NestedArray::getValue($form_state['complete_form'], $field_state['array_parents']);
|
||||
|
||||
|
|
@ -344,7 +353,16 @@ abstract class WidgetBase extends PluginSettingsBase implements WidgetInterface
|
|||
$definition = $this->getPluginDefinition();
|
||||
$is_multiple = $definition['multiple_values'];
|
||||
|
||||
foreach ($field_state['errors'] as $delta => $delta_errors) {
|
||||
$violations_by_delta = array();
|
||||
foreach ($field_state['constraint_violations'] as $violation) {
|
||||
// Separate violations by delta.
|
||||
$property_path = explode('.', $violation->getPropertyPath());
|
||||
$delta = array_shift($property_path);
|
||||
$violation->arrayPropertyPath = $property_path;
|
||||
$violations_by_delta[$delta][] = $violation;
|
||||
}
|
||||
|
||||
foreach ($violations_by_delta as $delta => $delta_violations) {
|
||||
// For a multiple-value widget, pass all errors to the main widget.
|
||||
// For single-value widgets, pass errors by delta.
|
||||
if ($is_multiple) {
|
||||
|
|
@ -354,13 +372,14 @@ abstract class WidgetBase extends PluginSettingsBase implements WidgetInterface
|
|||
$original_delta = $field_state['original_deltas'][$delta];
|
||||
$delta_element = $element[$original_delta];
|
||||
}
|
||||
foreach ($delta_errors as $error) {
|
||||
$error_element = $this->errorElement($delta_element, $error, $form, $form_state);
|
||||
form_error($error_element, $error['message']);
|
||||
foreach ($delta_violations as $violation) {
|
||||
// @todo: Pass $violation->arrayPropertyPath as property path.
|
||||
$error_element = $this->errorElement($delta_element, $violation, $form, $form_state);
|
||||
form_error($error_element, $violation->getMessage());
|
||||
}
|
||||
}
|
||||
// Reinitialize the errors list for the next submit.
|
||||
$field_state['errors'] = array();
|
||||
$field_state['constraint_violations'] = array();
|
||||
field_form_set_state($form['#parents'], $field_name, $langcode, $form_state, $field_state);
|
||||
}
|
||||
}
|
||||
|
|
@ -376,7 +395,7 @@ abstract class WidgetBase extends PluginSettingsBase implements WidgetInterface
|
|||
/**
|
||||
* Implements Drupal\field\Plugin\Type\Widget\WidgetInterface::errorElement().
|
||||
*/
|
||||
public function errorElement(array $element, array $error, array $form, array &$form_state) {
|
||||
public function errorElement(array $element, ConstraintViolationInterface $error, array $form, array &$form_state) {
|
||||
return $element;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ namespace Drupal\field\Plugin\Type\Widget;
|
|||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\field\Plugin\Core\Entity\FieldInstance;
|
||||
use Symfony\Component\Validator\ConstraintViolationInterface;
|
||||
|
||||
/**
|
||||
* Interface definition for field widget plugins.
|
||||
|
|
@ -112,12 +113,8 @@ interface WidgetInterface extends WidgetBaseInterface {
|
|||
* @param array $element
|
||||
* An array containing the form element for the widget, as generated by
|
||||
* formElement().
|
||||
* @param array $error
|
||||
* An associative array with the following key-value pairs, as returned by
|
||||
* hook_field_validate():
|
||||
* - error: the error code. Complex widgets might need to report different
|
||||
* errors to different form elements inside the widget.
|
||||
* - message: the human readable message to be displayed.
|
||||
* @param \Symfony\Component\Validator\ConstraintViolationInterface $violations
|
||||
* The list of constraint violations reported during the validation phase.
|
||||
* @param array $form
|
||||
* The form structure where field elements are attached to. This might be a
|
||||
* full form structure, or a sub-element of a larger form.
|
||||
|
|
@ -127,7 +124,7 @@ interface WidgetInterface extends WidgetBaseInterface {
|
|||
* @return array
|
||||
* The element on which the error should be flagged.
|
||||
*/
|
||||
public function errorElement(array $element, array $error, array $form, array &$form_state);
|
||||
public function errorElement(array $element, ConstraintViolationInterface $violations, array $form, array &$form_state);
|
||||
|
||||
/**
|
||||
* Massages the form values into the format expected for field values.
|
||||
|
|
|
|||
|
|
@ -64,9 +64,8 @@ class WidgetPluginManager extends PluginManagerBase {
|
|||
* following key value pairs are allowed, and are all optional if
|
||||
* 'prepare' is TRUE:
|
||||
* - type: (string) The widget to use. Defaults to the
|
||||
* 'default_widget' for the field type, specified in
|
||||
* hook_field_info(). The default widget will also be used if the
|
||||
* requested widget is not available.
|
||||
* 'default_widget' for the field type. The default widget will also be
|
||||
* used if the requested widget is not available.
|
||||
* - settings: (array) Settings specific to the widget. Each setting
|
||||
* defaults to the default value specified in the widget definition.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -0,0 +1,128 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\field\Plugin\field\field_type\LegacyConfigField.
|
||||
*/
|
||||
|
||||
namespace Drupal\field\Plugin\field\field_type;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\field\Plugin\Type\FieldType\ConfigField;
|
||||
use Symfony\Component\Validator\ConstraintViolation;
|
||||
|
||||
/**
|
||||
* Field class for legacy field types.
|
||||
*
|
||||
* This acts as a temporary BC layer for field types that have not been
|
||||
* converted to Plugins, and bridges new methods to the old-style hook_field_*()
|
||||
* callbacks.
|
||||
*
|
||||
* This class is not discovered by the annotations reader, but referenced by
|
||||
* the Drupal\field\Plugin\Discovery\LegacyDiscoveryDecorator.
|
||||
*
|
||||
* @todo Remove once all core field types have been converted (see
|
||||
* http://drupal.org/node/2014671).
|
||||
*/
|
||||
class LegacyConfigField extends ConfigField {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validate() {
|
||||
$violations = parent::validate();
|
||||
|
||||
// Filter out empty items (legacy hook_field_validate() implementations
|
||||
// used to receive pruned items).
|
||||
$this->filterEmptyValues();
|
||||
|
||||
$legacy_errors = array();
|
||||
$this->legacyCallback('validate', array(&$legacy_errors));
|
||||
|
||||
$entity = $this->getParent();
|
||||
$langcode = $entity->language()->langcode;
|
||||
|
||||
if (isset($legacy_errors[$this->getInstance()->getField()->id()][$langcode])) {
|
||||
foreach ($legacy_errors[$this->getInstance()->getField()->id()][$langcode] as $delta => $item_errors) {
|
||||
foreach ($item_errors as $item_error) {
|
||||
// We do not have the information about which column triggered the
|
||||
// error, so assume the first column...
|
||||
$column = key($this->getInstance()->getField()->getColumns());
|
||||
$violations->add(new ConstraintViolation($item_error['message'], $item_error['message'], array(), $this, $delta . '.' . $column, $this->offsetGet($delta)->get($column)->getValue(), NULL, $item_error['error']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $violations;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function preSave() {
|
||||
// Filter out empty items.
|
||||
$this->filterEmptyValues();
|
||||
|
||||
$this->legacyCallback('presave');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function insert() {
|
||||
$this->legacyCallback('insert');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function update() {
|
||||
$this->legacyCallback('update');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete() {
|
||||
$this->legacyCallback('delete');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteRevision() {
|
||||
$this->legacyCallback('delete_revision');
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the legacy callback for a given field type "hook", if it exists.
|
||||
*
|
||||
* @param string $hook
|
||||
* The name of the hook, e.g. 'presave', 'validate'.
|
||||
*/
|
||||
protected function legacyCallback($hook, $args = array()) {
|
||||
$definition = $this->getPluginDefinition();
|
||||
$module = $definition['module'];
|
||||
$callback = "{$module}_field_{$hook}";
|
||||
if (function_exists($callback)) {
|
||||
$entity = $this->getParent();
|
||||
$langcode = $entity->language()->langcode;
|
||||
|
||||
// We need to remove the empty "prototype" item here.
|
||||
// @todo Revisit after http://drupal.org/node/1988492.
|
||||
$this->filterEmptyValues();
|
||||
// Legcacy callbacks alter $items by reference.
|
||||
$items = (array) $this->getValue(TRUE);
|
||||
$args = array_merge(array(
|
||||
$entity,
|
||||
$this->getInstance()->getField(),
|
||||
$this->getInstance(),
|
||||
$langcode,
|
||||
&$items
|
||||
), $args);
|
||||
call_user_func_array($callback, $args);
|
||||
$this->setValue($items);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\field\Plugin\field\field_type\LegacyConfigFieldItem.
|
||||
*/
|
||||
|
||||
namespace Drupal\field\Plugin\field\field_type;
|
||||
|
||||
use Drupal\field\Plugin\Type\FieldType\ConfigFieldItemBase;
|
||||
use Drupal\field\Plugin\Core\Entity\Field;
|
||||
|
||||
/**
|
||||
* Plugin implementation for legacy field types.
|
||||
*
|
||||
* This special implementation acts as a temporary BC layer for field types
|
||||
* that have not been converted to Plugins, and bridges new methods to the
|
||||
* old-style hook_field_*() callbacks.
|
||||
*
|
||||
* This class is not discovered by the annotations reader, but referenced by
|
||||
* the Drupal\field\Plugin\Discovery\LegacyDiscoveryDecorator.
|
||||
*
|
||||
* @todo Remove once all core field types have been converted (see
|
||||
* http://drupal.org/node/2014671).
|
||||
*/
|
||||
abstract class LegacyConfigFieldItem extends ConfigFieldItemBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function schema(Field $field) {
|
||||
$definition = \Drupal::service('plugin.manager.entity.field.field_type')->getDefinition($field->type);
|
||||
$module = $definition['module'];
|
||||
module_load_install($module);
|
||||
$callback = "{$module}_field_schema";
|
||||
if (function_exists($callback)) {
|
||||
return $callback($field);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isEmpty() {
|
||||
$callback = $this->getLegacyCallback('is_empty');
|
||||
// Make sure the array received by the legacy callback includes computed
|
||||
// properties.
|
||||
$item = $this->getValue(TRUE);
|
||||
// The previous hook was never called on an empty item, but EntityNG always
|
||||
// creates a FieldItem element for an empty field.
|
||||
return empty($item) || $callback($item, $this->getInstance()->getField()->type);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsForm(array $form, array &$form_state) {
|
||||
if ($callback = $this->getLegacyCallback('settings_form')) {
|
||||
// hook_field_settings_form() used to receive the $instance (not actually
|
||||
// needed), and the value of field_has_data().
|
||||
return $callback($this->getInstance()->getField(), $this->getInstance(), $this->getInstance()->getField()->hasData());
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function instanceSettingsForm(array $form, array &$form_state) {
|
||||
if ($callback = $this->getLegacyCallback('instance_settings_form')) {
|
||||
return $callback($this->getInstance()->getField(), $this->getInstance(), $form_state);
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Massages loaded field values before they enter the field cache.
|
||||
*
|
||||
* This implements the prepareCache() method defined in PrepareCacheInterface
|
||||
* even if the class does explicitly implements it, so as to preserve
|
||||
* the optimizations of only creating Field and FieldItem objects and invoking
|
||||
* the method if are actually needed.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\DatabaseStorageController::invokeFieldItemPrepareCache()
|
||||
*/
|
||||
public function prepareCache() {
|
||||
if ($callback = $this->getLegacyCallback('load')) {
|
||||
$entity = $this->getParent()->getParent();
|
||||
$langcode = $entity->language()->langcode;
|
||||
$entity_id = $entity->id();
|
||||
|
||||
// hook_field_attach_load() receives items keyed by entity id, and alter
|
||||
// then by reference.
|
||||
$items = array($entity_id => array(0 => $this->getValue(TRUE)));
|
||||
$args = array(
|
||||
$entity->entityType(),
|
||||
array($entity_id => $entity),
|
||||
$this->getInstance()->getField(),
|
||||
array($entity_id => $this->getInstance()),
|
||||
$langcode,
|
||||
&$items,
|
||||
FIELD_LOAD_CURRENT,
|
||||
);
|
||||
call_user_func_array($callback, $args);
|
||||
$this->setValue($items[$entity_id][0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inherotdoc}
|
||||
*/
|
||||
public static function prepareView(array $entities_items) {
|
||||
if ($entities_items) {
|
||||
// Determine the legacy callback.
|
||||
$field_type_definition = current($entities_items)->getPluginDefinition();
|
||||
$module = $field_type_definition['module'];
|
||||
$callback = "{$module}_field_prepare_view";
|
||||
if (function_exists($callback)) {
|
||||
$entities = array();
|
||||
$instances = array();
|
||||
$itemsBC = array();
|
||||
foreach ($entities_items as $id => $items) {
|
||||
$entities[$id] = $items->getParent();
|
||||
$instances[$id] = $items->offsetGet(0)->getInstance();
|
||||
// We need to remove the empty "prototype" item here.
|
||||
// @todo Revisit after http://drupal.org/node/1988492.
|
||||
$items->filterEmptyValues();
|
||||
$itemsBC[$id] = $items->getValue(TRUE);
|
||||
}
|
||||
|
||||
// Determine the entity type, langcode and field.
|
||||
$entity_type = current($entities)->entityType();
|
||||
$langcode = current($entities)->language()->langcode;
|
||||
$field = current($instances)->getField();
|
||||
|
||||
$args = array(
|
||||
$entity_type,
|
||||
$entities,
|
||||
$field,
|
||||
$instances,
|
||||
$langcode,
|
||||
&$itemsBC,
|
||||
);
|
||||
call_user_func_array($callback, $args);
|
||||
|
||||
foreach ($entities_items as $id => $items) {
|
||||
$items->setValue($itemsBC[$id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the legacy callback for a given field type "hook".
|
||||
*
|
||||
* @param string $hook
|
||||
* The name of the hook, e.g. 'settings_form', 'is_empty'.
|
||||
*
|
||||
* @return string|null
|
||||
* The name of the legacy callback, or NULL if it does not exist.
|
||||
*/
|
||||
protected function getLegacyCallback($hook) {
|
||||
$definition = $this->getPluginDefinition();
|
||||
$module = $definition['module'];
|
||||
$callback = "{$module}_field_{$hook}";
|
||||
if (function_exists($callback)) {
|
||||
return $callback;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -205,7 +205,8 @@ class BulkDeleteTest extends FieldUnitTestBase {
|
|||
// The instance still exists, deleted.
|
||||
$instances = field_read_instances(array('field_id' => $field->uuid, 'deleted' => TRUE), array('include_deleted' => TRUE, 'include_inactive' => TRUE));
|
||||
$this->assertEqual(count($instances), 1, 'There is one deleted instance');
|
||||
$this->assertEqual($instances[0]['bundle'], $bundle, 'The deleted instance is for the correct bundle');
|
||||
$instance = $instances[0];
|
||||
$this->assertEqual($instance['bundle'], $bundle, 'The deleted instance is for the correct bundle');
|
||||
|
||||
// There are 0 entities of this bundle with non-deleted data.
|
||||
$found = $factory->get('test_entity')
|
||||
|
|
@ -230,7 +231,7 @@ class BulkDeleteTest extends FieldUnitTestBase {
|
|||
$ids->entity_id = $entity_id;
|
||||
$entities[$entity_id] = _field_create_entity_from_ids($ids);
|
||||
}
|
||||
field_attach_load($this->entity_type, $entities, FIELD_LOAD_CURRENT, array('field_id' => $field->uuid, 'deleted' => TRUE));
|
||||
field_attach_load($this->entity_type, $entities, FIELD_LOAD_CURRENT, array('instance' => $instance));
|
||||
$this->assertEqual(count($found), 10, 'Correct number of entities found after deleting');
|
||||
foreach ($entities as $id => $entity) {
|
||||
$this->assertEqual($this->entities[$id]->{$field->id()}, $entity->{$field->id()}, "Entity $id with deleted data loaded correctly");
|
||||
|
|
@ -270,17 +271,13 @@ class BulkDeleteTest extends FieldUnitTestBase {
|
|||
}
|
||||
|
||||
// Check hooks invocations.
|
||||
// - hook_field_load() (multiple hook) should have been called on all
|
||||
// entities by pairs of two.
|
||||
// - hook_field_delete() should have been called once for each entity in the
|
||||
// bundle.
|
||||
// hook_field_load() and hook_field_delete() should have been called once
|
||||
// for each entity in the bundle.
|
||||
$actual_hooks = field_test_memorize();
|
||||
$hooks = array();
|
||||
$entities = $this->convertToPartialEntities($this->entities_by_bundles[$bundle], $field->id());
|
||||
foreach (array_chunk($entities, $batch_size, TRUE) as $chunk_entity) {
|
||||
$hooks['field_test_field_load'][] = $chunk_entity;
|
||||
}
|
||||
foreach ($entities as $entity) {
|
||||
foreach ($entities as $id => $entity) {
|
||||
$hooks['field_test_field_load'][] = array($id => $entity);
|
||||
$hooks['field_test_field_delete'][] = $entity;
|
||||
}
|
||||
$this->checkHooksInvocations($hooks, $actual_hooks);
|
||||
|
|
@ -324,15 +321,15 @@ class BulkDeleteTest extends FieldUnitTestBase {
|
|||
field_purge_batch(10);
|
||||
|
||||
// Check hooks invocations.
|
||||
// - hook_field_load() (multiple hook) should have been called once, for all
|
||||
// entities in the bundle.
|
||||
// - hook_field_delete() should have been called once for each entity in the
|
||||
// bundle.
|
||||
// hook_field_load() and hook_field_delete() should have been called once
|
||||
// for each entity in the bundle.
|
||||
$actual_hooks = field_test_memorize();
|
||||
$hooks = array();
|
||||
$entities = $this->convertToPartialEntities($this->entities_by_bundles[$bundle], $field->id());
|
||||
$hooks['field_test_field_load'][] = $entities;
|
||||
$hooks['field_test_field_delete'] = $entities;
|
||||
foreach ($entities as $id => $entity) {
|
||||
$hooks['field_test_field_load'][] = array($id => $entity);
|
||||
$hooks['field_test_field_delete'][] = $entity;
|
||||
}
|
||||
$this->checkHooksInvocations($hooks, $actual_hooks);
|
||||
|
||||
// Purge again to purge the instance.
|
||||
|
|
@ -358,8 +355,10 @@ class BulkDeleteTest extends FieldUnitTestBase {
|
|||
$actual_hooks = field_test_memorize();
|
||||
$hooks = array();
|
||||
$entities = $this->convertToPartialEntities($this->entities_by_bundles[$bundle], $field->id());
|
||||
$hooks['field_test_field_load'][] = $entities;
|
||||
$hooks['field_test_field_delete'] = $entities;
|
||||
foreach ($entities as $id => $entity) {
|
||||
$hooks['field_test_field_load'][] = array($id => $entity);
|
||||
$hooks['field_test_field_delete'][] = $entity;
|
||||
}
|
||||
$this->checkHooksInvocations($hooks, $actual_hooks);
|
||||
|
||||
// The field still exists, deleted.
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
namespace Drupal\field\Tests;
|
||||
|
||||
use Drupal\Core\Language\Language;
|
||||
use Drupal\field\FieldValidationException;
|
||||
|
||||
/**
|
||||
* Unit test class for non-storage related field_attach_* functions.
|
||||
|
|
@ -99,21 +98,6 @@ class FieldAttachOtherTest extends FieldUnitTestBase {
|
|||
$this->content = $output;
|
||||
$this->assertRaw("$formatter_setting_2|{$value['value']}", "Value $delta is displayed, formatter settings are applied.");
|
||||
}
|
||||
// View single field (the second field).
|
||||
field_attach_prepare_view($entity_type, array($entity->ftid => $entity), $displays, $langcode, $options);
|
||||
$entity->content = field_attach_view($entity, $display, $langcode, $options);
|
||||
$output = drupal_render($entity->content);
|
||||
$this->content = $output;
|
||||
$this->assertNoRaw($this->instance['label'], "First field's label is not displayed.");
|
||||
foreach ($values as $delta => $value) {
|
||||
$this->content = $output;
|
||||
$this->assertNoRaw("$formatter_setting|{$value['value']}", "Value $delta is displayed, formatter settings are applied.");
|
||||
}
|
||||
$this->assertRaw($this->instance_2['label'], "Second field's label is displayed.");
|
||||
foreach ($values_2 as $delta => $value) {
|
||||
$this->content = $output;
|
||||
$this->assertRaw("$formatter_setting_2|{$value['value']}", "Value $delta is displayed, formatter settings are applied.");
|
||||
}
|
||||
|
||||
// Label hidden.
|
||||
$entity = clone($entity_init);
|
||||
|
|
@ -299,9 +283,10 @@ class FieldAttachOtherTest extends FieldUnitTestBase {
|
|||
|
||||
// Load a single field, and check that no cache entry is present.
|
||||
$entity = clone($entity_init);
|
||||
field_attach_load($entity_type, array($entity->ftid => $entity), FIELD_LOAD_CURRENT, array('field_id' => $this->field_id));
|
||||
$instance = field_info_instance($entity->entityType(), $this->field_name, $entity->bundle());
|
||||
field_attach_load($entity_type, array($entity->ftid => $entity), FIELD_LOAD_CURRENT, array('instance' => $instance));
|
||||
$cache = cache('field')->get($cid);
|
||||
$this->assertFalse(cache('field')->get($cid), 'Cached: no cache entry on loading a single field');
|
||||
$this->assertFalse($cache, 'Cached: no cache entry on loading a single field');
|
||||
|
||||
// Load, and check that a cache entry is present with the expected values.
|
||||
$entity = clone($entity_init);
|
||||
|
|
@ -346,98 +331,6 @@ class FieldAttachOtherTest extends FieldUnitTestBase {
|
|||
$this->assertFalse(cache('field')->get($cid), 'Cached: no cache entry after delete');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test field_attach_validate().
|
||||
*
|
||||
* Verify that field_attach_validate() invokes the correct
|
||||
* hook_field_validate.
|
||||
*/
|
||||
function testFieldAttachValidate() {
|
||||
$this->createFieldWithInstance('_2');
|
||||
|
||||
$entity_type = 'test_entity';
|
||||
$entity = field_test_create_entity(0, 0, $this->instance['bundle']);
|
||||
$langcode = Language::LANGCODE_NOT_SPECIFIED;
|
||||
|
||||
// Set up all but one values of the first field to generate errors.
|
||||
$values = array();
|
||||
for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
|
||||
$values[$delta]['value'] = -1;
|
||||
}
|
||||
// Arrange for item 1 not to generate an error.
|
||||
$values[1]['value'] = 1;
|
||||
$entity->{$this->field_name}[$langcode] = $values;
|
||||
|
||||
// Set up all values of the second field to generate errors.
|
||||
$values_2 = array();
|
||||
for ($delta = 0; $delta < $this->field_2['cardinality']; $delta++) {
|
||||
$values_2[$delta]['value'] = -1;
|
||||
}
|
||||
$entity->{$this->field_name_2}[$langcode] = $values_2;
|
||||
|
||||
// Validate all fields.
|
||||
try {
|
||||
field_attach_validate($entity);
|
||||
}
|
||||
catch (FieldValidationException $e) {
|
||||
$errors = $e->errors;
|
||||
}
|
||||
|
||||
foreach ($values as $delta => $value) {
|
||||
if ($value['value'] != 1) {
|
||||
$this->assertIdentical($errors[$this->field_name][$langcode][$delta][0]['error'], 'field_test_invalid', "Error set on first field's value $delta");
|
||||
$this->assertEqual(count($errors[$this->field_name][$langcode][$delta]), 1, "Only one error set on first field's value $delta");
|
||||
unset($errors[$this->field_name][$langcode][$delta]);
|
||||
}
|
||||
else {
|
||||
$this->assertFalse(isset($errors[$this->field_name][$langcode][$delta]), "No error set on first field's value $delta");
|
||||
}
|
||||
}
|
||||
foreach ($values_2 as $delta => $value) {
|
||||
$this->assertIdentical($errors[$this->field_name_2][$langcode][$delta][0]['error'], 'field_test_invalid', "Error set on second field's value $delta");
|
||||
$this->assertEqual(count($errors[$this->field_name_2][$langcode][$delta]), 1, "Only one error set on second field's value $delta");
|
||||
unset($errors[$this->field_name_2][$langcode][$delta]);
|
||||
}
|
||||
$this->assertEqual(count($errors[$this->field_name][$langcode]), 0, 'No extraneous errors set for first field');
|
||||
$this->assertEqual(count($errors[$this->field_name_2][$langcode]), 0, 'No extraneous errors set for second field');
|
||||
|
||||
// Validate a single field.
|
||||
$options = array('field_name' => $this->field_name_2);
|
||||
try {
|
||||
field_attach_validate($entity, $options);
|
||||
}
|
||||
catch (FieldValidationException $e) {
|
||||
$errors = $e->errors;
|
||||
}
|
||||
|
||||
foreach ($values_2 as $delta => $value) {
|
||||
$this->assertIdentical($errors[$this->field_name_2][$langcode][$delta][0]['error'], 'field_test_invalid', "Error set on second field's value $delta");
|
||||
$this->assertEqual(count($errors[$this->field_name_2][$langcode][$delta]), 1, "Only one error set on second field's value $delta");
|
||||
unset($errors[$this->field_name_2][$langcode][$delta]);
|
||||
}
|
||||
$this->assertFalse(isset($errors[$this->field_name]), 'No validation errors are set for the first field, despite it having errors');
|
||||
$this->assertEqual(count($errors[$this->field_name_2][$langcode]), 0, 'No extraneous errors set for second field');
|
||||
|
||||
// Check that cardinality is validated.
|
||||
$entity->{$this->field_name_2}[$langcode] = $this->_generateTestFieldValues($this->field_2['cardinality'] + 1);
|
||||
// When validating all fields.
|
||||
try {
|
||||
field_attach_validate($entity);
|
||||
}
|
||||
catch (FieldValidationException $e) {
|
||||
$errors = $e->errors;
|
||||
}
|
||||
$this->assertEqual($errors[$this->field_name_2][$langcode][0][0]['error'], 'field_cardinality', 'Cardinality validation failed.');
|
||||
// When validating a single field (the second field).
|
||||
try {
|
||||
field_attach_validate($entity, $options);
|
||||
}
|
||||
catch (FieldValidationException $e) {
|
||||
$errors = $e->errors;
|
||||
}
|
||||
$this->assertEqual($errors[$this->field_name_2][$langcode][0][0]['error'], 'field_cardinality', 'Cardinality validation failed.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test field_attach_form().
|
||||
*
|
||||
|
|
|
|||
|
|
@ -175,7 +175,8 @@ class FieldAttachStorageTest extends FieldUnitTestBase {
|
|||
|
||||
// Check that the single-field load option works.
|
||||
$entity = field_test_create_entity(1, 1, $bundles[1]);
|
||||
field_attach_load($entity_type, array(1 => $entity), FIELD_LOAD_CURRENT, array('field_id' => $field_ids[1]));
|
||||
$instance = field_info_instance($entity->entityType(), $field_names[1], $entity->bundle());
|
||||
field_attach_load($entity_type, array(1 => $entity), FIELD_LOAD_CURRENT, array('instance' => $instance));
|
||||
$this->assertEqual($entity->{$field_names[1]}[$langcode][0]['value'], $values[1][$field_names[1]], format_string('Entity %index: expected value was found.', array('%index' => 1)));
|
||||
$this->assertEqual($entity->{$field_names[1]}[$langcode][0]['additional_key'], 'additional_value', format_string('Entity %index: extra information was found', array('%index' => 1)));
|
||||
$this->assert(!isset($entity->{$field_names[2]}), format_string('Entity %index: field %field_name is not loaded.', array('%index' => 2, '%field_name' => $field_names[2])));
|
||||
|
|
|
|||
|
|
@ -48,8 +48,14 @@ abstract class FieldUnitTestBase extends DrupalUnitTestBase {
|
|||
* @param string $suffix
|
||||
* (optional) A string that should only contain characters that are valid in
|
||||
* PHP variable names as well.
|
||||
* @param string $entity_type
|
||||
* (optional) The entity type on which the instance should be created.
|
||||
* Defaults to 'test_entity'.
|
||||
* @param string $bundle
|
||||
* (optional) The entity type on which the instance should be created.
|
||||
* Defaults to 'test_bundle'.
|
||||
*/
|
||||
function createFieldWithInstance($suffix = '') {
|
||||
function createFieldWithInstance($suffix = '', $entity_type = 'test_entity', $bundle = 'test_bundle') {
|
||||
$field_name = 'field_name' . $suffix;
|
||||
$field = 'field' . $suffix;
|
||||
$field_id = 'field_id' . $suffix;
|
||||
|
|
@ -62,11 +68,10 @@ abstract class FieldUnitTestBase extends DrupalUnitTestBase {
|
|||
$this->$field_id = $this->{$field}['uuid'];
|
||||
$this->$instance_definition = array(
|
||||
'field_name' => $this->$field_name,
|
||||
'entity_type' => 'test_entity',
|
||||
'bundle' => 'test_bundle',
|
||||
'entity_type' => $entity_type,
|
||||
'bundle' => $bundle,
|
||||
'label' => $this->randomName() . '_label',
|
||||
'description' => $this->randomName() . '_description',
|
||||
'weight' => mt_rand(0, 127),
|
||||
'settings' => array(
|
||||
'test_instance_setting' => $this->randomName(),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\field\Tests\FieldValidationTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\field\Tests;
|
||||
|
||||
use Drupal\field\Tests\FieldUnitTestBase;
|
||||
|
||||
/**
|
||||
* Unit test class for field validation.
|
||||
*/
|
||||
class FieldValidationTest extends FieldUnitTestBase {
|
||||
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Field validation',
|
||||
'description' => 'Tests field validation.',
|
||||
'group' => 'Field API',
|
||||
);
|
||||
}
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Create a field and instance of type 'test_field', on the 'entity_test'
|
||||
// entity type.
|
||||
$this->entityType = 'entity_test';
|
||||
$this->bundle = 'entity_test';
|
||||
$this->createFieldWithInstance('', $this->entityType, $this->bundle);
|
||||
|
||||
// Create an 'entity_test' entity.
|
||||
$this->entity = entity_create($this->entityType, array(
|
||||
'type' => $this->bundle,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the number of values is validated against the field cardinality.
|
||||
*/
|
||||
function testCardinalityConstraint() {
|
||||
$cardinality = $this->field->cardinality;
|
||||
$entity = $this->entity;
|
||||
|
||||
for ($delta = 0; $delta < $cardinality + 1; $delta++) {
|
||||
$entity->{$this->field_name}->offsetGet($delta)->set('value', 1);
|
||||
}
|
||||
|
||||
// Validate the field.
|
||||
$violations = $entity->{$this->field_name}->validate();
|
||||
|
||||
// Check that the expected constraint violations are reported.
|
||||
$this->assertEqual(count($violations), 1);
|
||||
$this->assertEqual($violations[0]->getPropertyPath(), '');
|
||||
$this->assertEqual($violations[0]->getMessage(), t('%name: this field cannot hold more than @count values.', array('%name' => $this->instance['label'], '@count' => $cardinality)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that constraints defined by the field type are validated.
|
||||
*/
|
||||
function testFieldConstraints() {
|
||||
$cardinality = $this->field->cardinality;
|
||||
$entity = $this->entity;
|
||||
|
||||
// The test is only valid if the field cardinality is greater than 2.
|
||||
$this->assertTrue($cardinality >= 2);
|
||||
|
||||
// Set up values for the field.
|
||||
$expected_violations = array();
|
||||
for ($delta = 0; $delta < $cardinality; $delta++) {
|
||||
// All deltas except '1' have incorrect values.
|
||||
if ($delta == 1) {
|
||||
$value = 1;
|
||||
}
|
||||
else {
|
||||
$value = -1;
|
||||
$expected_violations[$delta . '.value'][] = t('%name does not accept the value -1.', array('%name' => $this->instance['label']));
|
||||
}
|
||||
$entity->{$this->field_name}->offsetGet($delta)->set('value', $value);
|
||||
}
|
||||
|
||||
// Validate the field.
|
||||
$violations = $entity->{$this->field_name}->validate();
|
||||
|
||||
// Check that the expected constraint violations are reported.
|
||||
$violations_by_path = array();
|
||||
foreach ($violations as $violation) {
|
||||
$violations_by_path[$violation->getPropertyPath()][] = $violation->getMessage();
|
||||
}
|
||||
$this->assertEqual($violations_by_path, $expected_violations);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -524,121 +524,6 @@ class FormTest extends FieldTestBase {
|
|||
$this->assertEqual($entity->{$field_name}[$langcode][0]['value'], 2, 'New revision has the expected value for the field with edit access.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Field API form integration within a subform.
|
||||
*/
|
||||
function testNestedFieldForm() {
|
||||
// Add two instances on the 'test_bundle'
|
||||
entity_create('field_entity', $this->field_single)->save();
|
||||
entity_create('field_entity', $this->field_unlimited)->save();
|
||||
$this->instance['field_name'] = 'field_single';
|
||||
$this->instance['label'] = 'Single field';
|
||||
entity_create('field_instance', $this->instance)->save();
|
||||
entity_get_form_display($this->instance['entity_type'], $this->instance['bundle'], 'default')
|
||||
->setComponent($this->instance['field_name'])
|
||||
->save();
|
||||
$this->instance['field_name'] = 'field_unlimited';
|
||||
$this->instance['label'] = 'Unlimited field';
|
||||
entity_create('field_instance', $this->instance)->save();
|
||||
entity_get_form_display($this->instance['entity_type'], $this->instance['bundle'], 'default')
|
||||
->setComponent($this->instance['field_name'])
|
||||
->save();
|
||||
|
||||
// Create two entities.
|
||||
$entity_1 = field_test_create_entity(1, 1);
|
||||
$entity_1->is_new = TRUE;
|
||||
$entity_1->field_single[Language::LANGCODE_NOT_SPECIFIED][] = array('value' => 0);
|
||||
$entity_1->field_unlimited[Language::LANGCODE_NOT_SPECIFIED][] = array('value' => 1);
|
||||
field_test_entity_save($entity_1);
|
||||
|
||||
$entity_2 = field_test_create_entity(2, 2);
|
||||
$entity_2->is_new = TRUE;
|
||||
$entity_2->field_single[Language::LANGCODE_NOT_SPECIFIED][] = array('value' => 10);
|
||||
$entity_2->field_unlimited[Language::LANGCODE_NOT_SPECIFIED][] = array('value' => 11);
|
||||
field_test_entity_save($entity_2);
|
||||
|
||||
// Display the 'combined form'.
|
||||
$this->drupalGet('test-entity/nested/1/2');
|
||||
$this->assertFieldByName('field_single[und][0][value]', 0, 'Entity 1: field_single value appears correctly is the form.');
|
||||
$this->assertFieldByName('field_unlimited[und][0][value]', 1, 'Entity 1: field_unlimited value 0 appears correctly is the form.');
|
||||
$this->assertFieldByName('entity_2[field_single][und][0][value]', 10, 'Entity 2: field_single value appears correctly is the form.');
|
||||
$this->assertFieldByName('entity_2[field_unlimited][und][0][value]', 11, 'Entity 2: field_unlimited value 0 appears correctly is the form.');
|
||||
|
||||
// Submit the form and check that the entities are updated accordingly.
|
||||
$edit = array(
|
||||
'field_single[und][0][value]' => 1,
|
||||
'field_unlimited[und][0][value]' => 2,
|
||||
'field_unlimited[und][1][value]' => 3,
|
||||
'entity_2[field_single][und][0][value]' => 11,
|
||||
'entity_2[field_unlimited][und][0][value]' => 12,
|
||||
'entity_2[field_unlimited][und][1][value]' => 13,
|
||||
);
|
||||
$this->drupalPost(NULL, $edit, t('Save'));
|
||||
field_cache_clear();
|
||||
$entity_1 = field_test_create_entity(1);
|
||||
$entity_2 = field_test_create_entity(2);
|
||||
$this->assertFieldValues($entity_1, 'field_single', Language::LANGCODE_NOT_SPECIFIED, array(1));
|
||||
$this->assertFieldValues($entity_1, 'field_unlimited', Language::LANGCODE_NOT_SPECIFIED, array(2, 3));
|
||||
$this->assertFieldValues($entity_2, 'field_single', Language::LANGCODE_NOT_SPECIFIED, array(11));
|
||||
$this->assertFieldValues($entity_2, 'field_unlimited', Language::LANGCODE_NOT_SPECIFIED, array(12, 13));
|
||||
|
||||
// Submit invalid values and check that errors are reported on the
|
||||
// correct widgets.
|
||||
$edit = array(
|
||||
'field_unlimited[und][1][value]' => -1,
|
||||
);
|
||||
$this->drupalPost('test-entity/nested/1/2', $edit, t('Save'));
|
||||
$this->assertRaw(t('%label does not accept the value -1', array('%label' => 'Unlimited field')), 'Entity 1: the field validation error was reported.');
|
||||
$error_field = $this->xpath('//input[@id=:id and contains(@class, "error")]', array(':id' => 'edit-field-unlimited-und-1-value'));
|
||||
$this->assertTrue($error_field, 'Entity 1: the error was flagged on the correct element.');
|
||||
$edit = array(
|
||||
'entity_2[field_unlimited][und][1][value]' => -1,
|
||||
);
|
||||
$this->drupalPost('test-entity/nested/1/2', $edit, t('Save'));
|
||||
$this->assertRaw(t('%label does not accept the value -1', array('%label' => 'Unlimited field')), 'Entity 2: the field validation error was reported.');
|
||||
$error_field = $this->xpath('//input[@id=:id and contains(@class, "error")]', array(':id' => 'edit-entity-2-field-unlimited-und-1-value'));
|
||||
$this->assertTrue($error_field, 'Entity 2: the error was flagged on the correct element.');
|
||||
|
||||
// Test that reordering works on both entities.
|
||||
$edit = array(
|
||||
'field_unlimited[und][0][_weight]' => 0,
|
||||
'field_unlimited[und][1][_weight]' => -1,
|
||||
'entity_2[field_unlimited][und][0][_weight]' => 0,
|
||||
'entity_2[field_unlimited][und][1][_weight]' => -1,
|
||||
);
|
||||
$this->drupalPost('test-entity/nested/1/2', $edit, t('Save'));
|
||||
field_cache_clear();
|
||||
$this->assertFieldValues($entity_1, 'field_unlimited', Language::LANGCODE_NOT_SPECIFIED, array(3, 2));
|
||||
$this->assertFieldValues($entity_2, 'field_unlimited', Language::LANGCODE_NOT_SPECIFIED, array(13, 12));
|
||||
|
||||
// Test the 'add more' buttons. Only Ajax submission is tested, because
|
||||
// the two 'add more' buttons present in the form have the same #value,
|
||||
// which confuses drupalPost().
|
||||
// 'Add more' button in the first entity:
|
||||
$this->drupalGet('test-entity/nested/1/2');
|
||||
$this->drupalPostAJAX(NULL, array(), 'field_unlimited_add_more');
|
||||
$this->assertFieldByName('field_unlimited[und][0][value]', 3, 'Entity 1: field_unlimited value 0 appears correctly is the form.');
|
||||
$this->assertFieldByName('field_unlimited[und][1][value]', 2, 'Entity 1: field_unlimited value 1 appears correctly is the form.');
|
||||
$this->assertFieldByName('field_unlimited[und][2][value]', '', 'Entity 1: field_unlimited value 2 appears correctly is the form.');
|
||||
$this->assertFieldByName('field_unlimited[und][3][value]', '', 'Entity 1: an empty widget was added for field_unlimited value 3.');
|
||||
// 'Add more' button in the first entity (changing field values):
|
||||
$edit = array(
|
||||
'entity_2[field_unlimited][und][0][value]' => 13,
|
||||
'entity_2[field_unlimited][und][1][value]' => 14,
|
||||
'entity_2[field_unlimited][und][2][value]' => 15,
|
||||
);
|
||||
$this->drupalPostAJAX(NULL, $edit, 'entity_2_field_unlimited_add_more');
|
||||
$this->assertFieldByName('entity_2[field_unlimited][und][0][value]', 13, 'Entity 2: field_unlimited value 0 appears correctly is the form.');
|
||||
$this->assertFieldByName('entity_2[field_unlimited][und][1][value]', 14, 'Entity 2: field_unlimited value 1 appears correctly is the form.');
|
||||
$this->assertFieldByName('entity_2[field_unlimited][und][2][value]', 15, 'Entity 2: field_unlimited value 2 appears correctly is the form.');
|
||||
$this->assertFieldByName('entity_2[field_unlimited][und][3][value]', '', 'Entity 2: an empty widget was added for field_unlimited value 3.');
|
||||
// Save the form and check values are saved correclty.
|
||||
$this->drupalPost(NULL, array(), t('Save'));
|
||||
field_cache_clear();
|
||||
$this->assertFieldValues($entity_1, 'field_unlimited', Language::LANGCODE_NOT_SPECIFIED, array(3, 2));
|
||||
$this->assertFieldValues($entity_2, 'field_unlimited', Language::LANGCODE_NOT_SPECIFIED, array(13, 14, 15));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the Hidden widget.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,197 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\field\Tests\NestedFormTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\field\Tests;
|
||||
|
||||
use Drupal\Core\Language\Language;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
|
||||
class NestedFormTest extends FieldTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('field_test', 'entity_test');
|
||||
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Nested form',
|
||||
'description' => 'Test the support for field elements in nested forms.',
|
||||
'group' => 'Field API',
|
||||
);
|
||||
}
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$web_user = $this->drupalCreateUser(array('view test entity', 'administer entity_test content'));
|
||||
$this->drupalLogin($web_user);
|
||||
|
||||
$this->field_single = array('field_name' => 'field_single', 'type' => 'test_field');
|
||||
$this->field_unlimited = array('field_name' => 'field_unlimited', 'type' => 'test_field', 'cardinality' => FIELD_CARDINALITY_UNLIMITED);
|
||||
|
||||
$this->instance = array(
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'label' => $this->randomName() . '_label',
|
||||
'description' => '[site:name]_description',
|
||||
'weight' => mt_rand(0, 127),
|
||||
'settings' => array(
|
||||
'test_instance_setting' => $this->randomName(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Field API form integration within a subform.
|
||||
*/
|
||||
function testNestedFieldForm() {
|
||||
// Add two instances on the 'entity_test'
|
||||
entity_create('field_entity', $this->field_single)->save();
|
||||
entity_create('field_entity', $this->field_unlimited)->save();
|
||||
$this->instance['field_name'] = 'field_single';
|
||||
$this->instance['label'] = 'Single field';
|
||||
entity_create('field_instance', $this->instance)->save();
|
||||
entity_get_form_display($this->instance['entity_type'], $this->instance['bundle'], 'default')
|
||||
->setComponent($this->instance['field_name'])
|
||||
->save();
|
||||
$this->instance['field_name'] = 'field_unlimited';
|
||||
$this->instance['label'] = 'Unlimited field';
|
||||
entity_create('field_instance', $this->instance)->save();
|
||||
entity_get_form_display($this->instance['entity_type'], $this->instance['bundle'], 'default')
|
||||
->setComponent($this->instance['field_name'])
|
||||
->save();
|
||||
|
||||
// Create two entities.
|
||||
$entity_type = 'entity_test';
|
||||
$entity_1 = entity_create($entity_type, array('id' => 1));
|
||||
$entity_1->enforceIsNew();
|
||||
$entity_1->field_single->value = 0;
|
||||
$entity_1->field_unlimited->value = 1;
|
||||
$entity_1->save();
|
||||
|
||||
$entity_2 = entity_create($entity_type, array('id' => 2));
|
||||
$entity_2->enforceIsNew();
|
||||
$entity_2->field_single->value = 10;
|
||||
$entity_2->field_unlimited->value = 11;
|
||||
$entity_2->save();
|
||||
|
||||
// Display the 'combined form'.
|
||||
$this->drupalGet('test-entity/nested/1/2');
|
||||
$this->assertFieldByName('field_single[und][0][value]', 0, 'Entity 1: field_single value appears correctly is the form.');
|
||||
$this->assertFieldByName('field_unlimited[und][0][value]', 1, 'Entity 1: field_unlimited value 0 appears correctly is the form.');
|
||||
$this->assertFieldByName('entity_2[field_single][und][0][value]', 10, 'Entity 2: field_single value appears correctly is the form.');
|
||||
$this->assertFieldByName('entity_2[field_unlimited][und][0][value]', 11, 'Entity 2: field_unlimited value 0 appears correctly is the form.');
|
||||
|
||||
// Submit the form and check that the entities are updated accordingly.
|
||||
$edit = array(
|
||||
'field_single[und][0][value]' => 1,
|
||||
'field_unlimited[und][0][value]' => 2,
|
||||
'field_unlimited[und][1][value]' => 3,
|
||||
'entity_2[field_single][und][0][value]' => 11,
|
||||
'entity_2[field_unlimited][und][0][value]' => 12,
|
||||
'entity_2[field_unlimited][und][1][value]' => 13,
|
||||
);
|
||||
$this->drupalPost(NULL, $edit, t('Save'));
|
||||
field_cache_clear();
|
||||
$entity_1 = entity_load($entity_type, 1);
|
||||
$entity_2 = entity_load($entity_type, 2);
|
||||
$this->assertFieldValues($entity_1, 'field_single', Language::LANGCODE_NOT_SPECIFIED, array(1));
|
||||
$this->assertFieldValues($entity_1, 'field_unlimited', Language::LANGCODE_NOT_SPECIFIED, array(2, 3));
|
||||
$this->assertFieldValues($entity_2, 'field_single', Language::LANGCODE_NOT_SPECIFIED, array(11));
|
||||
$this->assertFieldValues($entity_2, 'field_unlimited', Language::LANGCODE_NOT_SPECIFIED, array(12, 13));
|
||||
|
||||
// Submit invalid values and check that errors are reported on the
|
||||
// correct widgets.
|
||||
$edit = array(
|
||||
'field_unlimited[und][1][value]' => -1,
|
||||
);
|
||||
$this->drupalPost('test-entity/nested/1/2', $edit, t('Save'));
|
||||
$this->assertRaw(t('%label does not accept the value -1', array('%label' => 'Unlimited field')), 'Entity 1: the field validation error was reported.');
|
||||
$error_field = $this->xpath('//input[@id=:id and contains(@class, "error")]', array(':id' => 'edit-field-unlimited-und-1-value'));
|
||||
$this->assertTrue($error_field, 'Entity 1: the error was flagged on the correct element.');
|
||||
$edit = array(
|
||||
'entity_2[field_unlimited][und][1][value]' => -1,
|
||||
);
|
||||
$this->drupalPost('test-entity/nested/1/2', $edit, t('Save'));
|
||||
$this->assertRaw(t('%label does not accept the value -1', array('%label' => 'Unlimited field')), 'Entity 2: the field validation error was reported.');
|
||||
$error_field = $this->xpath('//input[@id=:id and contains(@class, "error")]', array(':id' => 'edit-entity-2-field-unlimited-und-1-value'));
|
||||
$this->assertTrue($error_field, 'Entity 2: the error was flagged on the correct element.');
|
||||
|
||||
// Test that reordering works on both entities.
|
||||
$edit = array(
|
||||
'field_unlimited[und][0][_weight]' => 0,
|
||||
'field_unlimited[und][1][_weight]' => -1,
|
||||
'entity_2[field_unlimited][und][0][_weight]' => 0,
|
||||
'entity_2[field_unlimited][und][1][_weight]' => -1,
|
||||
);
|
||||
$this->drupalPost('test-entity/nested/1/2', $edit, t('Save'));
|
||||
field_cache_clear();
|
||||
$this->assertFieldValues($entity_1, 'field_unlimited', Language::LANGCODE_NOT_SPECIFIED, array(3, 2));
|
||||
$this->assertFieldValues($entity_2, 'field_unlimited', Language::LANGCODE_NOT_SPECIFIED, array(13, 12));
|
||||
|
||||
// Test the 'add more' buttons. Only Ajax submission is tested, because
|
||||
// the two 'add more' buttons present in the form have the same #value,
|
||||
// which confuses drupalPost().
|
||||
// 'Add more' button in the first entity:
|
||||
$this->drupalGet('test-entity/nested/1/2');
|
||||
$this->drupalPostAJAX(NULL, array(), 'field_unlimited_add_more');
|
||||
$this->assertFieldByName('field_unlimited[und][0][value]', 3, 'Entity 1: field_unlimited value 0 appears correctly is the form.');
|
||||
$this->assertFieldByName('field_unlimited[und][1][value]', 2, 'Entity 1: field_unlimited value 1 appears correctly is the form.');
|
||||
$this->assertFieldByName('field_unlimited[und][2][value]', '', 'Entity 1: field_unlimited value 2 appears correctly is the form.');
|
||||
$this->assertFieldByName('field_unlimited[und][3][value]', '', 'Entity 1: an empty widget was added for field_unlimited value 3.');
|
||||
// 'Add more' button in the first entity (changing field values):
|
||||
$edit = array(
|
||||
'entity_2[field_unlimited][und][0][value]' => 13,
|
||||
'entity_2[field_unlimited][und][1][value]' => 14,
|
||||
'entity_2[field_unlimited][und][2][value]' => 15,
|
||||
);
|
||||
$this->drupalPostAJAX(NULL, $edit, 'entity_2_field_unlimited_add_more');
|
||||
$this->assertFieldByName('entity_2[field_unlimited][und][0][value]', 13, 'Entity 2: field_unlimited value 0 appears correctly is the form.');
|
||||
$this->assertFieldByName('entity_2[field_unlimited][und][1][value]', 14, 'Entity 2: field_unlimited value 1 appears correctly is the form.');
|
||||
$this->assertFieldByName('entity_2[field_unlimited][und][2][value]', 15, 'Entity 2: field_unlimited value 2 appears correctly is the form.');
|
||||
$this->assertFieldByName('entity_2[field_unlimited][und][3][value]', '', 'Entity 2: an empty widget was added for field_unlimited value 3.');
|
||||
// Save the form and check values are saved correctly.
|
||||
$this->drupalPost(NULL, array(), t('Save'));
|
||||
field_cache_clear();
|
||||
$this->assertFieldValues($entity_1, 'field_unlimited', Language::LANGCODE_NOT_SPECIFIED, array(3, 2));
|
||||
$this->assertFieldValues($entity_2, 'field_unlimited', Language::LANGCODE_NOT_SPECIFIED, array(13, 14, 15));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that a field has the expected values in an entity.
|
||||
*
|
||||
* This function only checks a single column in the field values.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity to test.
|
||||
* @param string $field_name
|
||||
* The name of the field to test.
|
||||
* @param string $langcode
|
||||
* The language code for the values.
|
||||
* @param array $expected_values
|
||||
* The array of expected values.
|
||||
* @param string $column
|
||||
* (Optional) the name of the column to check.
|
||||
*/
|
||||
function assertFieldValues(EntityInterface $entity, $field_name, $langcode, $expected_values, $column = 'value') {
|
||||
// Re-load the entity to make sure we have the latest changes.
|
||||
entity_get_controller($entity->entityType())->resetCache(array($entity->id()));
|
||||
$e = entity_load($entity->entityType(), $entity->id());
|
||||
$field = $values = $e->getTranslation($langcode, FALSE)->$field_name;
|
||||
// Filter out empty values so that they don't mess with the assertions.
|
||||
$field->filterEmptyValues();
|
||||
$values = $field->getValue();
|
||||
$this->assertEqual(count($values), count($expected_values), 'Expected number of values were saved.');
|
||||
foreach ($expected_values as $key => $value) {
|
||||
$this->assertEqual($values[$key][$column], $value, format_string('Value @value was saved correctly.', array('@value' => $value)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ use Drupal\Core\Language\Language;
|
|||
/**
|
||||
* Unit test class for the multilanguage fields logic.
|
||||
*
|
||||
* The following tests will check the multilanguage logic of _field_invoke() and
|
||||
* The following tests will check the multilanguage logic in field handling, and
|
||||
* that only the correct values are returned by field_available_languages().
|
||||
*/
|
||||
class TranslationTest extends FieldUnitTestBase {
|
||||
|
|
@ -141,116 +141,6 @@ class TranslationTest extends FieldUnitTestBase {
|
|||
$this->assertTrue(count($available_langcodes) == 1 && $available_langcodes[0] === Language::LANGCODE_NOT_SPECIFIED, 'For untranslatable fields only Language::LANGCODE_NOT_SPECIFIED is available.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the multilanguage logic of _field_invoke().
|
||||
*/
|
||||
function testFieldInvoke() {
|
||||
// Enable field translations for the entity.
|
||||
field_test_entity_info_translatable('test_entity', TRUE);
|
||||
|
||||
$entity = field_test_create_entity(0, 0, $this->instance['bundle']);
|
||||
|
||||
// Populate some extra languages to check if _field_invoke() correctly uses
|
||||
// the result of field_available_languages().
|
||||
$values = array();
|
||||
$extra_langcodes = mt_rand(1, 4);
|
||||
$langcodes = $available_langcodes = field_available_languages($this->entity_type, $this->field);
|
||||
for ($i = 0; $i < $extra_langcodes; ++$i) {
|
||||
$langcodes[] = $this->randomName(2);
|
||||
}
|
||||
|
||||
// For each given language provide some random values.
|
||||
foreach ($langcodes as $langcode) {
|
||||
for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
|
||||
$values[$langcode][$delta]['value'] = mt_rand(1, 127);
|
||||
}
|
||||
}
|
||||
$entity->{$this->field_name} = $values;
|
||||
|
||||
$results = _field_invoke('test_op', $entity);
|
||||
foreach ($results as $langcode => $result) {
|
||||
$hash = hash('sha256', serialize(array($entity, $this->field_name, $langcode, $values[$langcode])));
|
||||
// Check whether the parameters passed to _field_invoke() were correctly
|
||||
// forwarded to the callback function.
|
||||
$this->assertEqual($hash, $result, format_string('The result for %language is correctly stored.', array('%language' => $langcode)));
|
||||
}
|
||||
|
||||
$this->assertEqual(count($results), count($available_langcodes), 'No unavailable language has been processed.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the multilanguage logic of _field_invoke_multiple().
|
||||
*/
|
||||
function testFieldInvokeMultiple() {
|
||||
// Enable field translations for the entity.
|
||||
field_test_entity_info_translatable('test_entity', TRUE);
|
||||
|
||||
$values = array();
|
||||
$options = array();
|
||||
$entities = array();
|
||||
$entity_type = 'test_entity';
|
||||
$entity_count = 5;
|
||||
$available_langcodes = field_available_languages($this->entity_type, $this->field);
|
||||
|
||||
for ($id = 1; $id <= $entity_count; ++$id) {
|
||||
$entity = field_test_create_entity($id, $id, $this->instance['bundle']);
|
||||
$langcodes = $available_langcodes;
|
||||
|
||||
// Populate some extra languages to check whether _field_invoke()
|
||||
// correctly uses the result of field_available_languages().
|
||||
$extra_langcodes = mt_rand(1, 4);
|
||||
for ($i = 0; $i < $extra_langcodes; ++$i) {
|
||||
$langcodes[] = $this->randomName(2);
|
||||
}
|
||||
|
||||
// For each given language provide some random values.
|
||||
$language_count = count($langcodes);
|
||||
for ($i = 0; $i < $language_count; ++$i) {
|
||||
$langcode = $langcodes[$i];
|
||||
// Avoid to populate at least one field translation to check that
|
||||
// per-entity language suggestions work even when available field values
|
||||
// are different for each language.
|
||||
if ($i !== $id) {
|
||||
for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
|
||||
$values[$id][$langcode][$delta]['value'] = mt_rand(1, 127);
|
||||
}
|
||||
}
|
||||
// Ensure that a language for which there is no field translation is
|
||||
// used as display language to prepare per-entity language suggestions.
|
||||
elseif (!isset($display_langcode)) {
|
||||
$display_langcode = $langcode;
|
||||
}
|
||||
}
|
||||
|
||||
$entity->{$this->field_name} = $values[$id];
|
||||
$entities[$id] = $entity;
|
||||
|
||||
// Store per-entity language suggestions.
|
||||
$options['langcode'][$id] = field_language($entity, NULL, $display_langcode);
|
||||
}
|
||||
|
||||
$grouped_results = _field_invoke_multiple('test_op_multiple', $entity_type, $entities);
|
||||
foreach ($grouped_results as $id => $results) {
|
||||
foreach ($results as $langcode => $result) {
|
||||
if (isset($values[$id][$langcode])) {
|
||||
$hash = hash('sha256', serialize(array($entity_type, $entities[$id], $this->field_name, $langcode, $values[$id][$langcode])));
|
||||
// Check whether the parameters passed to _field_invoke_multiple()
|
||||
// were correctly forwarded to the callback function.
|
||||
$this->assertEqual($hash, $result, format_string('The result for entity %id/%language is correctly stored.', array('%id' => $id, '%language' => $langcode)));
|
||||
}
|
||||
}
|
||||
$this->assertEqual(count($results), count($available_langcodes), format_string('No unavailable language has been processed for entity %id.', array('%id' => $id)));
|
||||
}
|
||||
|
||||
$null = NULL;
|
||||
$grouped_results = _field_invoke_multiple('test_op_multiple', $entity_type, $entities, $null, $null, $options);
|
||||
foreach ($grouped_results as $id => $results) {
|
||||
foreach ($results as $langcode => $result) {
|
||||
$this->assertTrue(isset($options['langcode'][$id]), format_string('The result language code %langcode for entity %id was correctly suggested (display language: %display_langcode).', array('%id' => $id, '%langcode' => $langcode, '%display_langcode' => $display_langcode)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test translatable fields storage/retrieval.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -224,10 +224,10 @@ function field_test_entity_edit(TestEntity $entity) {
|
|||
*/
|
||||
function field_test_entity_nested_form($form, &$form_state, $entity_1, $entity_2) {
|
||||
// First entity.
|
||||
foreach (array('ftid', 'ftvid', 'fttype') as $key) {
|
||||
foreach (array('id', 'type') as $key) {
|
||||
$form[$key] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => $entity_1->$key,
|
||||
'#value' => $entity_1->$key->value,
|
||||
);
|
||||
}
|
||||
$form_state['form_display'] = entity_get_form_display($entity_1->entityType(), $entity_1->bundle(), 'default');
|
||||
|
|
@ -241,10 +241,10 @@ function field_test_entity_nested_form($form, &$form_state, $entity_1, $entity_2
|
|||
'#parents' => array('entity_2'),
|
||||
'#weight' => 50,
|
||||
);
|
||||
foreach (array('ftid', 'ftvid', 'fttype') as $key) {
|
||||
foreach (array('id', 'type') as $key) {
|
||||
$form['entity_2'][$key] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => $entity_2->$key,
|
||||
'#value' => $entity_2->$key->value,
|
||||
);
|
||||
}
|
||||
$form_state['form_display'] = entity_get_form_display($entity_1->entityType(), $entity_1->bundle(), 'default');
|
||||
|
|
@ -263,11 +263,11 @@ function field_test_entity_nested_form($form, &$form_state, $entity_1, $entity_2
|
|||
* Validate handler for field_test_entity_nested_form().
|
||||
*/
|
||||
function field_test_entity_nested_form_validate($form, &$form_state) {
|
||||
$entity_1 = entity_create('test_entity', $form_state['values']);
|
||||
$entity_1 = entity_create('entity_test', $form_state['values']);
|
||||
field_attach_extract_form_values($entity_1, $form, $form_state);
|
||||
field_attach_form_validate($entity_1, $form, $form_state);
|
||||
|
||||
$entity_2 = entity_create('test_entity', $form_state['values']['entity_2']);
|
||||
$entity_2 = entity_create('entity_test', $form_state['values']['entity_2']);
|
||||
field_attach_extract_form_values($entity_2, $form['entity_2'], $form_state);
|
||||
field_attach_form_validate($entity_2, $form['entity_2'], $form_state);
|
||||
}
|
||||
|
|
@ -276,13 +276,13 @@ function field_test_entity_nested_form_validate($form, &$form_state) {
|
|||
* Submit handler for field_test_entity_nested_form().
|
||||
*/
|
||||
function field_test_entity_nested_form_submit($form, &$form_state) {
|
||||
$entity_1 = entity_create('test_entity', $form_state['values']);
|
||||
$entity_1 = entity_create('entity_test', $form_state['values']);
|
||||
field_attach_extract_form_values($entity_1, $form, $form_state);
|
||||
field_test_entity_save($entity_1);
|
||||
|
||||
$entity_2 = entity_create('test_entity', $form_state['values']['entity_2']);
|
||||
$entity_2 = entity_create('entity_test', $form_state['values']['entity_2']);
|
||||
field_attach_extract_form_values($entity_2, $form['entity_2'], $form_state);
|
||||
field_test_entity_save($entity_2);
|
||||
|
||||
drupal_set_message(t('test_entities @id_1 and @id_2 have been updated.', array('@id_1' => $entity_1->ftid, '@id_2' => $entity_2->ftid)));
|
||||
drupal_set_message(t('test_entities @id_1 and @id_2 have been updated.', array('@id_1' => $entity_1->id(), '@id_2' => $entity_2->id())));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ function field_test_field_info() {
|
|||
),
|
||||
'default_widget' => 'test_field_widget',
|
||||
'default_formatter' => 'field_test_default',
|
||||
'field item class' => 'Drupal\field_test\Type\TestItem',
|
||||
'class' => 'Drupal\field_test\Type\TestItem',
|
||||
),
|
||||
'shape' => array(
|
||||
'label' => t('Shape'),
|
||||
|
|
@ -39,7 +39,7 @@ function field_test_field_info() {
|
|||
'instance_settings' => array(),
|
||||
'default_widget' => 'test_field_widget',
|
||||
'default_formatter' => 'field_test_default',
|
||||
'field item class' => 'Drupal\field_test\Type\ShapeItem',
|
||||
'class' => 'Drupal\field_test\Type\ShapeItem',
|
||||
),
|
||||
'hidden_test_field' => array(
|
||||
'no_ui' => TRUE,
|
||||
|
|
@ -49,7 +49,7 @@ function field_test_field_info() {
|
|||
'instance_settings' => array(),
|
||||
'default_widget' => 'test_field_widget',
|
||||
'default_formatter' => 'field_test_default',
|
||||
'field item class' => 'Drupal\field_test\Type\TestItem',
|
||||
'class' => 'Drupal\field_test\Type\HiddenTestItem',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -139,7 +139,10 @@ function field_test_field_validate(EntityInterface $entity = NULL, $field, $inst
|
|||
* Implements hook_field_is_empty().
|
||||
*/
|
||||
function field_test_field_is_empty($item, $field_type) {
|
||||
return empty($item['value']);
|
||||
if ($field_type == 'test_field') {
|
||||
return empty($item['value']);
|
||||
}
|
||||
return empty($item['shape']) && empty($item['color']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -63,45 +63,17 @@ function field_test_menu() {
|
|||
'type' => MENU_NORMAL_ITEM,
|
||||
);
|
||||
|
||||
$items['test-entity/nested/%field_test_entity_test/%field_test_entity_test'] = array(
|
||||
$items['test-entity/nested/%entity_test/%entity_test'] = array(
|
||||
'title' => 'Nested entity form',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('field_test_entity_nested_form', 2, 3),
|
||||
'access arguments' => array('administer field_test content'),
|
||||
'access arguments' => array('administer entity_test content'),
|
||||
'type' => MENU_NORMAL_ITEM,
|
||||
);
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic op to test _field_invoke behavior.
|
||||
*
|
||||
* This simulates a field operation callback to be invoked by _field_invoke().
|
||||
*/
|
||||
function field_test_field_test_op(EntityInterface $entity, $field, $instance, $langcode, &$items) {
|
||||
return array($langcode => hash('sha256', serialize(array($entity, $field['field_name'], $langcode, $items))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic op to test _field_invoke_multiple behavior.
|
||||
*
|
||||
* This simulates a multiple field operation callback to be invoked by
|
||||
* _field_invoke_multiple().
|
||||
*/
|
||||
function field_test_field_test_op_multiple($entity_type, $entities, $field, $instances, $langcode, &$items) {
|
||||
$result = array();
|
||||
foreach ($entities as $id => $entity) {
|
||||
// Entities, instances and items are assumed to be consistently grouped by
|
||||
// language. To verify this we try to access all the passed data structures
|
||||
// by entity id. If they are grouped correctly, one entity, one instance and
|
||||
// one array of items should be available for each entity id.
|
||||
$field_name = $instances[$id]['field_name'];
|
||||
$result[$id] = array($langcode => hash('sha256', serialize(array($entity_type, $entity, $field_name, $langcode, $items[$id]))));
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_available_languages_alter().
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ namespace Drupal\field_test\Plugin\field\widget;
|
|||
use Drupal\Component\Annotation\Plugin;
|
||||
use Drupal\Core\Annotation\Translation;
|
||||
use Drupal\field\Plugin\Type\Widget\WidgetBase;
|
||||
use Symfony\Component\Validator\ConstraintViolationInterface;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'test_field_widget' widget.
|
||||
|
|
@ -57,7 +58,7 @@ class TestFieldWidget extends WidgetBase {
|
|||
/**
|
||||
* Implements Drupal\field\Plugin\Type\Widget\WidgetInterface::errorElement().
|
||||
*/
|
||||
public function errorElement(array $element, array $error, array $form, array &$form_state) {
|
||||
public function errorElement(array $element, ConstraintViolationInterface $error, array $form, array &$form_state) {
|
||||
return $element['value'];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ namespace Drupal\field_test\Plugin\field\widget;
|
|||
use Drupal\Component\Annotation\Plugin;
|
||||
use Drupal\Core\Annotation\Translation;
|
||||
use Drupal\field\Plugin\Type\Widget\WidgetBase;
|
||||
use Symfony\Component\Validator\ConstraintViolationInterface;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'test_field_widget_multiple' widget.
|
||||
|
|
@ -63,7 +64,7 @@ class TestFieldWidgetMultiple extends WidgetBase {
|
|||
/**
|
||||
* Implements Drupal\field\Plugin\Type\Widget\WidgetInterface::errorElement().
|
||||
*/
|
||||
public function errorElement(array $element, array $error, array $form, array &$form_state) {
|
||||
public function errorElement(array $element, ConstraintViolationInterface $error, array $form, array &$form_state) {
|
||||
return $element;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\field_test\Type\HiddenTestItem.
|
||||
*/
|
||||
|
||||
namespace Drupal\field_test\Type;
|
||||
|
||||
/**
|
||||
* Defines the 'test_field' entity field item.
|
||||
*/
|
||||
class HiddenTestItem extends TestItem {
|
||||
|
||||
/**
|
||||
* Property definitions of the contained properties.
|
||||
*
|
||||
* @see TestItem::getPropertyDefinitions()
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
static $propertyDefinitions;
|
||||
|
||||
/**
|
||||
* Implements \Drupal\Core\TypedData\ComplexDataInterface::getPropertyDefinitions().
|
||||
*/
|
||||
public function getPropertyDefinitions() {
|
||||
|
||||
if (!isset(static::$propertyDefinitions)) {
|
||||
static::$propertyDefinitions['value'] = array(
|
||||
'type' => 'integer',
|
||||
'label' => t('Test integer value'),
|
||||
);
|
||||
}
|
||||
return static::$propertyDefinitions;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -7,12 +7,12 @@
|
|||
|
||||
namespace Drupal\field_test\Type;
|
||||
|
||||
use Drupal\Core\Entity\Field\FieldItemBase;
|
||||
use Drupal\field\Plugin\field\field_type\LegacyConfigFieldItem;
|
||||
|
||||
/**
|
||||
* Defines the 'shape_field' entity field item.
|
||||
*/
|
||||
class ShapeItem extends FieldItemBase {
|
||||
class ShapeItem extends LegacyConfigFieldItem {
|
||||
|
||||
/**
|
||||
* Property definitions of the contained properties.
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@
|
|||
|
||||
namespace Drupal\field_test\Type;
|
||||
|
||||
use Drupal\Core\Entity\Field\FieldItemBase;
|
||||
use Drupal\field\Plugin\field\field_type\LegacyConfigFieldItem;
|
||||
|
||||
/**
|
||||
* Defines the 'test_field' entity field item.
|
||||
*/
|
||||
class TestItem extends FieldItemBase {
|
||||
class TestItem extends LegacyConfigFieldItem {
|
||||
|
||||
/**
|
||||
* Property definitions of the contained properties.
|
||||
|
|
|
|||
|
|
@ -416,7 +416,8 @@ function field_sql_storage_field_storage_load($entity_type, $entities, $age, $fi
|
|||
// from the prefixed database column.
|
||||
foreach ($field['columns'] as $column => $attributes) {
|
||||
$column_name = _field_sql_storage_columnname($field_name, $column);
|
||||
$item[$column] = $row->$column_name;
|
||||
// Unserialize the value if specified in the column schema.
|
||||
$item[$column] = (!empty($attributes['serialize'])) ? unserialize($row->$column_name) : $row->$column_name;
|
||||
}
|
||||
|
||||
// Add the item to the field values for the entity.
|
||||
|
|
@ -496,7 +497,10 @@ function field_sql_storage_field_storage_write(EntityInterface $entity, $op, $fi
|
|||
'langcode' => $langcode,
|
||||
);
|
||||
foreach ($field['columns'] as $column => $attributes) {
|
||||
$record[_field_sql_storage_columnname($field_name, $column)] = isset($item[$column]) ? $item[$column] : NULL;
|
||||
$column_name = _field_sql_storage_columnname($field_name, $column);
|
||||
$value = isset($item[$column]) ? $item[$column] : NULL;
|
||||
// Serialize the value if specified in the column schema.
|
||||
$record[$column_name] = (!empty($attributes['serialize'])) ? serialize($value) : $value;
|
||||
}
|
||||
$query->values($record);
|
||||
if (isset($vid)) {
|
||||
|
|
|
|||
|
|
@ -10,86 +10,6 @@
|
|||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Add settings to a field settings form.
|
||||
*
|
||||
* Invoked from \Drupal\field_ui\Form\FieldInstanceEditForm to allow the module
|
||||
* defining the field to add global settings (i.e. settings that do not depend
|
||||
* on the bundle or instance) to the field settings form. If the field already
|
||||
* has data, only include settings that are safe to change.
|
||||
*
|
||||
* @todo: Only the field type module knows which settings will affect the
|
||||
* field's schema, but only the field storage module knows what schema
|
||||
* changes are permitted once a field already has data. Probably we need an
|
||||
* easy way for a field type module to ask whether an update to a new schema
|
||||
* will be allowed without having to build up a fake $prior_field structure
|
||||
* for hook_field_update_forbid().
|
||||
*
|
||||
* @param $field
|
||||
* The field structure being configured.
|
||||
* @param $instance
|
||||
* The instance structure being configured.
|
||||
*
|
||||
* @return
|
||||
* The form definition for the field settings.
|
||||
*/
|
||||
function hook_field_settings_form($field, $instance) {
|
||||
$settings = $field['settings'];
|
||||
$form['max_length'] = array(
|
||||
'#type' => 'number',
|
||||
'#title' => t('Maximum length'),
|
||||
'#default_value' => $settings['max_length'],
|
||||
'#required' => FALSE,
|
||||
'#min' => 1,
|
||||
'#description' => t('The maximum length of the field in characters. Leave blank for an unlimited size.'),
|
||||
);
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add settings to an instance field settings form.
|
||||
*
|
||||
* Invoked from \Drupal\field_ui\Form\FieldInstanceEditForm to allow the module
|
||||
* defining the field to add settings for a field instance.
|
||||
*
|
||||
* @param $field
|
||||
* The field structure being configured.
|
||||
* @param $instance
|
||||
* The instance structure being configured.
|
||||
* @param array $form_state
|
||||
* The form state of the (entire) configuration form.
|
||||
*
|
||||
* @return
|
||||
* The form definition for the field instance settings.
|
||||
*/
|
||||
function hook_field_instance_settings_form($field, $instance, $form_state) {
|
||||
$settings = $instance['settings'];
|
||||
|
||||
$form['text_processing'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => t('Text processing'),
|
||||
'#default_value' => $settings['text_processing'],
|
||||
'#options' => array(
|
||||
t('Plain text'),
|
||||
t('Filtered text (user selects text format)'),
|
||||
),
|
||||
);
|
||||
if ($field['type'] == 'text_with_summary') {
|
||||
$form['display_summary'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Display summary'),
|
||||
'#options' => array(
|
||||
t('No'),
|
||||
t('Yes'),
|
||||
),
|
||||
'#description' => t('Display the summary to allow the user to input a summary value. Hide the summary to automatically fill it with a trimmed portion from the main post.'),
|
||||
'#default_value' => !empty($settings['display_summary']) ? $settings['display_summary'] : 0,
|
||||
);
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alters the formatter settings form.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ namespace Drupal\field_ui\Form;
|
|||
|
||||
use Drupal\Core\Controller\ControllerInterface;
|
||||
use Drupal\Core\Entity\EntityManager;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Form\FormInterface;
|
||||
use Drupal\field\FieldInstanceInterface;
|
||||
use Drupal\field\Field;
|
||||
|
|
@ -134,10 +135,11 @@ class FieldEditForm implements FormInterface, ControllerInterface {
|
|||
$form['field']['settings'] = array(
|
||||
'#weight' => 10,
|
||||
);
|
||||
$additions = \Drupal::moduleHandler()->invoke($field['module'], 'field_settings_form', array($field, $this->instance));
|
||||
if (is_array($additions)) {
|
||||
$form['field']['settings'] += $additions;
|
||||
}
|
||||
// Create an arbitrary entity object, so that we can have an instantiated
|
||||
// FieldItem.
|
||||
$ids = (object) array('entity_type' => $this->instance['entity_type'], 'bundle' => $this->instance['bundle'], 'entity_id' => NULL);
|
||||
$entity = _field_create_entity_from_ids($ids);
|
||||
$form['field']['settings'] += $this->getFieldItem($entity, $field['field_name'])->settingsForm($form, $form_state);
|
||||
|
||||
$form['actions'] = array('#type' => 'actions');
|
||||
$form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save field settings'));
|
||||
|
|
@ -193,4 +195,28 @@ class FieldEditForm implements FormInterface, ControllerInterface {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a FieldItem object for an entity.
|
||||
*
|
||||
* @todo Remove when all entity types extend EntityNG.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* An entity.
|
||||
* @param string $field_name
|
||||
* The field name.
|
||||
*
|
||||
* @return \Drupal\field\Plugin\Type\FieldType\ConfigFieldItemInterface
|
||||
* The field item object.
|
||||
*/
|
||||
protected function getFieldItem(EntityInterface $entity, $field_name) {
|
||||
if ($entity instanceof \Drupal\Core\Entity\EntityNG) {
|
||||
$item = $entity->get($field_name)->offsetGet(0);
|
||||
}
|
||||
else {
|
||||
$definitions = \Drupal::entityManager()->getFieldDefinitions($entity->entityType(), $entity->bundle());
|
||||
$item = \Drupal::typedData()->create($definitions[$field_name], array(), $field_name, $entity)->offsetGet(0);
|
||||
}
|
||||
return $item;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,10 @@
|
|||
|
||||
namespace Drupal\field_ui\Form;
|
||||
|
||||
use Drupal\field\FieldInstanceInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityNG;
|
||||
use Drupal\Core\Language\Language;
|
||||
use Drupal\field\FieldInstanceInterface;
|
||||
|
||||
/**
|
||||
* Provides a form for the field instance settings form.
|
||||
|
|
@ -97,12 +99,9 @@ class FieldInstanceEditForm extends FieldInstanceFormBase {
|
|||
'#weight' => -5,
|
||||
);
|
||||
|
||||
// Add additional field instance settings from the field module.
|
||||
$additions = \Drupal::moduleHandler()->invoke($field['module'], 'field_instance_settings_form', array($field, $this->instance, $form_state));
|
||||
if (is_array($additions)) {
|
||||
$form['instance']['settings'] = $additions;
|
||||
$form['instance']['settings']['#weight'] = 10;
|
||||
}
|
||||
// Add instance settings for the field type.
|
||||
$form['instance']['settings'] = $this->getFieldItem($form['#entity'], $this->instance['field_name'])->instanceSettingsForm($form, $form_state);
|
||||
$form['instance']['settings']['#weight'] = 10;
|
||||
|
||||
// Add widget settings for the widget type.
|
||||
$additions = $entity_form_display->getWidget($this->instance->getField()->id)->settingsForm($form, $form_state);
|
||||
|
|
@ -136,7 +135,6 @@ class FieldInstanceEditForm extends FieldInstanceFormBase {
|
|||
$field_name = $this->instance['field_name'];
|
||||
$entity = $form['#entity'];
|
||||
$entity_form_display = $form['#entity_form_display'];
|
||||
$field = $this->instance->getField();
|
||||
|
||||
if (isset($form['instance']['default_value_widget'])) {
|
||||
$element = $form['instance']['default_value_widget'];
|
||||
|
|
@ -145,20 +143,25 @@ class FieldInstanceEditForm extends FieldInstanceFormBase {
|
|||
$items = array();
|
||||
$entity_form_display->getWidget($this->instance->getField()->id)->extractFormValues($entity, Language::LANGCODE_NOT_SPECIFIED, $items, $element, $form_state);
|
||||
|
||||
// Get the field state.
|
||||
$field_state = field_form_get_state($element['#parents'], $field_name, Language::LANGCODE_NOT_SPECIFIED, $form_state);
|
||||
|
||||
// Validate the value.
|
||||
$errors = array();
|
||||
$function = $field['module'] . '_field_validate';
|
||||
if (function_exists($function)) {
|
||||
$function(NULL, $field, $this->instance, Language::LANGCODE_NOT_SPECIFIED, $items, $errors);
|
||||
// @todo Simplify when all entity types are converted to EntityNG.
|
||||
if ($entity instanceof EntityNG) {
|
||||
$entity->{$field_name}->setValue($items);
|
||||
$itemsNG = $entity->{$field_name};
|
||||
}
|
||||
else {
|
||||
// For BC entities, instantiate NG items objects manually.
|
||||
$definitions = \Drupal::entityManager()->getFieldDefinitions($entity->entityType(), $entity->bundle());
|
||||
$itemsNG = \Drupal::typedData()->create($definitions[$field_name], $items, $field_name, $entity);
|
||||
}
|
||||
$violations = $itemsNG->validate();
|
||||
|
||||
// Grab the field definition from $form_state.
|
||||
|
||||
// Report errors.
|
||||
if (isset($errors[$field_name][Language::LANGCODE_NOT_SPECIFIED])) {
|
||||
if (count($violations)) {
|
||||
$field_state = field_form_get_state($element['#parents'], $field_name, Language::LANGCODE_NOT_SPECIFIED, $form_state);
|
||||
// Store reported errors in $form_state.
|
||||
$field_state['errors'] = $errors[$field_name][Language::LANGCODE_NOT_SPECIFIED];
|
||||
$field_state['constraint_violations'] = $violations;
|
||||
field_form_set_state($element['#parents'], $field_name, Language::LANGCODE_NOT_SPECIFIED, $form_state, $field_state);
|
||||
|
||||
// Assign reported errors to the correct form element.
|
||||
|
|
@ -265,4 +268,28 @@ class FieldInstanceEditForm extends FieldInstanceFormBase {
|
|||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a FieldItem object for an entity.
|
||||
*
|
||||
* @todo Remove when all entity types extend EntityNG.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* An entity.
|
||||
* @param string $field_name
|
||||
* The field name.
|
||||
*
|
||||
* @return \Drupal\field\Plugin\Type\FieldType\ConfigFieldItemInterface
|
||||
* The field item object.
|
||||
*/
|
||||
protected function getFieldItem(EntityInterface $entity, $field_name) {
|
||||
if ($entity instanceof EntityNG) {
|
||||
$item = $entity->get($field_name)->offsetGet(0);
|
||||
}
|
||||
else {
|
||||
$definitions = \Drupal::entityManager()->getFieldDefinitions($entity->entityType(), $entity->bundle());
|
||||
$item = \Drupal::typedData()->create($definitions[$field_name], array(), $field_name, $entity)->offsetGet(0);
|
||||
}
|
||||
return $item;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -419,7 +419,7 @@ class ManageFieldsTest extends FieldUiTestBase {
|
|||
}
|
||||
|
||||
/**
|
||||
* Tests that Field UI respects the 'no_ui' option in hook_field_info().
|
||||
* Tests that Field UI respects the 'no_ui' flag in the field type definition.
|
||||
*/
|
||||
function testHiddenFields() {
|
||||
$bundle_path = 'admin/structure/types/manage/' . $this->type . '/fields/';
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ function file_field_info() {
|
|||
),
|
||||
'default_widget' => 'file_generic',
|
||||
'default_formatter' => 'file_default',
|
||||
'field item class' => '\Drupal\file\Type\FileItem',
|
||||
'class' => '\Drupal\file\Type\FileItem',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -183,35 +183,6 @@ function _file_generic_settings_file_directory_validate($element, &$form_state)
|
|||
form_set_value($element, $value, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_prepare_view().
|
||||
*/
|
||||
function file_field_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items) {
|
||||
// Remove files specified to not be displayed.
|
||||
$fids = array();
|
||||
foreach ($entities as $id => $entity) {
|
||||
foreach ($items[$id] as $delta => $item) {
|
||||
if (file_field_displayed($item, $field) && !empty($item['fid'])) {
|
||||
// Load the files from the files table.
|
||||
$fids[] = $item['fid'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($fids) {
|
||||
$files = file_load_multiple($fids);
|
||||
|
||||
foreach ($entities as $id => $entity) {
|
||||
foreach ($items[$id] as $delta => $item) {
|
||||
// If the file does not exist, mark the entire item as empty.
|
||||
if (!empty($item['fid'])) {
|
||||
$items[$id][$delta]['entity'] = isset($files[$item['fid']]) ? $files[$item['fid']] : NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_insert().
|
||||
*/
|
||||
|
|
@ -291,24 +262,6 @@ function file_field_is_empty($item, $field_type) {
|
|||
return empty($item['fid']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a file should be displayed when outputting field content.
|
||||
*
|
||||
* @param $item
|
||||
* A field item array.
|
||||
* @param $field
|
||||
* A field array.
|
||||
*
|
||||
* @return
|
||||
* Boolean TRUE if the file will be displayed, FALSE if the file is hidden.
|
||||
*/
|
||||
function file_field_displayed($item, $field) {
|
||||
if (!empty($field['settings']['display_field'])) {
|
||||
return (bool) $item['display'];
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the upload validators for a file field.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\file\Plugin\field\formatter\FileFormatterBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\file\Plugin\field\formatter;
|
||||
|
||||
use Drupal\field\Plugin\Type\Formatter\FormatterBase;
|
||||
|
||||
/**
|
||||
* Base class for file formatters.
|
||||
*/
|
||||
abstract class FileFormatterBase extends FormatterBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareView(array $entities, $langcode, array &$items) {
|
||||
// Remove files specified to not be displayed.
|
||||
$fids = array();
|
||||
foreach ($entities as $id => $entity) {
|
||||
foreach ($items[$id] as $delta => $item) {
|
||||
if ($this->isDisplayed($item) && !empty($item['fid'])) {
|
||||
// Load the files from the files table.
|
||||
$fids[] = $item['fid'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($fids) {
|
||||
$files = file_load_multiple($fids);
|
||||
|
||||
foreach ($entities as $id => $entity) {
|
||||
foreach ($items[$id] as $delta => $item) {
|
||||
// If the file does not exist, mark the entire item as empty.
|
||||
if (!empty($item['fid'])) {
|
||||
$items[$id][$delta]['entity'] = isset($files[$item['fid']]) ? $files[$item['fid']] : NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a file should be displayed when outputting field content.
|
||||
*
|
||||
* @param $item
|
||||
* A field item array.
|
||||
* @param $field
|
||||
* A field array.
|
||||
*
|
||||
* @return
|
||||
* Boolean TRUE if the file will be displayed, FALSE if the file is hidden.
|
||||
*/
|
||||
protected function isDisplayed($item) {
|
||||
$settings = $this->getFieldSettings();
|
||||
if (!empty($settings['display_field'])) {
|
||||
return (bool) $item['display'];
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,6 @@ namespace Drupal\file\Plugin\field\formatter;
|
|||
|
||||
use Drupal\field\Annotation\FieldFormatter;
|
||||
use Drupal\Core\Annotation\Translation;
|
||||
use Drupal\field\Plugin\Type\Formatter\FormatterBase;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
|
||||
/**
|
||||
|
|
@ -24,7 +23,7 @@ use Drupal\Core\Entity\EntityInterface;
|
|||
* }
|
||||
* )
|
||||
*/
|
||||
class GenericFileFormatter extends FormatterBase {
|
||||
class GenericFileFormatter extends FileFormatterBase {
|
||||
|
||||
/**
|
||||
* Implements \Drupal\field\Plugin\Type\Formatter\FormatterInterface::viewElements().
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ namespace Drupal\file\Plugin\field\formatter;
|
|||
|
||||
use Drupal\field\Annotation\FieldFormatter;
|
||||
use Drupal\Core\Annotation\Translation;
|
||||
use Drupal\field\Plugin\Type\Formatter\FormatterBase;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
|
||||
/**
|
||||
|
|
@ -24,7 +23,7 @@ use Drupal\Core\Entity\EntityInterface;
|
|||
* }
|
||||
* )
|
||||
*/
|
||||
class RSSEnclosureFormatter extends FormatterBase {
|
||||
class RSSEnclosureFormatter extends FileFormatterBase {
|
||||
|
||||
/**
|
||||
* Implements \Drupal\field\Plugin\Type\Formatter\FormatterInterface::viewElements().
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ namespace Drupal\file\Plugin\field\formatter;
|
|||
|
||||
use Drupal\field\Annotation\FieldFormatter;
|
||||
use Drupal\Core\Annotation\Translation;
|
||||
use Drupal\field\Plugin\Type\Formatter\FormatterBase;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
|
||||
/**
|
||||
|
|
@ -24,7 +23,7 @@ use Drupal\Core\Entity\EntityInterface;
|
|||
* }
|
||||
* )
|
||||
*/
|
||||
class TableFormatter extends FormatterBase {
|
||||
class TableFormatter extends FileFormatterBase {
|
||||
|
||||
/**
|
||||
* Implements \Drupal\field\Plugin\Type\Formatter\FormatterInterface::viewElements().
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ namespace Drupal\file\Plugin\field\formatter;
|
|||
|
||||
use Drupal\field\Annotation\FieldFormatter;
|
||||
use Drupal\Core\Annotation\Translation;
|
||||
use Drupal\field\Plugin\Type\Formatter\FormatterBase;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
|
||||
/**
|
||||
|
|
@ -24,7 +23,7 @@ use Drupal\Core\Entity\EntityInterface;
|
|||
* }
|
||||
* )
|
||||
*/
|
||||
class UrlPlainFormatter extends FormatterBase {
|
||||
class UrlPlainFormatter extends FileFormatterBase {
|
||||
|
||||
/**
|
||||
* Implements \Drupal\field\Plugin\Type\Formatter\FormatterInterface::viewElements().
|
||||
|
|
|
|||
|
|
@ -7,13 +7,12 @@
|
|||
|
||||
namespace Drupal\file\Type;
|
||||
|
||||
use Drupal\Core\Entity\Field\FieldItemBase;
|
||||
use Drupal\Core\TypedData\TypedDataInterface;
|
||||
use Drupal\field\Plugin\field\field_type\LegacyConfigFieldItem;
|
||||
|
||||
/**
|
||||
* Defines the 'file_field' entity field item.
|
||||
*/
|
||||
class FileItem extends FieldItemBase {
|
||||
class FileItem extends LegacyConfigFieldItem {
|
||||
|
||||
/**
|
||||
* Property definitions of the contained properties.
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ function image_field_info() {
|
|||
),
|
||||
'default_widget' => 'image_image',
|
||||
'default_formatter' => 'image',
|
||||
'field item class' => '\Drupal\image\Type\ImageItem',
|
||||
'class' => '\Drupal\image\Type\ImageItem',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -222,39 +222,6 @@ function _image_field_resolution_validate($element, &$form_state) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_prepare_view().
|
||||
*/
|
||||
function image_field_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items) {
|
||||
// Load the images.
|
||||
file_field_prepare_view($entity_type, $entities, $field, $instances, $langcode, $items);
|
||||
// If there are no files specified at all, use the default.
|
||||
foreach ($entities as $id => $entity) {
|
||||
if (empty($items[$id])) {
|
||||
$fid = array();
|
||||
// Use the default for the instance if one is available.
|
||||
if (!empty($instances[$id]['settings']['default_image'])) {
|
||||
$fid = array($instances[$id]['settings']['default_image']);
|
||||
}
|
||||
// Otherwise, use the default for the field.
|
||||
elseif (!empty($field['settings']['default_image'])) {
|
||||
$fid = array($field['settings']['default_image']);
|
||||
}
|
||||
|
||||
// Add the default image if one is found.
|
||||
if ($fid && ($file = file_load($fid[0]))) {
|
||||
$items[$id][0] = array(
|
||||
'is_default' => TRUE,
|
||||
'alt' => '',
|
||||
'title' => '',
|
||||
'entity' => $file,
|
||||
'fid' => $file->id(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_presave().
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ namespace Drupal\image\Plugin\field\formatter;
|
|||
|
||||
use Drupal\field\Annotation\FieldFormatter;
|
||||
use Drupal\Core\Annotation\Translation;
|
||||
use Drupal\field\Plugin\Type\Formatter\FormatterBase;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
|
||||
/**
|
||||
|
|
@ -28,7 +27,7 @@ use Drupal\Core\Entity\EntityInterface;
|
|||
* }
|
||||
* )
|
||||
*/
|
||||
class ImageFormatter extends FormatterBase {
|
||||
class ImageFormatter extends ImageFormatterBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\image\Plugin\field\formatter\ImageFormatterBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\image\Plugin\field\formatter;
|
||||
|
||||
use Drupal\file\Plugin\field\formatter\FileFormatterBase;
|
||||
|
||||
/**
|
||||
* Base class for image file formatters.
|
||||
*/
|
||||
abstract class ImageFormatterBase extends FileFormatterBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareView(array $entities, $langcode, array &$items) {
|
||||
parent::prepareView($entities, $langcode, $items);
|
||||
|
||||
// If there are no files specified at all, use the default.
|
||||
foreach ($entities as $id => $entity) {
|
||||
if (empty($items[$id])) {
|
||||
$fid = array();
|
||||
$instance = field_info_instance($entity->entityType(), $this->fieldDefinition->getFieldName(), $entity->bundle());
|
||||
// Use the default for the instance if one is available.
|
||||
if (!empty($instance['settings']['default_image'])) {
|
||||
$fid = array($instance['settings']['default_image']);
|
||||
}
|
||||
// Otherwise, use the default for the field.
|
||||
// Note, that we have to bypass getFieldSetting() as this returns the
|
||||
// instance-setting default.
|
||||
elseif (($field = $this->fieldDefinition->getField()) && !empty($field->settings['default_image'])) {
|
||||
$fid = array($field->settings['default_image']);
|
||||
}
|
||||
|
||||
// Add the default image if one is found.
|
||||
if ($fid && ($file = file_load($fid[0]))) {
|
||||
$items[$id][0] = array(
|
||||
'is_default' => TRUE,
|
||||
'alt' => '',
|
||||
'title' => '',
|
||||
'entity' => $file,
|
||||
'fid' => $file->id(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -7,12 +7,12 @@
|
|||
|
||||
namespace Drupal\image\Type;
|
||||
|
||||
use Drupal\Core\Entity\Field\FieldItemBase;
|
||||
use Drupal\field\Plugin\field\field_type\LegacyConfigFieldItem;
|
||||
|
||||
/**
|
||||
* Defines the 'image_field' entity field item.
|
||||
*/
|
||||
class ImageItem extends FieldItemBase {
|
||||
class ImageItem extends LegacyConfigFieldItem {
|
||||
|
||||
/**
|
||||
* Property definitions of the contained properties.
|
||||
|
|
@ -33,7 +33,7 @@ class ImageItem extends FieldItemBase {
|
|||
'label' => t('Referenced file id.'),
|
||||
);
|
||||
static::$propertyDefinitions['alt'] = array(
|
||||
'type' => 'boolean',
|
||||
'type' => 'string',
|
||||
'label' => t("Alternative image text, for the image's 'alt' attribute."),
|
||||
);
|
||||
static::$propertyDefinitions['title'] = array(
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@
|
|||
|
||||
namespace Drupal\link\Type;
|
||||
|
||||
use Drupal\Core\Entity\Field\FieldItemBase;
|
||||
use Drupal\field\Plugin\field\field_type\LegacyConfigFieldItem;
|
||||
|
||||
/**
|
||||
* Defines the 'link_field' entity field item.
|
||||
*/
|
||||
class LinkItem extends FieldItemBase {
|
||||
class LinkItem extends LegacyConfigFieldItem {
|
||||
|
||||
/**
|
||||
* Property definitions of the contained properties.
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ function link_field_info() {
|
|||
),
|
||||
'default_widget' => 'link_default',
|
||||
'default_formatter' => 'link',
|
||||
'field item class' => '\Drupal\link\Type\LinkItem',
|
||||
'class' => '\Drupal\link\Type\LinkItem',
|
||||
);
|
||||
return $types;
|
||||
}
|
||||
|
|
@ -54,24 +54,6 @@ function link_field_instance_settings_form($field, $instance) {
|
|||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_load().
|
||||
*/
|
||||
function link_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) {
|
||||
foreach ($entities as $id => $entity) {
|
||||
foreach ($items[$id] as $delta => &$item) {
|
||||
// Unserialize the attributes into an array. The value stored in the
|
||||
// field data should either be NULL or a non-empty serialized array.
|
||||
if (empty($item['attributes'])) {
|
||||
$item['attributes'] = array();
|
||||
}
|
||||
else {
|
||||
$item['attributes'] = unserialize($item['attributes']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_is_empty().
|
||||
*/
|
||||
|
|
@ -87,9 +69,6 @@ function link_field_presave(EntityInterface $entity, $field, $instance, $langcod
|
|||
// Trim any spaces around the URL and link text.
|
||||
$item['url'] = trim($item['url']);
|
||||
$item['title'] = trim($item['title']);
|
||||
|
||||
// Serialize the attributes array.
|
||||
$item['attributes'] = !empty($item['attributes']) ? serialize($item['attributes']) : NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -165,6 +165,7 @@ class MenuLinkStorageController extends DatabaseStorageController implements Men
|
|||
// Unlike the save() method from DatabaseStorageController, we invoke the
|
||||
// 'presave' hook first because we want to allow modules to alter the
|
||||
// entity before all the logic from our preSave() method.
|
||||
$this->invokeFieldMethod('preSave', $entity);
|
||||
$this->invokeHook('presave', $entity);
|
||||
$entity->preSave($this);
|
||||
|
||||
|
|
@ -180,6 +181,7 @@ class MenuLinkStorageController extends DatabaseStorageController implements Men
|
|||
if (!$entity->isNew()) {
|
||||
$this->resetCache(array($entity->{$this->idKey}));
|
||||
$entity->postSave($this, TRUE);
|
||||
$this->invokeFieldMethod('update', $entity);
|
||||
$this->invokeHook('update', $entity);
|
||||
}
|
||||
else {
|
||||
|
|
@ -188,6 +190,7 @@ class MenuLinkStorageController extends DatabaseStorageController implements Men
|
|||
|
||||
$entity->enforceIsNew(FALSE);
|
||||
$entity->postSave($this, FALSE);
|
||||
$this->invokeFieldMethod('insert', $entity);
|
||||
$this->invokeHook('insert', $entity);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,7 +99,6 @@ use Drupal\Core\Entity\EntityInterface;
|
|||
* - Validating a node during editing form submit (calling
|
||||
* node_form_validate()):
|
||||
* - hook_node_validate() (all)
|
||||
* - field_attach_form_validate()
|
||||
* - Searching (calling node_search_execute()):
|
||||
* - hook_ranking() (all)
|
||||
* - Query is executed to find matching nodes
|
||||
|
|
|
|||
|
|
@ -109,7 +109,6 @@ function node_add($node_type) {
|
|||
*/
|
||||
function node_preview(EntityInterface $node) {
|
||||
if (node_access('create', $node) || node_access('update', $node)) {
|
||||
_field_invoke_multiple('load', 'node', array($node->nid => $node));
|
||||
// Load the user's name when needed.
|
||||
if (isset($node->name)) {
|
||||
// The use of isset() is mandatory in the context of user IDs, because
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ namespace Drupal\number\Plugin\field\widget;
|
|||
use Drupal\Component\Annotation\Plugin;
|
||||
use Drupal\Core\Annotation\Translation;
|
||||
use Drupal\field\Plugin\Type\Widget\WidgetBase;
|
||||
use Symfony\Component\Validator\ConstraintViolationInterface;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'number' widget.
|
||||
|
|
@ -91,7 +92,7 @@ class NumberWidget extends WidgetBase {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function errorElement(array $element, array $error, array $form, array &$form_state) {
|
||||
public function errorElement(array $element, ConstraintViolationInterface $error, array $form, array &$form_state) {
|
||||
return $element['value'];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@
|
|||
|
||||
namespace Drupal\number\Type;
|
||||
|
||||
use Drupal\Core\Entity\Field\FieldItemBase;
|
||||
use Drupal\field\Plugin\field\field_type\LegacyConfigFieldItem;
|
||||
|
||||
/**
|
||||
* Defines the 'number_decimal_field' entity field item.
|
||||
*/
|
||||
class DecimalItem extends FieldItemBase {
|
||||
class DecimalItem extends LegacyConfigFieldItem {
|
||||
|
||||
/**
|
||||
* Definitions of the contained properties.
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@
|
|||
|
||||
namespace Drupal\number\Type;
|
||||
|
||||
use Drupal\Core\Entity\Field\FieldItemBase;
|
||||
use Drupal\field\Plugin\field\field_type\LegacyConfigFieldItem;
|
||||
|
||||
/**
|
||||
* Defines the 'number_float_field' entity field item.
|
||||
*/
|
||||
class FloatItem extends FieldItemBase {
|
||||
class FloatItem extends LegacyConfigFieldItem {
|
||||
|
||||
/**
|
||||
* Definitions of the contained properties.
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@
|
|||
|
||||
namespace Drupal\number\Type;
|
||||
|
||||
use Drupal\Core\Entity\Field\FieldItemBase;
|
||||
use Drupal\field\Plugin\field\field_type\LegacyConfigFieldItem;
|
||||
|
||||
/**
|
||||
* Defines the 'number_integer_field' entity field item.
|
||||
*/
|
||||
class IntegerItem extends FieldItemBase {
|
||||
class IntegerItem extends LegacyConfigFieldItem {
|
||||
|
||||
/**
|
||||
* Definitions of the contained properties.
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ function number_field_info() {
|
|||
'instance_settings' => array('min' => '', 'max' => '', 'prefix' => '', 'suffix' => ''),
|
||||
'default_widget' => 'number',
|
||||
'default_formatter' => 'number_integer',
|
||||
'field item class' => '\Drupal\number\Type\IntegerItem',
|
||||
'class' => '\Drupal\number\Type\IntegerItem',
|
||||
),
|
||||
'number_decimal' => array(
|
||||
'label' => t('Decimal'),
|
||||
|
|
@ -40,7 +40,7 @@ function number_field_info() {
|
|||
'instance_settings' => array('min' => '', 'max' => '', 'prefix' => '', 'suffix' => ''),
|
||||
'default_widget' => 'number',
|
||||
'default_formatter' => 'number_decimal',
|
||||
'field item class' => '\Drupal\number\Type\DecimalItem',
|
||||
'class' => '\Drupal\number\Type\DecimalItem',
|
||||
),
|
||||
'number_float' => array(
|
||||
'label' => t('Float'),
|
||||
|
|
@ -48,7 +48,7 @@ function number_field_info() {
|
|||
'instance_settings' => array('min' => '', 'max' => '', 'prefix' => '', 'suffix' => ''),
|
||||
'default_widget' => 'number',
|
||||
'default_formatter' => 'number_decimal',
|
||||
'field item class' => '\Drupal\number\Type\FloatItem',
|
||||
'class' => '\Drupal\number\Type\FloatItem',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,6 @@
|
|||
|
||||
namespace Drupal\options\Tests;
|
||||
|
||||
use Drupal\Core\Language\Language;
|
||||
use Drupal\field\FieldValidationException;
|
||||
|
||||
/**
|
||||
* Tests the Options field allowed values function.
|
||||
*/
|
||||
|
|
@ -26,29 +23,19 @@ class OptionsDynamicValuesValidationTest extends OptionsDynamicValuesTest {
|
|||
* Test that allowed values function gets the entity.
|
||||
*/
|
||||
function testDynamicAllowedValues() {
|
||||
// Verify that the test passes against every value we had.
|
||||
// Verify that validation passes against every value we had.
|
||||
foreach ($this->test as $key => $value) {
|
||||
$this->entity->test_options->value = $value;
|
||||
try {
|
||||
field_attach_validate($this->entity);
|
||||
$this->pass("$key should pass");
|
||||
}
|
||||
catch (FieldValidationException $e) {
|
||||
// This will display as an exception, no need for a separate error.
|
||||
throw($e);
|
||||
}
|
||||
$violations = $this->entity->test_options->validate();
|
||||
$this->assertEqual(count($violations), 0, "$key is a valid value");
|
||||
}
|
||||
// Now verify that the test does not pass against anything else.
|
||||
|
||||
// Now verify that validation does not pass against anything else.
|
||||
foreach ($this->test as $key => $value) {
|
||||
$this->entity->test_options->value = is_numeric($value) ? (100 - $value) : ('X' . $value);
|
||||
$pass = FALSE;
|
||||
try {
|
||||
field_attach_validate($this->entity);
|
||||
}
|
||||
catch (FieldValidationException $e) {
|
||||
$pass = TRUE;
|
||||
}
|
||||
$this->assertTrue($pass, $key . ' should not pass');
|
||||
$violations = $this->entity->test_options->validate();
|
||||
$this->assertEqual(count($violations), 1, "$key is not a valid value");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\options\Type\ListBooleanItem.
|
||||
*/
|
||||
|
||||
namespace Drupal\options\Type;
|
||||
|
||||
/**
|
||||
* Defines the 'list_boolean' entity field item.
|
||||
*/
|
||||
class ListBooleanItem extends ListIntegerItem { }
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\options\Type\ListFloatItem.
|
||||
*/
|
||||
|
||||
namespace Drupal\options\Type;
|
||||
|
||||
use Drupal\field\Plugin\field\field_type\LegacyConfigFieldItem;
|
||||
|
||||
/**
|
||||
* Defines the 'list_float' entity field item.
|
||||
*/
|
||||
class ListFloatItem extends LegacyConfigFieldItem {
|
||||
|
||||
/**
|
||||
* Definitions of the contained properties.
|
||||
*
|
||||
* @see FloatItem::getPropertyDefinitions()
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
static $propertyDefinitions;
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::getPropertyDefinitions().
|
||||
*/
|
||||
public function getPropertyDefinitions() {
|
||||
|
||||
if (!isset(static::$propertyDefinitions)) {
|
||||
static::$propertyDefinitions['value'] = array(
|
||||
'type' => 'float',
|
||||
'label' => t('Float value'),
|
||||
);
|
||||
}
|
||||
return static::$propertyDefinitions;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\options\Type\ListIntegerItem.
|
||||
*/
|
||||
|
||||
namespace Drupal\options\Type;
|
||||
|
||||
use Drupal\field\Plugin\field\field_type\LegacyConfigFieldItem;
|
||||
|
||||
/**
|
||||
* Defines the 'list_integer' entity field item.
|
||||
*/
|
||||
class ListIntegerItem extends LegacyConfigFieldItem {
|
||||
|
||||
/**
|
||||
* Definitions of the contained properties.
|
||||
*
|
||||
* @see IntegerItem::getPropertyDefinitions()
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
static $propertyDefinitions;
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::getPropertyDefinitions().
|
||||
*/
|
||||
public function getPropertyDefinitions() {
|
||||
|
||||
if (!isset(static::$propertyDefinitions)) {
|
||||
static::$propertyDefinitions['value'] = array(
|
||||
'type' => 'integer',
|
||||
'label' => t('Integer value'),
|
||||
);
|
||||
}
|
||||
return static::$propertyDefinitions;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\options\Type\ListTextItem.
|
||||
*/
|
||||
|
||||
namespace Drupal\options\Type;
|
||||
|
||||
use Drupal\field\Plugin\field\field_type\LegacyConfigFieldItem;
|
||||
|
||||
/**
|
||||
* Defines the 'list_text' configurable field type.
|
||||
*/
|
||||
class ListTextItem extends LegacyConfigFieldItem {
|
||||
|
||||
/**
|
||||
* Definitions of the contained properties.
|
||||
*
|
||||
* @see TextItem::getPropertyDefinitions()
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
static $propertyDefinitions;
|
||||
|
||||
/**
|
||||
* Implements ComplexDataInterface::getPropertyDefinitions().
|
||||
*/
|
||||
public function getPropertyDefinitions() {
|
||||
|
||||
if (!isset(static::$propertyDefinitions)) {
|
||||
static::$propertyDefinitions['value'] = array(
|
||||
'type' => 'string',
|
||||
'label' => t('Text value'),
|
||||
);
|
||||
}
|
||||
return static::$propertyDefinitions;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -36,7 +36,7 @@ function options_field_info() {
|
|||
'settings' => array('allowed_values' => array(), 'allowed_values_function' => ''),
|
||||
'default_widget' => 'options_select',
|
||||
'default_formatter' => 'list_default',
|
||||
'field item class' => '\Drupal\number\Type\IntegerItem',
|
||||
'class' => '\Drupal\options\Type\ListIntegerItem',
|
||||
),
|
||||
'list_float' => array(
|
||||
'label' => t('List (float)'),
|
||||
|
|
@ -44,7 +44,7 @@ function options_field_info() {
|
|||
'settings' => array('allowed_values' => array(), 'allowed_values_function' => ''),
|
||||
'default_widget' => 'options_select',
|
||||
'default_formatter' => 'list_default',
|
||||
'field item class' => '\Drupal\number\Type\FloatItem',
|
||||
'class' => '\Drupal\options\Type\ListFloatItem',
|
||||
),
|
||||
'list_text' => array(
|
||||
'label' => t('List (text)'),
|
||||
|
|
@ -52,7 +52,7 @@ function options_field_info() {
|
|||
'settings' => array('allowed_values' => array(), 'allowed_values_function' => ''),
|
||||
'default_widget' => 'options_select',
|
||||
'default_formatter' => 'list_default',
|
||||
'field item class' => '\Drupal\text\Type\TextItem',
|
||||
'class' => '\Drupal\options\Type\ListTextItem',
|
||||
),
|
||||
'list_boolean' => array(
|
||||
'label' => t('Boolean'),
|
||||
|
|
@ -60,7 +60,7 @@ function options_field_info() {
|
|||
'settings' => array('allowed_values' => array(), 'allowed_values_function' => ''),
|
||||
'default_widget' => 'options_buttons',
|
||||
'default_formatter' => 'list_default',
|
||||
'field item class' => '\Drupal\number\Type\IntegerItem',
|
||||
'class' => '\Drupal\options\Type\ListBooleanItem',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue