diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php index 6bb91e1..e51cf0d 100644 --- a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php +++ b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php @@ -654,11 +654,16 @@ protected function preSaveRevision(\stdClass $record, EntityInterface $entity) { * 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. */ protected function invokeHook($hook, EntityInterface $entity) { + if (!empty($this->entityInfo['fieldable'])) { + $this->configFieldOp($hook, $entity->getBCEntity()); + } + $function = 'field_attach_' . $hook; // @todo: field_attach_delete_revision() is named the wrong way round, // consider renaming it. @@ -675,6 +680,29 @@ protected function invokeHook($hook, EntityInterface $entity) { } /** + * @todo Temporary, to avoid duplicating code between DSC & DSCNG for now. + */ + protected function configFieldOp($hook, EntityInterface $entity) { + if (in_array($hook, array('presave', 'insert', 'update', 'delete', 'revision_delete'))) { + $method = ($hook == 'revision_delete') ? 'revisionDelete' : $hook; + // @todo Check the multilingual logic. Current logic in _field_invoke() + // depends on each $field: + // $available_langcodes = field_available_languages($entity_type, $field); + // $langcodes = _field_language_suggestion($available_langcodes, NULL, $field_name); + // @todo getTranslationLanguages() seems like a potential perf drag ? + foreach (array_keys($entity->getTranslationLanguages()) as $langcode) { + // @todo translation not always available ? + // Install fails on user entity and even sending the BCentity doesn't work. + if ($translation = $entity->getTranslation($langcode)) { + foreach (array_keys($translation->getPropertyDefinitions()) as $property) { + $translation->get($property)->$method(); + } + } + } + } + } + + /** * Implements \Drupal\Core\Entity\EntityStorageControllerInterface::getFieldDefinitions(). */ public function getFieldDefinitions(array $constraints) { diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php index 9b7020a..e3c2dc2 100644 --- a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php +++ b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php @@ -409,6 +409,10 @@ protected function savePropertyData(EntityInterface $entity) { * Invokes field API attachers with a BC entity. */ protected function invokeHook($hook, EntityInterface $entity) { + if (!empty($this->entityInfo['fieldable'])) { + $this->configFieldOp($hook, $entity); + } + $function = 'field_attach_' . $hook; // @todo: field_attach_delete_revision() is named the wrong way round, // consider renaming it. diff --git a/core/lib/Drupal/Core/Entity/Field/FieldInterface.php b/core/lib/Drupal/Core/Entity/Field/FieldInterface.php index 7b1d235..cb880e6 100644 --- a/core/lib/Drupal/Core/Entity/Field/FieldInterface.php +++ b/core/lib/Drupal/Core/Entity/Field/FieldInterface.php @@ -75,4 +75,46 @@ public function getPropertyDefinition($name); * @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 mathod 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(); + } diff --git a/core/lib/Drupal/Core/Entity/Field/FieldItemBase.php b/core/lib/Drupal/Core/Entity/Field/FieldItemBase.php index 2c3feef..37f5b95 100644 --- a/core/lib/Drupal/Core/Entity/Field/FieldItemBase.php +++ b/core/lib/Drupal/Core/Entity/Field/FieldItemBase.php @@ -25,8 +25,8 @@ /** * Overrides \Drupal\Core\TypedData\TypedData::__construct(). */ - public function __construct(array $definition, $name = NULL, TypedDataInterface $parent = NULL) { - parent::__construct($definition, $name, $parent); + public function __construct(array $definition, $plugin_id, array $plugin_definition, $name = NULL, TypedDataInterface $parent = NULL) { + parent::__construct($definition, $plugin_id, $plugin_definition, $name, $parent); // Initialize computed properties by default, such that they get cloned // with the whole item. foreach ($this->getPropertyDefinitions() as $name => $definition) { @@ -136,4 +136,58 @@ public function onChange($property_name) { // updated property object. unset($this->values[$property_name]); } -} \ No newline at end of file + + /** + * {@inheritdoc} + */ + public function presave() { } + + /** + * {@inheritdoc} + */ + public function insert() { } + + /** + * {@inheritdoc} + */ + public function update() { } + + /** + * {@inheritdoc} + */ + public function delete() { } + + /** + * {@inheritdoc} + */ + public function deleteRevision() { } + + + + // @todo + + + + /** + * {@inheritdoc} + * + * @todo rename postLoad()? + */ + public function load(array $entities, array $instances, $langcode, array &$items, $age) { } + + /** + * {@inheritdoc} + */ + public function prepareView(array $entities, array $instances, $langcode, array &$items) { } + + /** + * {@inheritdoc} + */ + public function TODOvalidate(array &$errors) { } + + /** + * {@inheritdoc} + */ + public function prepareTranslation(EntityInterface $source_entity, $source_langcode) { } + +} diff --git a/core/lib/Drupal/Core/Entity/Field/FieldItemInterface.php b/core/lib/Drupal/Core/Entity/Field/FieldItemInterface.php index 31adec9..5bb7c3f 100644 --- a/core/lib/Drupal/Core/Entity/Field/FieldItemInterface.php +++ b/core/lib/Drupal/Core/Entity/Field/FieldItemInterface.php @@ -70,4 +70,46 @@ public function __isset($property_name); * 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 mathod 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(); + } diff --git a/core/lib/Drupal/Core/Entity/Field/Type/EntityWrapper.php b/core/lib/Drupal/Core/Entity/Field/Type/EntityWrapper.php index eb6ae7d..e99187b 100644 --- a/core/lib/Drupal/Core/Entity/Field/Type/EntityWrapper.php +++ b/core/lib/Drupal/Core/Entity/Field/Type/EntityWrapper.php @@ -61,8 +61,8 @@ class EntityWrapper extends TypedData implements IteratorAggregate, ComplexDataI /** * Overrides TypedData::__construct(). */ - public function __construct(array $definition, $name = NULL, TypedDataInterface $parent = NULL) { - parent::__construct($definition, $name, $parent); + public function __construct(array $definition, $plugin_id, array $plugin_definition, $name = NULL, TypedDataInterface $parent = NULL) { + parent::__construct($definition, $plugin_id, $plugin_definition, $name, $parent); $this->entityType = isset($this->definition['constraints']['EntityType']) ? $this->definition['constraints']['EntityType'] : NULL; } diff --git a/core/lib/Drupal/Core/Entity/Field/Type/Field.php b/core/lib/Drupal/Core/Entity/Field/Type/Field.php index 5d0a4ff..3cfc8b9 100644 --- a/core/lib/Drupal/Core/Entity/Field/Type/Field.php +++ b/core/lib/Drupal/Core/Entity/Field/Type/Field.php @@ -36,8 +36,8 @@ class Field extends ItemList implements FieldInterface { /** * Overrides TypedData::__construct(). */ - public function __construct(array $definition, $name = NULL, TypedDataInterface $parent = NULL) { - parent::__construct($definition, $name, $parent); + public function __construct(array $definition, $plugin_id, array $plugin_definition, $name = NULL, TypedDataInterface $parent = NULL) { + parent::__construct($definition, $plugin_id, $plugin_definition, $name, $parent); // Always initialize one empty item as most times a value for at least one // item will be present. That way prototypes created by // \Drupal\Core\TypedData\TypedDataManager::getPropertyInstance() will @@ -101,6 +101,18 @@ public function setValue($values, $notify = TRUE) { } /** + * @todo put this in FieldInterface ? + */ + public function filterEmpty() { + if (isset($this->list)) { + // @todo or call setValue ? + $this->list = array_values(array_filter($this->list, function ($item) { + return !$item->isEmpty(); + })); + } + } + + /** * Implements \Drupal\Core\Entity\Field\FieldInterface::getPropertyDefinition(). */ public function getPropertyDefinition($name) { @@ -201,4 +213,60 @@ public function defaultAccess($operation = 'view', User $account = NULL) { // Grant access per default. return TRUE; } + + /** + * {@inheritdoc} + */ + public function presave() { + // Filter out empty items. + // @todo It seems nothing filters out empty Field API fields on presave in HEAD ? + // At any rate, shouldn't be needed when validation happens on programmatic + // saves too. + $this->filterEmpty(); + + $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}(); + } + } + } + } diff --git a/core/lib/Drupal/Core/TypedData/TypedData.php b/core/lib/Drupal/Core/TypedData/TypedData.php index e9558f3..ad2634e 100644 --- a/core/lib/Drupal/Core/TypedData/TypedData.php +++ b/core/lib/Drupal/Core/TypedData/TypedData.php @@ -15,6 +15,11 @@ */ abstract class TypedData implements TypedDataInterface { + // @todo Should extend PluginBase, but PluginInspectionInterface::getDefinition() + // should be renamed getPluginDefinition() first. + protected $pluginId; + protected $pluginDefinition; + /** * The data definition. * @@ -41,6 +46,10 @@ * * @param array $definition * The data definition. + * @param string $plugin_id + * The plugin_id for the plugin instance. + * @param array $plugin_definition + * The plugin implementation definition. * @param string $name * (optional) The name of the created property, or NULL if it is the root * of a typed data tree. Defaults to NULL. @@ -50,12 +59,21 @@ * * @see Drupal\Core\TypedData\TypedDataManager::create() */ - public function __construct(array $definition, $name = NULL, TypedDataInterface $parent = NULL) { + public function __construct(array $definition, $plugin_id, array $plugin_definition, $name = NULL, TypedDataInterface $parent = NULL) { $this->definition = $definition; + $this->pluginId = $plugin_id; + $this->pluginDefinition = $plugin_definition; $this->parent = $parent; $this->name = $name; } + public function getPluginId() { + return $this->pluginId; + } + public function getPluginDefinition() { + return $this->pluginDefinition; + } + /** * Implements \Drupal\Core\TypedData\TypedDataInterface::getType(). */ diff --git a/core/lib/Drupal/Core/TypedData/TypedDataFactory.php b/core/lib/Drupal/Core/TypedData/TypedDataFactory.php index ef67cf2..1521eff 100644 --- a/core/lib/Drupal/Core/TypedData/TypedDataFactory.php +++ b/core/lib/Drupal/Core/TypedData/TypedDataFactory.php @@ -57,6 +57,8 @@ public function createInstance($plugin_id, array $configuration, $name = NULL, $ if (!isset($class)) { throw new PluginException(sprintf('The plugin (%s) did not specify an instance class.', $plugin_id)); } - return new $class($configuration, $name, $parent); + + return new $class($configuration, $plugin_id, $type_definition, $name, $parent); } + } diff --git a/core/lib/Drupal/Core/TypedData/TypedDataManager.php b/core/lib/Drupal/Core/TypedData/TypedDataManager.php index 195e32d..e203ce4 100644 --- a/core/lib/Drupal/Core/TypedData/TypedDataManager.php +++ b/core/lib/Drupal/Core/TypedData/TypedDataManager.php @@ -9,6 +9,7 @@ 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'); diff --git a/core/modules/datetime/lib/Drupal/datetime/Type/DateTimeItem.php b/core/modules/datetime/lib/Drupal/datetime/Type/DateTimeItem.php index 2d14e5a..e3e3f4e 100644 --- a/core/modules/datetime/lib/Drupal/datetime/Type/DateTimeItem.php +++ b/core/modules/datetime/lib/Drupal/datetime/Type/DateTimeItem.php @@ -7,12 +7,12 @@ namespace Drupal\datetime\Type; -use Drupal\Core\Entity\Field\FieldItemBase; +use Drupal\field\Plugin\field\field_type\LegacyFieldItem; /** * Defines the 'datetime' entity field item. */ -class DateTimeItem extends FieldItemBase { +class DateTimeItem extends LegacyFieldItem { /** * Field definitions of the contained properties. diff --git a/core/modules/email/lib/Drupal/email/Type/EmailItem.php b/core/modules/email/lib/Drupal/email/Type/EmailItem.php index c1705f5..be9d01b 100644 --- a/core/modules/email/lib/Drupal/email/Type/EmailItem.php +++ b/core/modules/email/lib/Drupal/email/Type/EmailItem.php @@ -7,12 +7,12 @@ namespace Drupal\email\Type; -use Drupal\Core\Entity\Field\FieldItemBase; +use Drupal\field\Plugin\field\field_type\LegacyFieldItem; /** * Defines the 'email_field' entity field item. */ -class EmailItem extends FieldItemBase { +class EmailItem extends LegacyFieldItem { /** * Definitions of the contained properties. diff --git a/core/modules/entity_reference/entity_reference.module b/core/modules/entity_reference/entity_reference.module index 3c68c46..74eeb6e 100644 --- a/core/modules/entity_reference/entity_reference.module +++ b/core/modules/entity_reference/entity_reference.module @@ -28,7 +28,6 @@ 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', ); return $field_info; diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/Type/ConfigurableEntityReferenceItem.php b/core/modules/entity_reference/lib/Drupal/entity_reference/Type/ConfigurableEntityReferenceItem.php index cc7a3f4..0031dbf 100644 --- a/core/modules/entity_reference/lib/Drupal/entity_reference/Type/ConfigurableEntityReferenceItem.php +++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Type/ConfigurableEntityReferenceItem.php @@ -8,6 +8,7 @@ namespace Drupal\entity_reference\Type; use Drupal\Core\Entity\Field\Type\EntityReferenceItem; +use Drupal\Core\TypedData\TypedDataInterface; /** * Defines the 'entity_reference_configurable' entity field item. @@ -18,7 +19,7 @@ * Required settings (below the definition's 'settings' key) are: * - target_type: The entity type to reference. */ -class ConfigurableEntityReferenceItem extends EntityReferenceItem { +class ConfigurableEntityReferenceItem extends EntityReferenceItem implements CFieldItemInterface { /** * Definitions of the contained properties. @@ -30,6 +31,34 @@ class ConfigurableEntityReferenceItem extends EntityReferenceItem { static $propertyDefinitions; /** + * The Field instance definition. + * + * @var \Drupal\field\Plugin\Core\Entity\FieldInstance + */ + protected $instance; + + /** + * Constructs a Drupal\Component\Plugin\ConfigurableEntityReferenceItem object. + * + * Duplicated from \Drupal\field\Plugin\Type\FieldType\CFieldItemBase, since + * we cannot extend it. + * + * @param string $plugin_id + * The plugin_id for the plugin instance. + * @param array $plugin_definition + * The plugin implementation definition. + * @param \Drupal\field\Plugin\Core\Entity\Field $field + * The field definition. + */ + public function __construct(array $definition, $plugin_id, array $plugin_definition, $name = NULL, TypedDataInterface $parent = NULL) { + parent::__construct($definition, $plugin_id, $plugin_definition, $name, $parent); + // @todo No good, the instance must be injected somehow. + $entity = $parent->getParent(); + $instances = FieldAPI::fieldInfo()->getBundleInstances($entity->entityType(), $entity->bundle()); + $this->instance = $instances[$parent->name]; + } + + /** * Overrides \Drupal\Core\Entity\Field\Type\EntityReferenceItem::getPropertyDefinitions(). */ public function getPropertyDefinitions() { @@ -62,4 +91,80 @@ public function getPropertyDefinitions() { return static::$propertyDefinitions[$target_type]; } + /** + * {@inheritdoc} + * + * Duplicated from \Drupal\field\Plugin\field\field_type\LegacyFieldItem, + * since we cannot extend it. + */ + public static function schema(Field $field) { + $definition = \Drupal::typedData()->getDefinition('field_type:' . $field->type); + $module = $definition['module']; + module_load_install($module); + $callback = "{$module}_field_schema"; + if (function_exists($callback)) { + return $callback($field); + } + } + + /** + * {@inheritdoc} + * + * Duplicated from \Drupal\field\Plugin\field\field_type\LegacyFieldItem, + * since we cannot extend it. + */ + public function isEmpty() { + $callback = $this->getLegacyCallback('is_empty'); + // The previous hook was never called on an empty item, but EntityNG always + // creates a FieldItem element for an empty field. + $item = $this->getValue(); + return empty($item) || $callback($item, $this->instance->getField()); + } + + /** + * {@inheritdoc} + * + * Duplicated from \Drupal\field\Plugin\field\field_type\LegacyFieldItem, + * since we cannot extend it. + */ + public function settingsForm(array $form, array &$form_state, $has_data) { + 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->instance->getField(), $this->instance, $has_data); + } + } + + /** + * {@inheritdoc} + * + * Duplicated from \Drupal\field\Plugin\field\field_type\LegacyFieldItem, + * since we cannot extend it. + */ + public function instanceSettingsForm(array $form, array &$form_state) { + if ($callback = $this->getLegacyCallback('instance_settings_form')) { + return $callback($this->instance->getField(), $this->instance, $form_state); + } + } + + /** + * Returns the legacy callback for a given field type "hook". + * + * Duplicated from \Drupal\field\Plugin\field\field_type\LegacyFieldItem, + * 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) { + $module = $this->pluginDefinition['module']; + $callback = "{$module}_field_{$hook}"; + if (function_exists($callback)) { + return $callback; + } + } + } diff --git a/core/modules/field/field.api.php b/core/modules/field/field.api.php index 5ba15c9..977c28f 100644 --- a/core/modules/field/field.api.php +++ b/core/modules/field/field.api.php @@ -208,468 +208,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. - * @param $has_data - * TRUE if the field has data in storage currently. - */ -function hook_field_storage_update_field($field, $prior_field, $has_data) { - if (!$has_data) { - // 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 $item - * An item that may or may not be empty. - * @param $field - * The field to which $item belongs. - * - * @return - * TRUE if $field's type considers $item not to contain any data; FALSE - * otherwise. - */ -function hook_field_is_empty($item, $field) { - if (empty($item['value']) && (string) $item['value'] !== '0') { - return TRUE; - } - return FALSE; -} - -/** * @} End of "defgroup field_types". */ @@ -965,42 +503,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. * * This hook is invoked while preprocessing the field.tpl.php template file in @@ -1019,30 +521,6 @@ function hook_field_attach_preprocess_alter(&$variables, $context) { } /** - * 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(). * * This hook is invoked in field_purge_data() and allows modules to act on @@ -1640,6 +1118,41 @@ function hook_field_storage_create_field($field) { } /** + * 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. + * @param $has_data + * TRUE if the field has data in storage currently. + */ +function hook_field_storage_update_field($field, $prior_field, $has_data) { + if (!$has_data) { + // 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. * * This hook is invoked during the deletion of a field to ask the field storage diff --git a/core/modules/field/field.attach.inc b/core/modules/field/field.attach.inc index e61f659..32b4a72 100644 --- a/core/modules/field/field.attach.inc +++ b/core/modules/field/field.attach.inc @@ -1144,25 +1144,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. * * The passed-in entity must already contain its id and (if applicable) @@ -1180,8 +1161,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(); @@ -1209,9 +1188,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); } /** @@ -1224,8 +1200,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(); @@ -1258,9 +1232,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()); @@ -1278,8 +1249,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) { @@ -1294,9 +1263,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()); @@ -1314,8 +1280,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) { @@ -1329,9 +1293,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); } /** diff --git a/core/modules/field/field.default.inc b/core/modules/field/field.default.inc deleted file mode 100644 index 27ba32c..0000000 --- a/core/modules/field/field.default.inc +++ /dev/null @@ -1,83 +0,0 @@ -{$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, $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_NOT_SPECIFIED. - if ($langcode == LANGUAGE_NOT_SPECIFIED) { - $source_langcode = LANGUAGE_NOT_SPECIFIED; - } - if (isset($source_entity->{$field_name}[$source_langcode])) { - $items = $source_entity->{$field_name}[$source_langcode]; - } -} diff --git a/core/modules/field/field.info.inc b/core/modules/field/field.info.inc index 38edcdb..b789b90 100644 --- a/core/modules/field/field.info.inc +++ b/core/modules/field/field.info.inc @@ -36,6 +36,9 @@ function field_info_cache_clear() { // functions are moved to the entity API. entity_info_cache_clear(); + // Clear typed data definitions. + \Drupal::service('typed_data')->clearCachedDefinitions(); + _field_info_collate_types_reset(); Field::fieldInfo()->flush(); } @@ -45,11 +48,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 @@ -78,25 +76,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'); @@ -184,15 +166,11 @@ function field_info_field_map() { * 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.field.field_type')->getDefinition($field_type); } else { - return $field_types; + return Drupal::service('plugin.manager.field.field_type')->getDefinitions(); } } diff --git a/core/modules/field/field.module b/core/modules/field/field.module index db31d77..26662db 100644 --- a/core/modules/field/field.module +++ b/core/modules/field/field.module @@ -13,7 +13,6 @@ * 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,21 +227,14 @@ function field_system_info_alter(&$info, $file, $type) { } /** - * Implements hook_data_type_info() to register data types for all field types. + * Implements hook_data_type_info(). */ 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', - ); - } + // Expose each "configurable field" type as a data type. We add one single + // entry, which will be expanded through plugin derivatives. + $items['field_type'] = array( + 'derivative' => '\Drupal\field\Plugin\DataType\ConfigurableFieldDataType', + ); return $items; } @@ -288,7 +280,6 @@ 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; @@ -299,7 +290,7 @@ function field_entity_field_info($entity_type) { // @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', + 'type' => 'field_type:' . $field['type'], 'configurable' => TRUE, 'translatable' => !empty($field['translatable']) ); @@ -495,63 +486,6 @@ function field_get_default_value(EntityInterface $entity, $field, $instance, $la } /** - * Filters out empty field values. - * - * @param $field - * The field definition. - * @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, $items) { - $function = $field['module'] . '_field_is_empty'; - foreach ((array) $items as $delta => $item) { - // Explicitly break if the function is undefined. - if ($function($item, $field)) { - 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(). * * Sorts using ['_weight']['#value'] @@ -900,6 +834,8 @@ function field_get_items(EntityInterface $entity, $field_name, $langcode = NULL) /** * Determines whether a field has any data. * + * @todo Move to a method on Field class. + * * @param $field * A field structure. * diff --git a/core/modules/field/field.services.yml b/core/modules/field/field.services.yml index f4c28db..023083d 100644 --- a/core/modules/field/field.services.yml +++ b/core/modules/field/field.services.yml @@ -1,4 +1,7 @@ services: + plugin.manager.field.field_type: + class: Drupal\field\Plugin\Type\FieldType\FieldTypePluginManager + arguments: ['@container.namespaces'] plugin.manager.field.widget: class: Drupal\field\Plugin\Type\Widget\WidgetPluginManager arguments: ['@container.namespaces'] diff --git a/core/modules/field/lib/Drupal/field/FieldInterface.php b/core/modules/field/lib/Drupal/field/FieldInterface.php index 32673ff..acbdc20 100644 --- a/core/modules/field/lib/Drupal/field/FieldInterface.php +++ b/core/modules/field/lib/Drupal/field/FieldInterface.php @@ -33,6 +33,17 @@ 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. * * The content of the returned value depends on the storage backend, and some diff --git a/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/Field.php b/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/Field.php index e7e9bd5..4b3a04d 100644 --- a/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/Field.php +++ b/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/Field.php @@ -196,6 +196,13 @@ class Field extends ConfigEntityBase implements FieldInterface { public $deleted = FALSE; /** + * The field type handler. + * + * @var \Drupal\field\Plugin\Type\FieldType\CFieldItemInterface + */ + protected $handler; + + /** * The field schema. * * @var array @@ -435,15 +442,12 @@ public function delete() { */ 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::typedData()->getDefinition('field_type:' . $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']), field_reserved_columns())) { @@ -463,6 +467,20 @@ public function getSchema() { /** * {@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} + */ public function getStorageDetails() { if (!isset($this->storageDetails)) { $module_handler = \Drupal::moduleHandler(); @@ -563,5 +581,4 @@ public function unserialize($serialized) { $this->__construct(unserialize($serialized)); } - } diff --git a/core/modules/field/lib/Drupal/field/Plugin/DataType/ConfigurableFieldDataType.php b/core/modules/field/lib/Drupal/field/Plugin/DataType/ConfigurableFieldDataType.php new file mode 100644 index 0000000..50bd967 --- /dev/null +++ b/core/modules/field/lib/Drupal/field/Plugin/DataType/ConfigurableFieldDataType.php @@ -0,0 +1,51 @@ +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.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; + } + +} diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/CField.php b/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/CField.php new file mode 100644 index 0000000..a1ecad5 --- /dev/null +++ b/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/CField.php @@ -0,0 +1,86 @@ +getBundleInstances($parent->entityType(), $parent->bundle()); + $this->instance = $instances[$name]; + } + + + // @todo... former code in field.default.inc + + /** + * {@inheritdoc} + */ + public function validate() { + // Filter out empty items. + // @todo It seems nothing filters out empty Field API fields on presave in HEAD ? + // At any rate, shouldn't be needed when validation happens on programmatic + // saves too. + $this->filterEmpty(); + + // Defer to the item class method. + foreach ($this->list as $item) { + $item->validate(); + } + + // @todo Adapt what's below... + + // 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 = $field->cardinality; + if ($cardinality != FIELD_CARDINALITY_UNLIMITED && count($items) > $cardinality) { + $errors[$field->id][$langcode][0][] = array( + 'error' => 'field_cardinality', + 'message' => t('%name: this field cannot hold more than @count values.', array('%name' => $instance->label, '@count' => $cardinality)), + ); + } + } + + /** + * {@inheritdoc} + */ + public function prepareTranslation(EntityInterface $source_entity, $source_langcode) { + $field = $this->field; + + // @todo Adapt... + + // If the field is untranslatable keep using LANGUAGE_NOT_SPECIFIED. + if ($langcode == LANGUAGE_NOT_SPECIFIED) { + $source_langcode = LANGUAGE_NOT_SPECIFIED; + } + if (isset($source_entity->{$field->id}[$source_langcode])) { + $items = $source_entity->{$field->id}[$source_langcode]; + } + } + +} diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/CFieldItemBase.php b/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/CFieldItemBase.php new file mode 100644 index 0000000..183b71c --- /dev/null +++ b/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/CFieldItemBase.php @@ -0,0 +1,59 @@ +getParent(); + $instances = FieldAPI::fieldInfo()->getBundleInstances($entity->entityType(), $entity->bundle()); + $this->instance = $instances[$parent->name]; + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, array &$form_state, $has_data) { + return array(); + } + + /** + * {@inheritdoc} + */ + public function instanceSettingsForm(array $form, array &$form_state) { + return array(); + } + +} diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/CFieldItemInterface.php b/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/CFieldItemInterface.php new file mode 100644 index 0000000..73338d0 --- /dev/null +++ b/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/CFieldItemInterface.php @@ -0,0 +1,187 @@ +decorated = $decorated; + } + + /** + * {@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(); + + $legacy_discovery = new HookDiscovery('field_info'); + foreach ($legacy_discovery->getDefinitions() as $plugin_id => $definition) { + $this->processDefinition($definition); + + $definition['id'] = $plugin_id; + $definitions[$plugin_id] = $definition; + } + return $definitions; + } + + /** + * {@inheritdoc} + */ + public function processDefinition(array &$definition) { + $definition['class'] = $definition['field item class']; + unset($definition['field item class']); + $definition['list_class'] = 'Drupal\field\Plugin\field\field_type\LegacyField'; + } + +} diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/FieldTypePluginManager.php b/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/FieldTypePluginManager.php new file mode 100644 index 0000000..8fd1da3 --- /dev/null +++ b/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/FieldTypePluginManager.php @@ -0,0 +1,53 @@ + array(), + 'instance_settings' => array(), + 'list_class' => '\Drupal\field\Plugin\Type\FieldType\CField', + ); + + /** + * {@inheritdoc} + */ + public function __construct(\Traversable $namespaces) { + $this->discovery = new AnnotatedClassDiscovery('field/field_type', $namespaces); + $this->discovery = new FieldTypeLegacyDiscoveryDecorator($this->discovery); + $this->discovery = new ProcessDecorator($this->discovery, array($this, 'processDefinition')); + $this->discovery = new AlterDecorator($this->discovery, 'field_info'); + $this->discovery = new CacheDecorator($this->discovery, 'field_types', 'field'); + + $this->factory = new ReflectionFactory($this); + } + +} diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetBase.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetBase.php index 64c83a2..ace64f2 100644 --- a/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetBase.php +++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetBase.php @@ -342,7 +342,10 @@ public function extractFormValues(EntityInterface $entity, $langcode, array &$it $this->sortItems($items); // Remove empty values. - $items = _field_filter_items($this->field, $items); + // @todo It should be the definition of items based on $this->field & $this->instance. + $itemsNG = \Drupal::typedData()->getPropertyInstance($entity, $field_name, $items); + $itemsNG->filterEmpty(); + $items = $itemsNG->getValue(); // 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); diff --git a/core/modules/field/lib/Drupal/field/Plugin/field/field_type/LegacyField.php b/core/modules/field/lib/Drupal/field/Plugin/field/field_type/LegacyField.php new file mode 100644 index 0000000..873a187 --- /dev/null +++ b/core/modules/field/lib/Drupal/field/Plugin/field/field_type/LegacyField.php @@ -0,0 +1,135 @@ +filterEmpty(); + + $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 nam of the hook, e.g. 'presave', 'validate'. + */ + protected function legacyCallback($hook) { + $module = $this->pluginDefinition['module']; + $callback = "{$module}_field_{$hook}"; + if (function_exists($callback)) { + $entity = $this->getParent(); + // @todo Is that correct ? + $langcode = $entity->langcode->value; + + // Legcacy callbacks alter $items by reference. + $items = $this->getValue(); + $callback($entity, $this->instance->getField(), $this->instance, $langcode, $items); + $this->setValue($items); + } + } + + + // @todo - what's below is not working nor actually invoked. + + + /** + * {@inheritdoc} + */ + public function load(array $entities, array $instances, $langcode, array &$items, $age) { + parent::load($entities, $instances, $langcode, $items, $age); + if ($entities && $callback = $this->legacyCallback('load')) { + $entity = current($entities); + $callback($entity->entityType(), $entities, $this->field, $instances, $langcode, $items, $age); + } + } + + /** + * {@inheritdoc} + */ + public function prepareView(array $entities, array $instances, $langcode, array &$items) { + parent::prepareView($entities, $instances, $langcode, $items); + if ($entities && $callback = $this->legacyCallback('prepare_view')) { + $entity = current($entities); + $callback($entity->entityType(), $entities, $this->field, $instances, $langcode, $items); + } + } + + /** + * {@inheritdoc} + */ + public function TODOvalidate(array &$errors) { + parent::validate(); + if ($callback = $this->legacyCallback('validate')) { + $callback($entity->entityType(), $entity, $this->field, $instance, $langcode, $items, $errors); + } + } + + /** + * {@inheritdoc} + */ + public function prepareTranslation(EntityInterface $source_entity, $source_langcode) { + parent::prepareTranslation($source_entity, $source_langcode); + if ($callback = $this->legacyCallback('prepare_translation')) { + $callback($entity->entityType(), $entity, $this->field, $instance, $langcode, $items, $source_entity, $source_langcode); + } + } + +} diff --git a/core/modules/field/lib/Drupal/field/Plugin/field/field_type/LegacyFieldItem.php b/core/modules/field/lib/Drupal/field/Plugin/field/field_type/LegacyFieldItem.php new file mode 100644 index 0000000..99f12de --- /dev/null +++ b/core/modules/field/lib/Drupal/field/Plugin/field/field_type/LegacyFieldItem.php @@ -0,0 +1,88 @@ +getDefinition('field_type:' . $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'); + // The previous hook was never called on an empty item, but EntityNG always + // creates a FieldItem element for an empty field. + $item = $this->getValue(); + return empty($item) || $callback($item, $this->instance->getField()); + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, array &$form_state, $has_data) { + 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->instance->getField(), $this->instance, $has_data); + } + } + + /** + * {@inheritdoc} + */ + public function instanceSettingsForm(array $form, array &$form_state) { + if ($callback = $this->getLegacyCallback('instance_settings_form')) { + return $callback($this->instance->getField(), $this->instance, $form_state); + } + } + + /** + * 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) { + $module = $this->pluginDefinition['module']; + $callback = "{$module}_field_{$hook}"; + if (function_exists($callback)) { + return $callback; + } + } + +} diff --git a/core/modules/field/tests/modules/field_test/field_test.field.inc b/core/modules/field/tests/modules/field_test/field_test.field.inc index d2f4a94..5dee6af 100644 --- a/core/modules/field/tests/modules/field_test/field_test.field.inc +++ b/core/modules/field/tests/modules/field_test/field_test.field.inc @@ -48,7 +48,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', + 'field item class' => 'Drupal\field_test\Type\HiddenTestItem', ), ); } diff --git a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Type/HiddenTestItem.php b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Type/HiddenTestItem.php new file mode 100644 index 0000000..7a9dd30 --- /dev/null +++ b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Type/HiddenTestItem.php @@ -0,0 +1,38 @@ + 'integer', + 'label' => t('Test integer value'), + ); + } + return static::$propertyDefinitions; + } + +} diff --git a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Type/ShapeItem.php b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Type/ShapeItem.php index 1a67329..a91fd90 100644 --- a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Type/ShapeItem.php +++ b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Type/ShapeItem.php @@ -7,12 +7,12 @@ namespace Drupal\field_test\Type; -use Drupal\Core\Entity\Field\FieldItemBase; +use Drupal\field\Plugin\field\field_type\LegacyFieldItem; /** * Defines the 'shape_field' entity field item. */ -class ShapeItem extends FieldItemBase { +class ShapeItem extends LegacyFieldItem { /** * Property definitions of the contained properties. diff --git a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Type/TestItem.php b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Type/TestItem.php index 0c61d15..1d39b09 100644 --- a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Type/TestItem.php +++ b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Type/TestItem.php @@ -7,12 +7,12 @@ namespace Drupal\field_test\Type; -use Drupal\Core\Entity\Field\FieldItemBase; +use Drupal\field\Plugin\field\field_type\LegacyFieldItem; /** * Defines the 'test_field' entity field item. */ -class TestItem extends FieldItemBase { +class TestItem extends LegacyFieldItem { /** * Property definitions of the contained properties. diff --git a/core/modules/field_ui/field_ui.api.php b/core/modules/field_ui/field_ui.api.php index ff25ad5..03ca5aa 100644 --- a/core/modules/field_ui/field_ui.api.php +++ b/core/modules/field_ui/field_ui.api.php @@ -11,88 +11,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. - * @param $has_data - * TRUE if the field already has data, FALSE if not. - * - * @return - * The form definition for the field settings. - */ -function hook_field_settings_form($field, $instance, $has_data) { - $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; -} - -/** * Alter the formatter settings form. * * @param $element diff --git a/core/modules/field_ui/lib/Drupal/field_ui/Form/FieldEditForm.php b/core/modules/field_ui/lib/Drupal/field_ui/Form/FieldEditForm.php index 8e8d890..968d86f 100644 --- a/core/modules/field_ui/lib/Drupal/field_ui/Form/FieldEditForm.php +++ b/core/modules/field_ui/lib/Drupal/field_ui/Form/FieldEditForm.php @@ -102,10 +102,15 @@ public function buildForm(array $form, array &$form_state, FieldInstance $field_ $form['field']['settings'] = array( '#weight' => 10, ); - $additions = \Drupal::moduleHandler()->invoke($field['module'], 'field_settings_form', array($field, $this->instance, $has_data)); - if (is_array($additions)) { - $form['field']['settings'] += $additions; - } + // Create an arbitrary entity object, so that we can have an instanciated + // FieldItem. + $ids = (object) array('entity_type' => $this->instance['entity_type'], 'bundle' => $this->instance['bundle'], 'entity_id' => NULL); + // @todo If we wanted to allow presenting a field edit form before having + // fully created Field and FieldIntance objects, we would need to inject + // them in the FieldItem object we manipulate here. + $entity = _field_create_entity_from_ids($ids); + $item = $entity->get($field['field_name'])->offsetGet(0); + $form['field']['settings'] += $item->settingsForm($form, $form_state, $has_data); $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save field settings')); diff --git a/core/modules/field_ui/lib/Drupal/field_ui/Form/FieldInstanceEditForm.php b/core/modules/field_ui/lib/Drupal/field_ui/Form/FieldInstanceEditForm.php index 4803701..493d336 100644 --- a/core/modules/field_ui/lib/Drupal/field_ui/Form/FieldInstanceEditForm.php +++ b/core/modules/field_ui/lib/Drupal/field_ui/Form/FieldInstanceEditForm.php @@ -143,12 +143,13 @@ public function buildForm(array $form, array &$form_state, FieldInstance $field_ '#value' => $this->instance['widget']['type'], ); - // 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. + // @todo If we wanted to allow presenting a field edit form before having + // fully created Field and FieldIntance objects, we would need to inject + // them in the FieldItem object we manipulate here. + $item = $form['#entity']->get($field['field_name'])->offsetGet(0); + $form['instance']['settings'] = $item->instanceSettingsForm($form, $form_state);; + $form['instance']['settings']['#weight'] = 10; // Add widget settings for the widget type. $additions = $this->instance->getWidget()->settingsForm($form, $form_state); diff --git a/core/modules/file/lib/Drupal/file/Type/FileItem.php b/core/modules/file/lib/Drupal/file/Type/FileItem.php index 0b2c74b..65ed2f8 100644 --- a/core/modules/file/lib/Drupal/file/Type/FileItem.php +++ b/core/modules/file/lib/Drupal/file/Type/FileItem.php @@ -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\LegacyFieldItem; /** * Defines the 'file_field' entity field item. */ -class FileItem extends FieldItemBase { +class FileItem extends LegacyFieldItem { /** * Property definitions of the contained properties. diff --git a/core/modules/image/lib/Drupal/image/Type/ImageItem.php b/core/modules/image/lib/Drupal/image/Type/ImageItem.php index f6cedf2..3ffa568 100644 --- a/core/modules/image/lib/Drupal/image/Type/ImageItem.php +++ b/core/modules/image/lib/Drupal/image/Type/ImageItem.php @@ -7,12 +7,12 @@ namespace Drupal\image\Type; -use Drupal\Core\Entity\Field\FieldItemBase; +use Drupal\field\Plugin\field\field_type\LegacyFieldItem; /** * Defines the 'image_field' entity field item. */ -class ImageItem extends FieldItemBase { +class ImageItem extends LegacyFieldItem { /** * Property definitions of the contained properties. diff --git a/core/modules/link/lib/Drupal/link/Type/LinkItem.php b/core/modules/link/lib/Drupal/link/Type/LinkItem.php index ad20400..8a66d22 100644 --- a/core/modules/link/lib/Drupal/link/Type/LinkItem.php +++ b/core/modules/link/lib/Drupal/link/Type/LinkItem.php @@ -7,12 +7,12 @@ namespace Drupal\link\Type; -use Drupal\Core\Entity\Field\FieldItemBase; +use Drupal\field\Plugin\field\field_type\LegacyFieldItem; /** * Defines the 'link_field' entity field item. */ -class LinkItem extends FieldItemBase { +class LinkItem extends LegacyFieldItem { /** * Property definitions of the contained properties. diff --git a/core/modules/locale/lib/Drupal/locale/LocaleTypedConfig.php b/core/modules/locale/lib/Drupal/locale/LocaleTypedConfig.php index 5d6b200..481cb94 100644 --- a/core/modules/locale/lib/Drupal/locale/LocaleTypedConfig.php +++ b/core/modules/locale/lib/Drupal/locale/LocaleTypedConfig.php @@ -50,6 +50,7 @@ class LocaleTypedConfig extends Element { * @param \Drupal\locale\LocaleConfigManager $localeConfig; * The locale configuration manager object. */ + // @todo Figure out signature. public function __construct(array $definition, $name, $langcode, \Drupal\locale\LocaleConfigManager $localeConfig) { parent::__construct($definition, $name); $this->langcode = $langcode; diff --git a/core/modules/node/lib/Drupal/node/NodeStorageController.php b/core/modules/node/lib/Drupal/node/NodeStorageController.php index c9e832e..727189e 100644 --- a/core/modules/node/lib/Drupal/node/NodeStorageController.php +++ b/core/modules/node/lib/Drupal/node/NodeStorageController.php @@ -98,6 +98,8 @@ protected function buildQuery($ids, $revision_id = FALSE) { * Overrides Drupal\Core\Entity\DatabaseStorageController::invokeHook(). */ protected function invokeHook($hook, EntityInterface $node) { + $this->configFieldOp($hook, $node); + $node = $node->getBCEntity(); if ($hook == 'insert' || $hook == 'update') { diff --git a/core/modules/number/lib/Drupal/number/Type/DecimalItem.php b/core/modules/number/lib/Drupal/number/Type/DecimalItem.php index 5235f21..fa5ac4f 100644 --- a/core/modules/number/lib/Drupal/number/Type/DecimalItem.php +++ b/core/modules/number/lib/Drupal/number/Type/DecimalItem.php @@ -7,12 +7,12 @@ namespace Drupal\number\Type; -use Drupal\Core\Entity\Field\FieldItemBase; +use Drupal\field\Plugin\field\field_type\LegacyFieldItem; /** * Defines the 'number_decimal_field' entity field item. */ -class DecimalItem extends FieldItemBase { +class DecimalItem extends LegacyFieldItem { /** * Definitions of the contained properties. diff --git a/core/modules/number/lib/Drupal/number/Type/FloatItem.php b/core/modules/number/lib/Drupal/number/Type/FloatItem.php index 8f8fddd..0673873 100644 --- a/core/modules/number/lib/Drupal/number/Type/FloatItem.php +++ b/core/modules/number/lib/Drupal/number/Type/FloatItem.php @@ -7,12 +7,12 @@ namespace Drupal\number\Type; -use Drupal\Core\Entity\Field\FieldItemBase; +use Drupal\field\Plugin\field\field_type\LegacyFieldItem; /** * Defines the 'number_float_field' entity field item. */ -class FloatItem extends FieldItemBase { +class FloatItem extends LegacyFieldItem { /** * Definitions of the contained properties. diff --git a/core/modules/number/lib/Drupal/number/Type/IntegerItem.php b/core/modules/number/lib/Drupal/number/Type/IntegerItem.php index 6b126f1..9b22687 100644 --- a/core/modules/number/lib/Drupal/number/Type/IntegerItem.php +++ b/core/modules/number/lib/Drupal/number/Type/IntegerItem.php @@ -7,12 +7,12 @@ namespace Drupal\number\Type; -use Drupal\Core\Entity\Field\FieldItemBase; +use Drupal\field\Plugin\field\field_type\LegacyFieldItem; /** * Defines the 'number_integer_field' entity field item. */ -class IntegerItem extends FieldItemBase { +class IntegerItem extends LegacyFieldItem { /** * Definitions of the contained properties. diff --git a/core/modules/options/lib/Drupal/options/Type/ListBooleanItem.php b/core/modules/options/lib/Drupal/options/Type/ListBooleanItem.php new file mode 100644 index 0000000..109a1c7 --- /dev/null +++ b/core/modules/options/lib/Drupal/options/Type/ListBooleanItem.php @@ -0,0 +1,13 @@ + 'float', + 'label' => t('Float value'), + ); + } + return static::$propertyDefinitions; + } +} diff --git a/core/modules/options/lib/Drupal/options/Type/ListIntegerItem.php b/core/modules/options/lib/Drupal/options/Type/ListIntegerItem.php new file mode 100644 index 0000000..553806c --- /dev/null +++ b/core/modules/options/lib/Drupal/options/Type/ListIntegerItem.php @@ -0,0 +1,39 @@ + 'integer', + 'label' => t('Integer value'), + ); + } + return static::$propertyDefinitions; + } +} diff --git a/core/modules/options/lib/Drupal/options/Type/ListTextItem.php b/core/modules/options/lib/Drupal/options/Type/ListTextItem.php new file mode 100644 index 0000000..f09a4a7 --- /dev/null +++ b/core/modules/options/lib/Drupal/options/Type/ListTextItem.php @@ -0,0 +1,40 @@ + 'string', + 'label' => t('Text value'), + ); + } + return static::$propertyDefinitions; + } + +} diff --git a/core/modules/options/options.module b/core/modules/options/options.module index 0d8ec00..1373f12 100644 --- a/core/modules/options/options.module +++ b/core/modules/options/options.module @@ -34,7 +34,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', + 'field item class' => '\Drupal\options\Type\ListIntegerItem', ), 'list_float' => array( 'label' => t('List (float)'), @@ -42,7 +42,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', + 'field item class' => '\Drupal\options\Type\ListFloatItem', ), 'list_text' => array( 'label' => t('List (text)'), @@ -50,7 +50,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', + 'field item class' => '\Drupal\options\Type\ListTextItem', ), 'list_boolean' => array( 'label' => t('Boolean'), @@ -58,7 +58,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', + 'field item class' => '\Drupal\options\Type\ListBooleanItem', ), ); } diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Type/TaxonomyTermReferenceItem.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Type/TaxonomyTermReferenceItem.php index b558d97..488deaa 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/Type/TaxonomyTermReferenceItem.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Type/TaxonomyTermReferenceItem.php @@ -7,13 +7,12 @@ namespace Drupal\taxonomy\Type; -use Drupal\Core\Entity\Field\FieldItemBase; -use Drupal\Core\TypedData\TypedDataInterface; +use Drupal\field\Plugin\field\field_type\LegacyFieldItem; /** * Defines the 'taxonomy_term_reference' entity field item. */ -class TaxonomyTermReferenceItem extends FieldItemBase { +class TaxonomyTermReferenceItem extends LegacyFieldItem { /** * Property definitions of the contained properties. diff --git a/core/modules/telephone/lib/Drupal/telephone/Type/TelephoneItem.php b/core/modules/telephone/lib/Drupal/telephone/Type/TelephoneItem.php index 195e4a5..701ed6a 100644 --- a/core/modules/telephone/lib/Drupal/telephone/Type/TelephoneItem.php +++ b/core/modules/telephone/lib/Drupal/telephone/Type/TelephoneItem.php @@ -7,12 +7,12 @@ namespace Drupal\telephone\Type; -use Drupal\Core\Entity\Field\FieldItemBase; +use Drupal\field\Plugin\field\field_type\LegacyFieldItem; /** * Defines the 'telephone_field' entity field items. */ -class TelephoneItem extends FieldItemBase { +class TelephoneItem extends LegacyFieldItem { /** * Definitions of the contained properties. diff --git a/core/modules/text/lib/Drupal/text/Plugin/field/field_type/CTextItem.php b/core/modules/text/lib/Drupal/text/Plugin/field/field_type/CTextItem.php new file mode 100644 index 0000000..acc8bde --- /dev/null +++ b/core/modules/text/lib/Drupal/text/Plugin/field/field_type/CTextItem.php @@ -0,0 +1,103 @@ + array( + 'value' => array( + 'type' => 'varchar', + 'length' => $field->settings['max_length'], + 'not null' => FALSE, + ), + 'format' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + ), + ), + 'indexes' => array( + 'format' => array('format'), + ), + 'foreign keys' => array( + 'format' => array( + 'table' => 'filter_format', + 'columns' => array('format' => 'format'), + ), + ), + ); + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, array &$form_state, $has_data) { + $element = array(); + + $element['max_length'] = array( + '#type' => 'number', + '#title' => t('Maximum length'), + '#default_value' => $this->instance->getField()->settings['max_length'], + '#required' => TRUE, + '#description' => t('The maximum length of the field in characters.'), + '#min' => 1, + // @todo: If $has_data, add a validate handler that only allows + // max_length to increase. + '#disabled' => $has_data, + ); + + return $element; + } + + /** + * {@inheritdoc} + */ + public function instanceSettingsForm(array $form, array &$form_state) { + $element = array(); + + $element['text_processing'] = array( + '#type' => 'radios', + '#title' => t('Text processing'), + '#default_value' => $this->instance->settings['text_processing'], + '#options' => array( + t('Plain text'), + t('Filtered text (user selects text format)'), + ), + ); + + return $element; + } + +} diff --git a/core/modules/text/lib/Drupal/text/Plugin/field/field_type/CTextItemBase.php b/core/modules/text/lib/Drupal/text/Plugin/field/field_type/CTextItemBase.php new file mode 100644 index 0000000..7184a92 --- /dev/null +++ b/core/modules/text/lib/Drupal/text/Plugin/field/field_type/CTextItemBase.php @@ -0,0 +1,176 @@ + 'string', + 'label' => t('Text value'), + ); + static::$propertyDefinitions['format'] = array( + 'type' => 'string', + 'label' => t('Text format'), + ); + static::$propertyDefinitions['processed'] = array( + 'type' => 'string', + 'label' => t('Processed text'), + 'description' => t('The text value with the text format applied.'), + 'computed' => TRUE, + 'class' => '\Drupal\text\TextProcessed', + 'settings' => array( + 'text source' => 'value', + ), + ); + } + return static::$propertyDefinitions; + } + + /** + * {@inheritdoc} + */ + public function isEmpty() { + $value = $this->properties['value']->getValue(); + return (empty($value) && $value !== '0'); + } + + /** + * {@inheritdoc} + * + * @todo Just testing - remove. + */ + public function presave() { +// $this->properties['value']->setValue($this->properties['value']->getValue() . 'a'); + } + + + + // @todo... + + /** + * Overrides \Drupal\field\Plugin\Type\FieldType\CFieldItemBase::load(). + * + * Where possible, the function generates the sanitized version of each field + * early so that it is cached in the field cache. This avoids the need to + * look up the field in the filter cache separately. + */ + public function load(array $entities, array $instances, $langcode, array &$items, $age) { + parent::load($entities, $instances, $langcode, $items, $age); + + 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. + $instance = $instances[$id]; + if (!$instance->settings['text_processing'] || filter_format_allowcache($item['format'])) { + $item['safe_value'] = isset($item['value']) ? _text_sanitize($this->instance['settings']['text_processing'], $langcode, $item, 'value') : ''; + if ($this->getPluginId() == 'text_with_summary') { + $item['safe_summary'] = isset($item['summary']) ? _text_sanitize($this->instance['settings']['text_processing'], $langcode, $item, 'summary') : ''; + } + } + } + } + } + + /** + * Overrides \Drupal\field\Plugin\Type\FieldType\CFieldItemBase::validate(). + * + * Possible error codes: + * - text_value_max_length: The value exceeds the maximum length. + * - text_summary_max_length: The summary exceeds the maximum length. + */ + public function TODOvalidate(array &$errors) { + parent::validate($entity, $instance, $langcode, $items, $errors); + + $max_length = $this->field->settings['max_length']; + + foreach ($items as $delta => $item) { + // Just to quickly test validation errors. + // @todo remove + if ($item['value'] == 'aa') { + $errors[$this->field->id][$langcode][$delta][] = array( + 'error' => "text_dummy_error", + 'message' => 'aa is invalid, dude', + ); + } + + // @todo Length is counted separately for summary and value, so the maximum + // length can be exceeded very easily. + foreach (array('value', 'summary') as $column) { + if (!empty($item[$column])) { + if ($max_length && drupal_strlen($item[$column]) > $max_length) { + switch ($column) { + case 'value': + $message = t('%name: the text may not be longer than %max characters.', array('%name' => $instance->label, '%max' => $max_length)); + break; + + case 'summary': + $message = t('%name: the summary may not be longer than %max characters.', array('%name' => $instance->label, '%max' => $max_length)); + break; + } + $errors[$this->field->id][$langcode][$delta][] = array( + 'error' => "text_{$column}_length", + 'message' => $message, + ); + } + } + } + } + } + + /** + * {@inheritdoc} + */ + public function prepareTranslation(EntityInterface $source_entity, $source_langcode) { + parent::prepareTranslation($entity, $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. + if (!empty($source_entity->{$this->field->id}[$source_langcode])) { + $formats = filter_formats(); + foreach ($source_entity->{$this->field->id}[$source_langcode] as $delta => $item) { + $format_id = $item['format']; + if (!empty($format_id) && !filter_access($formats[$format_id])) { + unset($items[$delta]); + } + } + } + } + + /** + * {@inheritdoc} + * + * @todo Just for testing - remove... + */ + public function prepareView(array $entities, array $instances, $langcode, array &$items) { + foreach ($entities as $id => $entity) { + foreach ($items[$id] as $delta => $item) { +// $items[$id][$delta]['safe_value'] = $delta . $items[$id][$delta]['safe_value']; + } + } + } + +} diff --git a/core/modules/text/lib/Drupal/text/Plugin/field/field_type/CTextLongItem.php b/core/modules/text/lib/Drupal/text/Plugin/field/field_type/CTextLongItem.php new file mode 100644 index 0000000..9c86b18 --- /dev/null +++ b/core/modules/text/lib/Drupal/text/Plugin/field/field_type/CTextLongItem.php @@ -0,0 +1,79 @@ + array( + 'value' => array( + 'type' => 'text', + 'size' => 'big', + 'not null' => FALSE, + ), + 'format' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + ), + ), + 'indexes' => array( + 'format' => array('format'), + ), + 'foreign keys' => array( + 'format' => array( + 'table' => 'filter_format', + 'columns' => array('format' => 'format'), + ), + ), + ); + } + + /** + * {@inheritdoc} + */ + public function instanceSettingsForm(array $form, array &$form_state) { + $element = array(); + + $element['text_processing'] = array( + '#type' => 'radios', + '#title' => t('Text processing'), + '#default_value' => $this->instance->settings['text_processing'], + '#options' => array( + t('Plain text'), + t('Filtered text (user selects text format)'), + ), + ); + + return $element; + } + +} diff --git a/core/modules/text/lib/Drupal/text/Plugin/field/field_type/CTextWithSummaryItem.php b/core/modules/text/lib/Drupal/text/Plugin/field/field_type/CTextWithSummaryItem.php new file mode 100644 index 0000000..c5275de --- /dev/null +++ b/core/modules/text/lib/Drupal/text/Plugin/field/field_type/CTextWithSummaryItem.php @@ -0,0 +1,132 @@ + 'string', + 'label' => t('Summary text value'), + ); + static::$propertyDefinitions['summary_processed'] = array( + 'type' => 'string', + 'label' => t('Processed summary text'), + 'description' => t('The summary text value with the text format applied.'), + 'computed' => TRUE, + 'class' => '\Drupal\text\TextProcessed', + 'settings' => array( + 'text source' => 'summary', + ), + ); + } + return static::$propertyDefinitions; + } + + /** + * {@inheritdoc} + */ + public static function schema(Field $field) { + return array( + 'columns' => array( + 'value' => array( + 'type' => 'text', + 'size' => 'big', + 'not null' => FALSE, + ), + 'summary' => array( + 'type' => 'text', + 'size' => 'big', + 'not null' => FALSE, + ), + 'format' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + ), + ), + 'indexes' => array( + 'format' => array('format'), + ), + 'foreign keys' => array( + 'format' => array( + 'table' => 'filter_format', + 'columns' => array('format' => 'format'), + ), + ), + ); + } + + /** + * {@inheritdoc} + */ + public function isEmpty() { + $value = $this->properties['value']->getValue(); + $summary = $this->properties['summary']->getValue(); + return (empty($value) && $value !== '0' && empty($summary) && $summary !== '0'); + } + + /** + * {@inheritdoc} + */ + public function instanceSettingsForm(array $form, array &$form_state) { + $element = array(); + + $element['text_processing'] = array( + '#type' => 'radios', + '#title' => t('Text processing'), + '#default_value' => $this->instance->settings['text_processing'], + '#options' => array( + t('Plain text'), + t('Filtered text (user selects text format)'), + ), + ); + $element['display_summary'] = array( + '#type' => 'checkbox', + '#title' => t('Summary input'), + '#default_value' => $this->instance->settings['display_summary'], + '#description' => t('This allows authors to input an explicit summary, to be displayed instead of the automatically trimmed text when using the "Summary or trimmed" display type.'), + ); + + return $element; + } + +} diff --git a/core/modules/text/lib/Drupal/text/TextProcessed.php b/core/modules/text/lib/Drupal/text/TextProcessed.php index 3a44722..02e5883 100644 --- a/core/modules/text/lib/Drupal/text/TextProcessed.php +++ b/core/modules/text/lib/Drupal/text/TextProcessed.php @@ -37,8 +37,8 @@ class TextProcessed extends TypedData { /** * Overrides TypedData::__construct(). */ - public function __construct(array $definition, $name = NULL, TypedDataInterface $parent = NULL) { - parent::__construct($definition, $name, $parent); + public function __construct(array $definition, $plugin_id, array $plugin_definition, $name = NULL, TypedDataInterface $parent = NULL) { + parent::__construct($definition, $plugin_id, $plugin_definition, $name, $parent); if (!isset($definition['settings']['text source'])) { throw new InvalidArgumentException("The definition's 'source' key has to specify the name of the text property to be processed."); diff --git a/core/modules/text/lib/Drupal/text/Type/TextItem.php b/core/modules/text/lib/Drupal/text/Type/TextItem.php deleted file mode 100644 index d7aefc7..0000000 --- a/core/modules/text/lib/Drupal/text/Type/TextItem.php +++ /dev/null @@ -1,53 +0,0 @@ - 'string', - 'label' => t('Text value'), - ); - static::$propertyDefinitions['format'] = array( - 'type' => 'string', - 'label' => t('Text format'), - ); - static::$propertyDefinitions['processed'] = array( - 'type' => 'string', - 'label' => t('Processed text'), - 'description' => t('The text value with the text format applied.'), - 'computed' => TRUE, - 'class' => '\Drupal\text\TextProcessed', - 'settings' => array( - 'text source' => 'value', - ), - ); - } - return static::$propertyDefinitions; - } -} diff --git a/core/modules/text/lib/Drupal/text/Type/TextSummaryItem.php b/core/modules/text/lib/Drupal/text/Type/TextSummaryItem.php deleted file mode 100644 index 90dc8b9..0000000 --- a/core/modules/text/lib/Drupal/text/Type/TextSummaryItem.php +++ /dev/null @@ -1,50 +0,0 @@ - 'string', - 'label' => t('Summary text value'), - ); - static::$propertyDefinitions['summary_processed'] = array( - 'type' => 'string', - 'label' => t('Processed summary text'), - 'description' => t('The summary text value with the text format applied.'), - 'computed' => TRUE, - 'class' => '\Drupal\text\TextProcessed', - 'settings' => array( - 'text source' => 'summary', - ), - ); - } - return static::$propertyDefinitions; - } -} diff --git a/core/modules/text/text.module b/core/modules/text/text.module index 4bf51b6..027d796 100644 --- a/core/modules/text/text.module +++ b/core/modules/text/text.module @@ -41,164 +41,6 @@ function text_help($path, $arg) { } /** - * Implements hook_field_info(). - * - * Field settings: - * - max_length: The maximum length for a varchar field. - * Instance settings: - * - text_processing: Whether text input filters should be used. - * - display_summary: Whether the summary field should be displayed. When - * empty and not displayed the summary will take its value from the trimmed - * value of the main text field. - */ -function text_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', - 'field item class' => '\Drupal\text\Type\TextItem', - ), - 'text_long' => array( - 'label' => t('Long text'), - 'description' => t('This field stores long text in the database.'), - 'instance_settings' => array('text_processing' => 0), - 'default_widget' => 'text_textarea', - 'default_formatter' => 'text_default', - 'field item class' => '\Drupal\text\Type\TextItem', - ), - 'text_with_summary' => array( - 'label' => t('Long text and summary'), - 'description' => t('This field stores long text in the database along with optional summary text.'), - 'instance_settings' => array('text_processing' => 1, 'display_summary' => 0), - 'default_widget' => 'text_textarea_with_summary', - 'default_formatter' => 'text_default', - 'field item class' => '\Drupal\text\Type\TextSummaryItem', - ), - ); -} - -/** - * Implements hook_field_settings_form(). - */ -function text_field_settings_form($field, $instance, $has_data) { - $settings = $field['settings']; - - $form = array(); - - if ($field['type'] == 'text') { - $form['max_length'] = array( - '#type' => 'number', - '#title' => t('Maximum length'), - '#default_value' => $settings['max_length'], - '#required' => TRUE, - '#description' => t('The maximum length of the field in characters.'), - '#min' => 1, - // @todo: If $has_data, add a validate handler that only allows - // max_length to increase. - '#disabled' => $has_data, - ); - } - - return $form; -} - -/** - * Implements hook_field_instance_settings_form(). - */ -function text_field_instance_settings_form($field, $instance) { - $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' => 'checkbox', - '#title' => t('Summary input'), - '#default_value' => $settings['display_summary'], - '#description' => t('This allows authors to input an explicit summary, to be displayed instead of the automatically trimmed text when using the "Summary or trimmed" display type.'), - ); - } - - return $form; -} - -/** - * Implements hook_field_validate(). - * - * Possible error codes: - * - text_value_max_length: The value exceeds the maximum length. - * - text_summary_max_length: The summary exceeds the maximum length. - */ -function text_field_validate(EntityInterface $entity = NULL, $field, $instance, $langcode, $items, &$errors) { - foreach ($items as $delta => $item) { - // @todo Length is counted separately for summary and value, so the maximum - // length can be exceeded very easily. - foreach (array('value', 'summary') as $column) { - if (!empty($item[$column])) { - if (!empty($field['settings']['max_length']) && drupal_strlen($item[$column]) > $field['settings']['max_length']) { - switch ($column) { - case 'value': - $message = t('%name: the text may not be longer than %max characters.', array('%name' => $instance['label'], '%max' => $field['settings']['max_length'])); - break; - - case 'summary': - $message = t('%name: the summary may not be longer than %max characters.', array('%name' => $instance['label'], '%max' => $field['settings']['max_length'])); - break; - } - $errors[$field['field_name']][$langcode][$delta][] = array( - 'error' => "text_{$column}_length", - 'message' => $message, - ); - } - } - } - } -} - -/** - * Implements hook_field_load(). - * - * Where possible, the function generates the sanitized version of each field - * early so that it is cached in the field cache. This avoids the need to look - * up the field in the filter cache separately. - */ -function text_field_load($entity_type, $entities, $field, $instances, $langcode, &$items) { - 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') : ''; - } - } - } - } -} - -/** - * Implements hook_field_is_empty(). - */ -function text_field_is_empty($item, $field) { - if (!isset($item['value']) || $item['value'] === '') { - return !isset($item['summary']) || $item['summary'] === ''; - } - return FALSE; -} - -/** * Sanitizes the 'value' or 'summary' data of a text value. * * Depending on whether the field instance uses text processing, data is run @@ -352,24 +194,6 @@ function text_summary($text, $format = NULL, $size = NULL) { } /** - * Implements hook_field_prepare_translation(). - */ -function text_field_prepare_translation(EntityInterface $entity, $field, $instance, $langcode, &$items, EntityInterface $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']; - if (!empty($source_entity->{$field_name}[$source_langcode])) { - $formats = filter_formats(); - foreach ($source_entity->{$field_name}[$source_langcode] as $delta => $item) { - $format_id = $item['format']; - if (!empty($format_id) && !filter_access($formats[$format_id])) { - unset($items[$delta]); - } - } - } -} - -/** * Implements hook_filter_format_update(). */ function text_filter_format_update($format) { diff --git a/core/modules/translation_entity/translation_entity.admin.inc b/core/modules/translation_entity/translation_entity.admin.inc index 8c184b5..dbea827 100644 --- a/core/modules/translation_entity/translation_entity.admin.inc +++ b/core/modules/translation_entity/translation_entity.admin.inc @@ -586,20 +586,21 @@ function translation_entity_translatable_batch($translatable, $field_name, &$con */ function _translation_entity_update_field($entity_type, EntityInterface $entity, $field_name) { $empty = 0; - $field = field_info_field($field_name); // Ensure that we are trying to store only valid data. foreach ($entity->{$field_name} as $langcode => $items) { - $entity->{$field_name}[$langcode] = _field_filter_items($field, $entity->{$field_name}[$langcode]); - $empty += empty($entity->{$field_name}[$langcode]); + // @todo Double check this wrt NG logic / syntax. + $items->filterEmpty(); + $empty += empty($items); } // Save the field value only if there is at least one item available, // otherwise any stored empty field value would be deleted. If this happens // the range queries would be messed up. if ($empty < count($entity->{$field_name})) { - field_attach_presave($entity); - field_attach_update($entity); + // @todo replace that... +// field_attach_presave($entity); +// field_attach_update($entity); } } diff --git a/core/modules/translation_entity/translation_entity.module b/core/modules/translation_entity/translation_entity.module index 82bd66d..6537805 100644 --- a/core/modules/translation_entity/translation_entity.module +++ b/core/modules/translation_entity/translation_entity.module @@ -861,10 +861,11 @@ function translation_entity_field_info_alter(&$info) { } /** - * Implements hook_field_attach_presave(). + * Implements hook_entity_presave(). */ -function translation_entity_field_attach_presave(EntityInterface $entity) { - if ($entity->isTranslatable()) { +function translation_entity_entity_presave(EntityInterface $entity) { + $entity_info = $entity->entityInfo(); + if ($entity->isTranslatable() && !empty($entity_info['fieldable'])) { $attributes = drupal_container()->get('request')->attributes; Drupal::service('translation_entity.synchronizer')->synchronizeFields($entity, $attributes->get('working_langcode'), $attributes->get('source_langcode')); }