first commit

This commit is contained in:
2024-07-15 12:33:27 +02:00
commit ce50ae282b
22084 changed files with 2623791 additions and 0 deletions

464
core/modules/field/field.api.php Executable file
View File

@@ -0,0 +1,464 @@
<?php
/**
* @file
* Field API documentation.
*/
/**
* @addtogroup hooks
* @{
*/
/**
* @defgroup field_types Field Types API
* @{
* Defines field, widget, display formatter, and storage types.
*
* In the Field API, each field has a type, which determines what kind of data
* (integer, string, date, etc.) the field can hold, which settings it provides,
* and so on. The data type(s) accepted by a field are defined in the class
* implementing \Drupal\Core\Field\FieldItemInterface::schema() method.
*
* Field types are plugins with \Drupal\Core\Field\Attribute\FieldType
* attributes and implement plugin interface
* \Drupal\Core\Field\FieldItemInterface. Field Type plugins are managed by the
* \Drupal\Core\Field\FieldTypePluginManager class. Field type classes usually
* extend base class \Drupal\Core\Field\FieldItemBase. Field-type plugins need
* to be in the namespace \Drupal\{your_module}\Plugin\Field\FieldType. See the
* @link plugin_api Plugin API topic @endlink for more information on how to
* define plugins.
*
* The Field Types API also defines two kinds of pluggable handlers: widgets
* and formatters. @link field_widget Widgets @endlink specify how the field
* appears in edit forms, while @link field_formatter formatters @endlink
* specify how the field appears in displayed entities.
*
* See @link field Field API @endlink for information about the other parts of
* the Field API.
*
* @see field
* @see field_widget
* @see field_formatter
* @see plugin_api
*/
/**
* Perform alterations on Field API field types.
*
* @param $info
* Array of information on field types as collected by the "field type" plugin
* manager.
*/
function hook_field_info_alter(&$info) {
// Change the default widget for fields of type 'foo'.
if (isset($info['foo'])) {
$info['foo']['default_widget'] = 'my_module_widget';
}
}
/**
* Alters the UI field definitions.
*
* This hook can be used for altering field definitions available in the UI
* dynamically per entity type. For example, it can be used to hide field types
* that are incompatible with an entity type.
*
* @param array $ui_definitions
* Definition of all field types that can be added via UI.
* @param string $entity_type_id
* The entity type id.
*
* @see \Drupal\Core\Field\FieldTypePluginManagerInterface::getEntityTypeUiDefinitions
*/
function hook_field_info_entity_type_ui_definitions_alter(array &$ui_definitions, string $entity_type_id) {
if ($entity_type_id === 'node') {
unset($ui_definitions['field_type_not_compatible_with_node']);
}
}
/**
* Perform alterations on preconfigured field options.
*
* @param array $options
* Array of options as returned from
* \Drupal\Core\Field\PreconfiguredFieldUiOptionsInterface::getPreconfiguredOptions().
* @param string $field_type
* The field type plugin ID.
*
* @see \Drupal\Core\Field\PreconfiguredFieldUiOptionsInterface::getPreconfiguredOptions()
*/
function hook_field_ui_preconfigured_options_alter(array &$options, $field_type) {
// If the field is not an "entity_reference"-based field, bail out.
/** @var \Drupal\Core\Field\FieldTypePluginManager $field_type_manager */
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
$class = $field_type_manager->getPluginClass($field_type);
if (!is_a($class, 'Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem', TRUE)) {
return;
}
// Set the default formatter for media in entity reference fields to be the
// "Rendered entity" formatter.
if (!empty($options['media'])) {
$options['media']['entity_view_display']['type'] = 'entity_reference_entity_view';
}
}
/**
* Forbid a field storage update from occurring.
*
* Any module may forbid any update for any reason. For example, the
* field's storage module might forbid an update if it would change
* the storage schema while data for the field exists. A field type
* module might forbid an update if it would change existing data's
* semantics, or if there are external dependencies on field settings
* that cannot be updated.
*
* To forbid the update from occurring, throw a
* \Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException.
*
* @param \Drupal\field\FieldStorageConfigInterface $field_storage
* The field storage as it will be post-update.
* @param \Drupal\field\FieldStorageConfigInterface $prior_field_storage
* The field storage as it is pre-update.
*
* @see entity_crud
*/
function hook_field_storage_config_update_forbid(\Drupal\field\FieldStorageConfigInterface $field_storage, \Drupal\field\FieldStorageConfigInterface $prior_field_storage) {
if ($field_storage->getTypeProvider() == 'options' && $field_storage->hasData()) {
// Forbid any update that removes allowed values with actual data.
$allowed_values = $field_storage->getSetting('allowed_values');
$prior_allowed_values = $prior_field_storage->getSetting('allowed_values');
$lost_keys = array_keys(array_diff_key($prior_allowed_values, $allowed_values));
if (_options_values_in_use($field_storage->getTargetEntityTypeId(), $field_storage->getName(), $lost_keys)) {
throw new \Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException("A list field '{$field_storage->getName()}' with existing data cannot have its keys changed.");
}
}
}
/**
* @} End of "defgroup field_types".
*/
/**
* @defgroup field_widget Field Widget API
* @{
* Define Field API widget types.
*
* Field API widgets specify how fields are displayed in edit forms. Fields of a
* given @link field_types field type @endlink may be edited using more than one
* widget. In this case, the Field UI module allows the site builder to choose
* which widget to use.
*
* Widgets are Plugins managed by the
* \Drupal\Core\Field\WidgetPluginManager class. A widget is a plugin
* attributed with class \Drupal\Core\Field\Attribute\FieldWidget that
* implements \Drupal\Core\Field\WidgetInterface (in most cases, by subclassing
* \Drupal\Core\Field\WidgetBase). Widget plugins need to be in the namespace
* \Drupal\{your_module}\Plugin\Field\FieldWidget.
*
* Widgets are @link form_api Form API @endlink elements with additional
* processing capabilities. The methods of the WidgetInterface object are
* typically called by respective methods in the
* \Drupal\Core\Entity\Entity\EntityFormDisplay class.
*
* @see field
* @see field_types
* @see field_formatter
* @see plugin_api
*/
/**
* Perform alterations on Field API widget types.
*
* @param array $info
* An array of information on existing widget types, as collected by the
* plugin discovery mechanism.
*/
function hook_field_widget_info_alter(array &$info) {
// Let a new field type re-use an existing widget.
$info['options_select']['field_types'][] = 'my_field_type';
}
/**
* Alter forms for field widgets provided by other modules.
*
* This hook can only modify individual elements within a field widget and
* cannot alter the top level (parent element) for multi-value fields. In most
* cases, you should use hook_field_widget_complete_form_alter() instead and
* loop over the elements.
*
* @param array $element
* The field widget form element as constructed by
* \Drupal\Core\Field\WidgetBaseInterface::form().
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param array $context
* An associative array containing the following key-value pairs:
* - form: The form structure to which widgets are being attached. This may be
* a full form structure, or a sub-element of a larger form.
* - widget: The widget plugin instance.
* - items: The field values, as a
* \Drupal\Core\Field\FieldItemListInterface object.
* - delta: The order of this item in the array of subelements (0, 1, 2, etc).
* - default: A boolean indicating whether the form is being shown as a dummy
* form to set default values.
*
* @see \Drupal\Core\Field\WidgetBaseInterface::form()
* @see \Drupal\Core\Field\WidgetBase::formSingleElement()
* @see hook_field_widget_single_element_WIDGET_TYPE_form_alter()
* @see hook_field_widget_complete_form_alter()
* @see https://www.drupal.org/node/3180429
*/
function hook_field_widget_single_element_form_alter(array &$element, \Drupal\Core\Form\FormStateInterface $form_state, array $context) {
// Add a css class to widget form elements for all fields of type my_type.
$field_definition = $context['items']->getFieldDefinition();
if ($field_definition->getType() == 'my_type') {
// Be sure not to overwrite existing attributes.
$element['#attributes']['class'][] = 'my-class';
}
}
/**
* Alter widget forms for a specific widget provided by another module.
*
* Modules can implement
* hook_field_widget_single_element_WIDGET_TYPE_form_alter() to modify a
* specific widget form, rather than using
* hook_field_widget_single_element_form_alter() and checking the widget type.
*
* This hook can only modify individual elements within a field widget and
* cannot alter the top level (parent element) for multi-value fields. In most
* cases, you should use hook_field_widget_complete_WIDGET_TYPE_form_alter()
* instead and loop over the elements.
*
* @param array $element
* The field widget form element as constructed by
* \Drupal\Core\Field\WidgetBaseInterface::form().
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param array $context
* An associative array. See hook_field_widget_single_element_form_alter()
* for the structure and content of the array.
*
* @see https://www.drupal.org/node/3180429
* @see \Drupal\Core\Field\WidgetBaseInterface::form()
* @see \Drupal\Core\Field\WidgetBase::formSingleElement()
* @see hook_field_widget_single_element_form_alter()
* @see hook_field_widget_complete_WIDGET_TYPE_form_alter()
*/
function hook_field_widget_single_element_WIDGET_TYPE_form_alter(array &$element, \Drupal\Core\Form\FormStateInterface $form_state, array $context) {
// Code here will only act on widgets of type WIDGET_TYPE. For example,
// hook_field_widget_single_element_my_module_autocomplete_form_alter() will
// only act on widgets of type 'my_module_autocomplete'.
$element['#autocomplete_route_name'] = 'my_module.autocomplete_route';
}
/**
* Alter the complete form for field widgets provided by other modules.
*
* @param $field_widget_complete_form
* The field widget form element as constructed by
* \Drupal\Core\Field\WidgetBaseInterface::form().
* @param $form_state
* The current state of the form.
* @param $context
* An associative array containing the following key-value pairs:
* - form: The form structure to which widgets are being attached. This may be
* a full form structure, or a sub-element of a larger form.
* - widget: The widget plugin instance.
* - items: The field values, as a
* \Drupal\Core\Field\FieldItemListInterface object.
* - delta: The order of this item in the array of subelements (0, 1, 2, etc).
* - default: A boolean indicating whether the form is being shown as a dummy
* form to set default values.
*
* @see \Drupal\Core\Field\WidgetBaseInterface::form()
* @see \Drupal\Core\Field\WidgetBase::form()
* @see hook_field_widget_complete_WIDGET_TYPE_form_alter()
* @see https://www.drupal.org/node/3180429
*/
function hook_field_widget_complete_form_alter(&$field_widget_complete_form, \Drupal\Core\Form\FormStateInterface $form_state, $context) {
$field_widget_complete_form['#attributes']['class'][] = 'my-class';
}
/**
* Alter the complete form for a specific widget provided by other modules.
*
* Modules can implement hook_field_widget_complete_WIDGET_TYPE_form_alter()
* to modify a specific widget form, rather than using
* hook_field_widget_complete_form_alter() and checking the widget type.
*
* @param $field_widget_complete_form
* The field widget form element as constructed by
* \Drupal\Core\Field\WidgetBaseInterface::form().
* @param $form_state
* The current state of the form.
* @param $context
* An associative array containing the following key-value pairs:
* - form: The form structure to which widgets are being attached. This may be
* a full form structure, or a sub-element of a larger form.
* - widget: The widget plugin instance.
* - items: The field values, as a
* \Drupal\Core\Field\FieldItemListInterface object.
* - delta: The order of this item in the array of subelements (0, 1, 2, etc).
* - default: A boolean indicating whether the form is being shown as a dummy
* form to set default values.
*
* @see \Drupal\Core\Field\WidgetBaseInterface::form()
* @see \Drupal\Core\Field\WidgetBase::form()
* @see hook_field_widget_complete_form_alter()
* @see https://www.drupal.org/node/3180429
*/
function hook_field_widget_complete_WIDGET_TYPE_form_alter(&$field_widget_complete_form, \Drupal\Core\Form\FormStateInterface $form_state, $context) {
$field_widget_complete_form['#attributes']['class'][] = 'my-class';
}
/**
* @} End of "defgroup field_widget".
*/
/**
* @defgroup field_formatter Field Formatter API
* @{
* Define Field API formatter types.
*
* Field API formatters specify how fields are displayed when the entity to
* which the field is attached is displayed. Fields of a given
* @link field_types field type @endlink may be displayed using more than one
* formatter. In this case, the Field UI module allows the site builder to
* choose which formatter to use.
*
* Formatters are Plugins managed by the
* \Drupal\Core\Field\FormatterPluginManager class. A formatter is a plugin
* attributed with class \Drupal\Core\Field\Attribute\FieldFormatter that
* implements \Drupal\Core\Field\FormatterInterface (in most cases, by
* subclassing \Drupal\Core\Field\FormatterBase). Formatter plugins need to be
* in the namespace \Drupal\{your_module}\Plugin\Field\FieldFormatter.
*
* @see field
* @see field_types
* @see field_widget
* @see plugin_api
*/
/**
* Perform alterations on Field API formatter types.
*
* @param array $info
* An array of information on existing formatter types, as collected by the
* plugin discovery mechanism.
*/
function hook_field_formatter_info_alter(array &$info) {
// Let a new field type re-use an existing formatter.
$info['text_default']['field_types'][] = 'my_field_type';
}
/**
* @} End of "defgroup field_formatter".
*/
/**
* Returns the maximum weight for the entity components handled by the module.
*
* Field API takes care of fields and 'extra_fields'. This hook is intended for
* third-party modules adding other entity components (e.g. field_group).
*
* @param string $entity_type
* The type of entity; e.g. 'node' or 'user'.
* @param string $bundle
* The bundle name.
* @param string $context
* The context for which the maximum weight is requested. Either 'form' or
* 'display'.
* @param string $context_mode
* The view or form mode name.
*
* @return int
* The maximum weight of the entity's components, or NULL if no components
* were found.
*
* @ingroup field_info
*/
function hook_field_info_max_weight($entity_type, $bundle, $context, $context_mode) {
$weights = [];
foreach (my_module_entity_additions($entity_type, $bundle, $context, $context_mode) as $addition) {
$weights[] = $addition['weight'];
}
return $weights ? max($weights) : NULL;
}
/**
* @addtogroup field_purge
* @{
*/
/**
* Acts when a field storage definition is being purged.
*
* In field_purge_field_storage(), after the storage definition has been removed
* from the system, the entity storage has purged stored field data, and the
* field definitions cache has been cleared, this hook is invoked on all modules
* to allow them to respond to the field storage being purged.
*
* @param $field_storage \Drupal\field\Entity\FieldStorageConfig
* The field storage being purged.
*/
function hook_field_purge_field_storage(\Drupal\field\Entity\FieldStorageConfig $field_storage) {
\Drupal::database()->delete('my_module_field_storage_info')
->condition('uuid', $field_storage->uuid())
->execute();
}
/**
* Acts when a field is being purged.
*
* In field_purge_field(), after the field definition has been removed
* from the system, the entity storage has purged stored field data, and the
* field info cache has been cleared, this hook is invoked on all modules to
* allow them to respond to the field being purged.
*
* @param $field
* The field being purged.
*/
function hook_field_purge_field(\Drupal\field\Entity\FieldConfig $field) {
\Drupal::database()->delete('my_module_field_info')
->condition('id', $field->id())
->execute();
}
/**
* Allows modules to alter the field type category information.
*
* This hook provides a way for modules to modify or add to the existing
* category information. Modules can use this hook to modify the properties of
* existing categories. It can also be used to define custom field type
* categories although the use of YAML-based plugins should be preferred over
* the hook.
*
* @param array &$categories
* An associative array of field type categories, keyed by category machine
* name.
*
* @see \Drupal\Core\Field\FieldTypeCategoryManager
*/
function hook_field_type_category_info_alter(array &$categories) {
// Modify or add field type categories.
$categories['my_custom_category'] = [
'label' => 'My Custom Category',
'description' => 'This is a custom category for my field types.',
];
// Modify the properties of an existing category.
$categories['text']['description'] = 'Modified Text';
}
/**
* @} End of "addtogroup field_purge".
*/
/**
* @} End of "addtogroup hooks".
*/

View File

@@ -0,0 +1,10 @@
name: Field
type: module
description: 'Provides the capabilities to add fields to entities.'
package: Core
# version: VERSION
# Information added by Drupal.org packaging script on 2024-07-04
version: '10.3.1'
project: 'drupal'
datestamp: 1720094222

View File

@@ -0,0 +1,13 @@
<?php
/**
* @file
* Install, update and uninstall functions for the field module.
*/
/**
* Implements hook_update_last_removed().
*/
function field_update_last_removed() {
return 8500;
}

502
core/modules/field/field.module Executable file
View File

@@ -0,0 +1,502 @@
<?php
/**
* @file
* Attach custom data fields to Drupal entities.
*/
use Drupal\Core\Config\ConfigImporter;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\DynamicallyFieldableEntityStorageInterface;
use Drupal\field\ConfigImporterFieldPurger;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field\EntityDisplayRebuilder;
use Drupal\field\FieldConfigInterface;
use Drupal\field\FieldStorageConfigInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Link;
use Drupal\Core\Url;
/*
* Load all public Field API functions. Drupal currently has no
* mechanism for auto-loading core APIs, so we have to load them on
* every page request.
*/
require_once __DIR__ . '/field.purge.inc';
/**
* @defgroup field Field API
* @{
* Attaches custom data fields to Drupal entities.
*
* The Field API allows custom data fields to be attached to Drupal entities and
* takes care of storing, loading, editing, and rendering field data. Any entity
* type (node, user, etc.) can use the Field API to make itself "fieldable" and
* thus allow fields to be attached to it. Other modules can provide a user
* interface for managing custom fields via a web browser as well as a wide and
* flexible variety of data type, form element, and display format capabilities.
*
* The Field API defines two primary data structures, FieldStorage and Field,
* and the concept of a Bundle. A FieldStorage defines a particular type of data
* that can be attached to entities. A Field is attached to a single
* Bundle. A Bundle is a set of fields that are treated as a group by the Field
* Attach API and is related to a single fieldable entity type.
*
* For example, suppose a site administrator wants Article nodes to have a
* subtitle and photo. Using the Field API or Field UI module, the administrator
* creates a field named 'subtitle' of type 'text' and a field named 'photo' of
* type 'image'. The administrator (again, via a UI) creates two Field
* Instances, one attaching the field 'subtitle' to the 'node' bundle 'article'
* and one attaching the field 'photo' to the 'node' bundle 'article'. When the
* node storage loads an Article node, it loads the values of the
* 'subtitle' and 'photo' fields because they are both attached to the 'node'
* bundle 'article'.
*
* - @link field_types Field Types API @endlink: Defines field types, widget
* types, and display formatters. Field modules use this API to provide field
* types like Text and Node Reference along with the associated form elements
* and display formatters.
*
* - @link field_purge Field API bulk data deletion @endlink: Cleans up after
* bulk deletion operations such as deletion of field storage or field.
*/
/**
* Implements hook_help().
*/
function field_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.field':
$field_ui_url = \Drupal::moduleHandler()->moduleExists('field_ui') ? Url::fromRoute('help.page', ['name' => 'field_ui'])->toString() : '#';
$output = '';
$output .= '<h2>' . t('About') . '</h2>';
$output .= '<p>' . t('The Field module allows custom data fields to be defined for <em>entity</em> types (see below). The Field module takes care of storing, loading, editing, and rendering field data. Most users will not interact with the Field module directly, but will instead use the <a href=":field-ui-help">Field UI module</a> user interface. Module developers can use the Field API to make new entity types "fieldable" and thus allow fields to be attached to them. For more information, see the <a href=":field">online documentation for the Field module</a>.', [':field-ui-help' => $field_ui_url, ':field' => 'https://www.drupal.org/documentation/modules/field']) . '</p>';
$output .= '<h2>' . t('Terminology') . '</h2>';
$output .= '<dl>';
$output .= '<dt>' . t('Entities and entity types') . '</dt>';
$output .= '<dd>' . t("The website's content and configuration is managed using <em>entities</em>, which are grouped into <em>entity types</em>. <em>Content entity types</em> are the entity types for site content (such as the main site content, comments, content blocks, taxonomy terms, and user accounts). <em>Configuration entity types</em> are used to store configuration information for your site, such as individual views in the Views module, and settings for your main site content types.") . '</dd>';
$output .= '<dt>' . t('Entity sub-types') . '</dt>';
$output .= '<dd>' . t('Some content entity types are further grouped into sub-types (for example, you could have article and page content types within the main site content entity type, and tag and category vocabularies within the taxonomy term entity type); other entity types, such as user accounts, do not have sub-types. Programmers use the term <em>bundle</em> for entity sub-types.') . '</dd>';
$output .= '<dt>' . t('Fields and field types') . '</dt>';
$output .= '<dd>' . t('Content entity types and sub-types store most of their text, file, and other information in <em>fields</em>. Fields are grouped by <em>field type</em>; field types define what type of data can be stored in that field, such as text, images, or taxonomy term references.') . '</dd>';
$output .= '<dt>' . t('Formatters and view modes') . '</dd>';
$output .= '<dd>' . t('Content entity types and sub-types can have one or more <em>view modes</em>, used for displaying the entity items. For instance, a content item could be viewed in full content mode on its own page, teaser mode in a list, or RSS mode in a feed. In each view mode, each field can be hidden or displayed, and if it is displayed, you can choose and configure the <em>formatter</em> that is used to display the field. For instance, a long text field can be displayed trimmed or full-length, and taxonomy term reference fields can be displayed in plain text or linked to the taxonomy term page.') . '</dd>';
$output .= '<dt>' . t('Widgets and form modes') . '</dd>';
$output .= '<dd>' . t('Content entity types and sub-types can have one or more <em>form modes</em>, used for editing. For instance, a content item could be edited in a compact format with only some fields editable, or a full format that allows all fields to be edited. In each form mode, each field can be hidden or displayed, and if it is displayed, you can choose and configure the <em>widget</em> that is used to edit the field. For instance, a taxonomy term reference field can be edited using a select list, radio buttons, or an autocomplete widget.') . '</dd>';
$output .= '</dl>';
$output .= '<h2>' . t('Uses') . '</h2>';
$output .= '<dl>';
$output .= '<dt>' . t('Enabling field types, widgets, and formatters') . '</dt>';
$output .= '<dd>' . t('The Field module provides the infrastructure for fields; the field types, formatters, and widgets are provided by Drupal core or additional modules. Some of the modules are required; the optional modules can be installed from the <a href=":modules">Extend administration page</a>. Additional fields, formatters, and widgets may be provided by contributed modules, which you can find in the <a href=":contrib">contributed module section of Drupal.org</a>.', [':modules' => Url::fromRoute('system.modules_list')->toString(), ':contrib' => 'https://www.drupal.org/project/modules']) . '</dd>';
$output .= '<h2>' . t('Field, widget, and formatter information') . '</h2>';
// Make a list of all widget, formatter, and field modules currently
// enabled, ordered by displayed module name (module names are not
// translated).
$items = [];
$modules = \Drupal::moduleHandler()->getModuleList();
$widgets = \Drupal::service('plugin.manager.field.widget')->getDefinitions();
$field_types = \Drupal::service('plugin.manager.field.field_type')->getUiDefinitions();
$formatters = \Drupal::service('plugin.manager.field.formatter')->getDefinitions();
$providers = [];
foreach (array_merge($field_types, $widgets, $formatters) as $plugin) {
$providers[] = $plugin['provider'];
}
$providers = array_unique($providers);
sort($providers);
$module_extension_list = \Drupal::service('extension.list.module');
foreach ($providers as $provider) {
// Skip plugins provided by core components as they do not implement
// hook_help().
if (isset($modules[$provider])) {
$display = $module_extension_list->getName($provider);
if (\Drupal::moduleHandler()->hasImplementations('help', $provider)) {
$items[] = Link::fromTextAndUrl($display, Url::fromRoute('help.page', ['name' => $provider]))->toRenderable();
}
else {
$items[] = $display;
}
}
}
if ($items) {
$output .= '<dt>' . t('Provided by modules') . '</dt>';
$output .= '<dd>' . t('Here is a list of the currently installed field, formatter, and widget modules:');
$item_list = [
'#theme' => 'item_list',
'#items' => $items,
];
$output .= \Drupal::service('renderer')->renderInIsolation($item_list);
$output .= '</dd>';
}
$output .= '<dt>' . t('Provided by Drupal core') . '</dt>';
$output .= '<dd>' . t('As mentioned previously, some field types, widgets, and formatters are provided by Drupal core. Here are some notes on how to use some of these:');
$output .= '<ul>';
$output .= '<li><p>' . t('<strong>Entity Reference</strong> fields allow you to create fields that contain links to other entities (such as content items, taxonomy terms, etc.) within the site. This allows you, for example, to include a link to a user within a content item. For more information, see <a href=":er_do">the online documentation for the Entity Reference module</a>.', [':er_do' => 'https://drupal.org/documentation/modules/entityreference']) . '</p>';
$output .= '<dl>';
$output .= '<dt>' . t('Managing and displaying entity reference fields') . '</dt>';
$output .= '<dd>' . t('The <em>settings</em> and the <em>display</em> of the entity reference field can be configured separately. See the <a href=":field_ui">Field UI help</a> for more information on how to manage fields and their display.', [':field_ui' => $field_ui_url]) . '</dd>';
$output .= '<dt>' . t('Selecting reference type') . '</dt>';
$output .= '<dd>' . t('In the field settings you can select which entity type you want to create a reference to.') . '</dd>';
$output .= '<dt>' . t('Filtering and sorting reference fields') . '</dt>';
$output .= '<dd>' . t('Depending on the chosen entity type, additional filtering and sorting options are available for the list of entities that can be referred to, in the field settings. For example, the list of users can be filtered by role and sorted by name or ID.') . '</dd>';
$output .= '<dt>' . t('Displaying a reference') . '</dt>';
$output .= '<dd>' . t('An entity reference can be displayed as a simple label with or without a link to the entity. Alternatively, the referenced entity can be displayed as a teaser (or any other available view mode) inside the referencing entity.') . '</dd>';
$output .= '<dt>' . t('Configuring form displays') . '</dt>';
$output .= '<dd>' . t('Reference fields have several widgets available on the <em>Manage form display</em> page:');
$output .= '<ul>';
$output .= '<li>' . t('The <em>Check boxes/radio buttons</em> widget displays the existing entities for the entity type as check boxes or radio buttons based on the <em>Allowed number of values</em> set for the field.') . '</li>';
$output .= '<li>' . t('The <em>Select list</em> widget displays the existing entities in a drop-down list or scrolling list box based on the <em>Allowed number of values</em> setting for the field.') . '</li>';
$output .= '<li>' . t('The <em>Autocomplete</em> widget displays text fields in which users can type entity labels based on the <em>Allowed number of values</em>. The widget can be configured to display all entities that contain the typed characters or restricted to those starting with those characters.') . '</li>';
$output .= '<li>' . t('The <em>Autocomplete (Tags style)</em> widget displays a multi-text field in which users can type in a comma-separated list of entity labels.') . '</li>';
$output .= '</ul></dd>';
$output .= '</dl></li>';
$output .= '<li>' . t('<strong>Number fields</strong>: When you add a number field you can choose from three types: <em>decimal</em>, <em>float</em>, and <em>integer</em>. The <em>decimal</em> number field type allows users to enter exact decimal values, with fixed numbers of decimal places. The <em>float</em> number field type allows users to enter approximate decimal values. The <em>integer</em> number field type allows users to enter whole numbers, such as years (for example, 2012) or values (for example, 1, 2, 5, 305). It does not allow decimals.') . '</li>';
$output .= '</ul></dd>';
$output .= '</dl>';
return $output;
}
}
/**
* Implements hook_cron().
*/
function field_cron() {
// Do a pass of purging on deleted Field API data, if any exists.
$limit = \Drupal::config('field.settings')->get('purge_batch_size');
field_purge_batch($limit);
}
/**
* Implements hook_entity_field_storage_info().
*/
function field_entity_field_storage_info(EntityTypeInterface $entity_type) {
if (\Drupal::entityTypeManager()->getStorage($entity_type->id()) instanceof DynamicallyFieldableEntityStorageInterface) {
// Query by filtering on the ID as this is more efficient than filtering
// on the entity_type property directly.
$ids = \Drupal::entityQuery('field_storage_config')
->condition('id', $entity_type->id() . '.', 'STARTS_WITH')
->execute();
// Fetch all fields and key them by field name.
$field_storages = FieldStorageConfig::loadMultiple($ids);
$result = [];
foreach ($field_storages as $field_storage) {
$result[$field_storage->getName()] = $field_storage;
}
return $result;
}
}
/**
* Implements hook_entity_bundle_field_info().
*/
function field_entity_bundle_field_info(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
if (\Drupal::entityTypeManager()->getStorage($entity_type->id()) instanceof DynamicallyFieldableEntityStorageInterface) {
// Query by filtering on the ID as this is more efficient than filtering
// on the entity_type property directly.
$ids = \Drupal::entityQuery('field_config')
->condition('id', $entity_type->id() . '.' . $bundle . '.', 'STARTS_WITH')
->execute();
// Fetch all fields and key them by field name.
$field_configs = FieldConfig::loadMultiple($ids);
$result = [];
foreach ($field_configs as $field_instance) {
$result[$field_instance->getName()] = $field_instance;
}
return $result;
}
}
/**
* Implements hook_entity_bundle_delete().
*/
function field_entity_bundle_delete($entity_type_id, $bundle) {
$storage = \Drupal::entityTypeManager()->getStorage('field_config');
// Get the fields on the bundle.
$fields = $storage->loadByProperties(['entity_type' => $entity_type_id, 'bundle' => $bundle]);
// This deletes the data for the field as well as the field themselves. This
// function actually just marks the data and fields as deleted, leaving the
// garbage collection for a separate process, because it is not always
// possible to delete this much data in a single page request (particularly
// since for some field types, the deletion is more than just a simple DELETE
// query).
foreach ($fields as $field) {
$field->delete();
}
// We are duplicating the work done by
// \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem::onDependencyRemoval()
// because we need to take into account bundles that are not provided by a
// config entity type so they are not part of the config dependencies.
// Gather a list of all entity reference fields.
$map = \Drupal::service('entity_field.manager')->getFieldMapByFieldType('entity_reference');
$ids = [];
foreach ($map as $type => $info) {
foreach ($info as $name => $data) {
foreach ($data['bundles'] as $bundle_name) {
$ids[] = "$type.$bundle_name.$name";
}
}
}
// Update the 'target_bundles' handler setting if needed.
foreach (FieldConfig::loadMultiple($ids) as $field_config) {
if ($field_config->getSetting('target_type') == $entity_type_id) {
$handler_settings = $field_config->getSetting('handler_settings');
if (isset($handler_settings['target_bundles'][$bundle])) {
unset($handler_settings['target_bundles'][$bundle]);
$field_config->setSetting('handler_settings', $handler_settings);
$field_config->save();
}
}
}
}
/**
* @} End of "defgroup field".
*/
/**
* Assembles a partial entity structure with initial IDs.
*
* @param object $ids
* An object with the properties entity_type (required), entity_id (required),
* revision_id (optional) and bundle (optional).
*
* @return \Drupal\Core\Entity\EntityInterface
* An entity, initialized with the provided IDs.
*/
function _field_create_entity_from_ids($ids) {
$id_properties = [];
$entity_type = \Drupal::entityTypeManager()->getDefinition($ids->entity_type);
if ($id_key = $entity_type->getKey('id')) {
$id_properties[$id_key] = $ids->entity_id;
}
if (isset($ids->revision_id) && $revision_key = $entity_type->getKey('revision')) {
$id_properties[$revision_key] = $ids->revision_id;
}
if (isset($ids->bundle) && $bundle_key = $entity_type->getKey('bundle')) {
$id_properties[$bundle_key] = $ids->bundle;
}
return \Drupal::entityTypeManager()
->getStorage($ids->entity_type)
->create($id_properties);
}
/**
* Implements hook_config_import_steps_alter().
*/
function field_config_import_steps_alter(&$sync_steps, ConfigImporter $config_importer) {
$field_storages = ConfigImporterFieldPurger::getFieldStoragesToPurge(
$config_importer->getStorageComparer()->getSourceStorage()->read('core.extension'),
$config_importer->getStorageComparer()->getChangelist('delete')
);
if ($field_storages) {
// Add a step to the beginning of the configuration synchronization process
// to purge field data where the module that provides the field is being
// uninstalled.
array_unshift($sync_steps, ['\Drupal\field\ConfigImporterFieldPurger', 'process']);
}
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Adds a warning if field data will be permanently removed by the configuration
* synchronization.
*
* @see \Drupal\field\ConfigImporterFieldPurger
*/
function field_form_config_admin_import_form_alter(&$form, FormStateInterface $form_state) {
// Only display the message when core.extension is available in the source
// storage and the form is not submitted.
$user_input = $form_state->getUserInput();
$storage_comparer = $form_state->get('storage_comparer');
if ($storage_comparer?->getSourceStorage()->exists('core.extension') && empty($user_input)) {
$field_storages = ConfigImporterFieldPurger::getFieldStoragesToPurge(
$storage_comparer->getSourceStorage()->read('core.extension'),
$storage_comparer->getChangelist('delete')
);
if ($field_storages) {
foreach ($field_storages as $field) {
$field_labels[] = $field->label();
}
\Drupal::messenger()->addWarning(\Drupal::translation()->formatPlural(
count($field_storages),
'This synchronization will delete data from the field %fields.',
'This synchronization will delete data from the fields: %fields.',
['%fields' => implode(', ', $field_labels)]
));
}
}
}
/**
* Implements hook_ENTITY_TYPE_insert() for 'field_config'.
*/
function field_field_config_insert(FieldConfigInterface $field) {
if ($field->isSyncing()) {
// Don't change anything during a configuration sync.
return;
}
// Allow other view modes to update their configuration for the new field.
// Otherwise, configuration for view modes won't get updated until the mode
// is used for the first time, creating noise in config diffs.
\Drupal::classResolver(EntityDisplayRebuilder::class)
->rebuildEntityTypeDisplays($field->getTargetEntityTypeId(), $field->getTargetBundle());
}
/**
* Implements hook_ENTITY_TYPE_update() for 'field_storage_config'.
*
* Reset the field handler settings, when the storage target_type is changed on
* an entity reference field.
*/
function field_field_storage_config_update(FieldStorageConfigInterface $field_storage) {
if ($field_storage->isSyncing()) {
// Don't change anything during a configuration sync.
return;
}
// Act on all sub-types of the entity_reference field type.
/** @var \Drupal\Core\Field\FieldTypePluginManager $field_type_manager */
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
$item_class = 'Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem';
$class = $field_type_manager->getPluginClass($field_storage->getType());
if ($class !== $item_class && !is_subclass_of($class, $item_class)) {
return;
}
// If target_type changed, reset the handler in the fields using that storage.
if ($field_storage->getSetting('target_type') !== $field_storage->original->getSetting('target_type')) {
foreach ($field_storage->getBundles() as $bundle) {
$field = FieldConfig::loadByName($field_storage->getTargetEntityTypeId(), $bundle, $field_storage->getName());
// Reset the handler settings. This triggers field_field_config_presave(),
// which will take care of reassigning the handler to the correct
// derivative for the new target_type.
$field->setSetting('handler_settings', []);
$field->save();
}
}
}
/**
* Implements hook_ENTITY_TYPE_create() for 'field_config'.
*
* Determine the selection handler plugin ID for an entity reference field.
*/
function field_field_config_create(FieldConfigInterface $field) {
if ($field->isSyncing()) {
return;
}
// Act on all sub-types of the entity_reference field type.
/** @var \Drupal\Core\Field\FieldTypePluginManager $field_type_manager */
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
$item_class = 'Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem';
$class = $field_type_manager->getPluginClass($field->getType());
if ($class !== $item_class && !is_subclass_of($class, $item_class)) {
return;
}
// If we don't know the target type yet, there's nothing else we can do.
$target_type = $field->getFieldStorageDefinition()->getSetting('target_type');
if (empty($target_type)) {
return;
}
// Make sure the selection handler plugin is the correct derivative for the
// target entity type.
$selection_manager = \Drupal::service('plugin.manager.entity_reference_selection');
[$current_handler] = explode(':', $field->getSetting('handler'), 2);
$field->setSetting('handler', $selection_manager->getPluginId($target_type, $current_handler));
}
/**
* Implements hook_ENTITY_TYPE_presave() for 'field_config'.
*
* Determine the selection handler plugin ID for an entity reference field.
*/
function field_field_config_presave(FieldConfigInterface $field) {
// Don't change anything during a configuration sync.
if ($field->isSyncing()) {
return;
}
field_field_config_create($field);
// Act on all sub-types of the entity_reference field type.
/** @var \Drupal\Core\Field\FieldTypePluginManager $field_type_manager */
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
$item_class = 'Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem';
$class = $field_type_manager->getPluginClass($field->getType());
if ($class !== $item_class && !is_subclass_of($class, $item_class)) {
return;
}
// In case we removed all the target bundles allowed by the field in
// EntityReferenceItem::onDependencyRemoval() or field_entity_bundle_delete()
// we have to log a critical message because the field will not function
// correctly anymore.
$handler_settings = $field->getSetting('handler_settings');
if (isset($handler_settings['target_bundles']) && $handler_settings['target_bundles'] === []) {
\Drupal::logger('entity_reference')->critical('The %field_name entity reference field (entity_type: %entity_type, bundle: %bundle) no longer has any valid bundle it can reference. The field is not working correctly anymore and has to be adjusted.', [
'%field_name' => $field->getName(),
'%entity_type' => $field->getTargetEntityTypeId(),
'%bundle' => $field->getTargetBundle(),
]);
}
}
/**
* Entity form builder for field config edit form.
*
* @param string $entity_type_id
* The entity type identifier.
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity updated with the submitted values.
* @param array $form
* The complete form array.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @see \Drupal\field_ui\Form\FieldConfigEditForm::form
* @see \Drupal\field_ui\Form\FieldConfigEditForm::copyFormValuesToEntity
*/
function field_form_field_config_edit_form_entity_builder($entity_type_id, $entity, &$form, FormStateInterface $form_state) {
assert($entity instanceof FieldConfigInterface);
$previous_field_storage = $form_state->getFormObject()->getEntity()->getFieldStorageDefinition();
assert($previous_field_storage instanceof FieldStorageConfigInterface);
// Act on all sub-types of the entity_reference field type.
/** @var \Drupal\Core\Field\FieldTypePluginManager $field_type_manager */
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
$item_class = 'Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem';
$class = $field_type_manager->getPluginClass($entity->getFieldStorageDefinition()->getType());
if ($class !== $item_class && !is_subclass_of($class, $item_class)) {
return;
}
// Update handler settings when target_type is changed.
if ($entity->getFieldStorageDefinition()->getSetting('target_type') !== $previous_field_storage->getSetting('target_type')) {
// @see field_field_storage_config_update().
$entity->setSetting('handler_settings', []);
// @see field_field_config_presave().
field_field_config_create($entity);
// Store updated settings in form state so that the form state can be copied
// directly to the entity.
$form_state->setValue('settings', $entity->getSettings());
// Unset user input for the settings because they are not valid after the
// target type has changed.
$user_input = $form_state->getUserInput();
unset($user_input['settings']);
$form_state->setUserInput($user_input);
}
}

View File

@@ -0,0 +1,18 @@
<?php
/**
* @file
* Post update functions for Field module.
*/
/**
* Implements hook_removed_post_updates().
*/
function field_removed_post_updates() {
return [
'field_post_update_save_custom_storage_property' => '9.0.0',
'field_post_update_entity_reference_handler_setting' => '9.0.0',
'field_post_update_email_widget_size_setting' => '9.0.0',
'field_post_update_remove_handler_submit_setting' => '9.0.0',
];
}

View File

@@ -0,0 +1,158 @@
<?php
/**
* @file
* Provides support for field data purge after mass deletion.
*/
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldException;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
/**
* @defgroup field_purge Field API bulk data deletion
* @{
* Cleans up after Field API bulk deletion operations.
*
* Field API provides functions for deleting data attached to individual
* entities as well as deleting entire fields or field storages in a single
* operation.
*
* When a single entity is deleted, the Entity storage performs the
* following operations:
* - Invoking the method \Drupal\Core\Field\FieldItemListInterface::delete() for
* each field on the entity. A file field type might use this method to delete
* uploaded files from the filesystem.
* - Removing the data from storage.
* - Invoking the global hook_entity_delete() for all modules that implement it.
* Each hook implementation receives the entity being deleted and can operate
* on whichever subset of the entity's bundle's fields it chooses to.
*
* Similar operations are performed on deletion of a single entity revision.
*
* When a bundle, field or field storage is deleted, it is not practical to
* perform those operations immediately on every affected entity in a single
* page request; there could be thousands or millions of them. Instead, the
* appropriate field data items, fields, and/or field storages are marked as
* deleted so that subsequent load or query operations will not return them.
* Later, a separate process cleans up, or "purges", the marked-as-deleted data
* by going through the three-step process described above and, finally,
* removing deleted field storage and field records.
*
* Purging field data is made somewhat tricky by the fact that, while
* $entity->delete() has a complete entity to pass to the various deletion
* steps, the Field API purge process only has the field data it has previously
* stored. It cannot reconstruct complete original entities to pass to the
* deletion operations. It is even possible that the original entity to which
* some Field API data was attached has been itself deleted before the field
* purge operation takes place.
*
* Field API resolves this problem by using stub entities during purge
* operations, containing only the information from the original entity that
* Field API knows about: entity type, ID, revision ID, and bundle. It also
* contains the field data for whichever field is currently being purged.
*
* See @link field Field API @endlink for information about the other parts of
* the Field API.
*/
/**
* Purges a batch of deleted Field API data, field storages, or fields.
*
* This function will purge deleted field data in batches. The batch size
* is defined as an argument to the function, and once each batch is finished,
* it continues with the next batch until all have completed. If a deleted field
* with no remaining data records is found, the field itself will
* be purged. If a deleted field storage with no remaining fields is found, the
* field storage itself will be purged.
*
* @param int $batch_size
* The maximum number of field data records to purge before returning.
* @param string $field_storage_unique_id
* (optional) Limit the purge to a specific field storage. Defaults to NULL.
*/
function field_purge_batch($batch_size, $field_storage_unique_id = NULL) {
/** @var \Drupal\Core\Field\DeletedFieldsRepositoryInterface $deleted_fields_repository */
$deleted_fields_repository = \Drupal::service('entity_field.deleted_fields_repository');
$fields = $deleted_fields_repository->getFieldDefinitions($field_storage_unique_id);
foreach ($fields as $field) {
$entity_type = $field->getTargetEntityTypeId();
$count_purged = \Drupal::entityTypeManager()->getStorage($entity_type)->purgeFieldData($field, $batch_size);
if ($count_purged < $batch_size || $count_purged == 0) {
// No field data remains for the field, so we can remove it.
field_purge_field($field);
}
$batch_size -= $count_purged;
// Only delete up to the maximum number of records.
if ($batch_size == 0) {
break;
}
}
// Retrieve all deleted field storages. Any that have no fields can be purged.
foreach ($deleted_fields_repository->getFieldStorageDefinitions() as $field_storage) {
if ($field_storage_unique_id && $field_storage->getUniqueStorageIdentifier() != $field_storage_unique_id) {
// If a specific UUID is provided, only purge the corresponding field.
continue;
}
$fields = $deleted_fields_repository->getFieldDefinitions($field_storage->getUniqueStorageIdentifier());
if (empty($fields)) {
field_purge_field_storage($field_storage);
}
}
}
/**
* Purges a field record from the database.
*
* This function assumes all data for the field has already been purged and
* should only be called by field_purge_batch().
*
* @param \Drupal\Core\Field\FieldDefinitionInterface $field
* The field to purge.
*/
function field_purge_field(FieldDefinitionInterface $field) {
/** @var \Drupal\Core\Field\DeletedFieldsRepositoryInterface $deleted_fields_repository */
$deleted_fields_repository = \Drupal::service('entity_field.deleted_fields_repository');
$deleted_fields_repository->removeFieldDefinition($field);
// Invoke external hooks after the cache is cleared for API consistency.
\Drupal::moduleHandler()->invokeAll('field_purge_field', [$field]);
}
/**
* Purges a field record from the database.
*
* This function assumes all fields for the field storage has already been
* purged, and should only be called by field_purge_batch().
*
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $field_storage
* The field storage to purge.
*
* @throws \Drupal\Core\Field\FieldException
*/
function field_purge_field_storage(FieldStorageDefinitionInterface $field_storage) {
/** @var \Drupal\Core\Field\DeletedFieldsRepositoryInterface $deleted_fields_repository */
$deleted_fields_repository = \Drupal::service('entity_field.deleted_fields_repository');
$fields = $deleted_fields_repository->getFieldDefinitions($field_storage->getUniqueStorageIdentifier());
if (count($fields) > 0) {
throw new FieldException("Attempt to purge a field storage '{$field_storage->getName()}' that still has fields.");
}
$deleted_fields_repository->removeFieldStorageDefinition($field_storage);
// Notify the storage layer.
\Drupal::entityTypeManager()->getStorage($field_storage->getTargetEntityTypeId())->finalizePurge($field_storage);
// Invoke external hooks after the cache is cleared for API consistency.
\Drupal::moduleHandler()->invokeAll('field_purge_field_storage', [$field_storage]);
}
/**
* @} End of "defgroup field_purge".
*/

View File

@@ -0,0 +1,7 @@
services:
field.uninstall_validator:
class: Drupal\field\FieldUninstallValidator
tags:
- { name: module_install.uninstall_validator }
arguments: ['@entity_type.manager', '@string_translation', '@plugin.manager.field.field_type']
lazy: true

View File

@@ -0,0 +1,131 @@
# cspell:ignore filefield imagefield optionwidgets nodereference onoff
# cspell:ignore userreference
id: d6_field
label: Field configuration
migration_tags:
- Drupal 6
- Configuration
class: Drupal\migrate_drupal\Plugin\migrate\FieldMigration
field_plugin_method: alterFieldMigration
source:
plugin: d6_field
constants:
entity_type: node
langcode: en
process:
entity_type: 'constants/entity_type'
status: active
langcode: 'constants/langcode'
field_name: field_name
type:
plugin: field_type
source:
- type
- widget_type
map:
userreference:
userreference_select: entity_reference
userreference_buttons: entity_reference
userreference_autocomplete: entity_reference
nodereference:
nodereference_select: entity_reference
nodereference_url: entity_reference
number_integer:
number: integer
optionwidgets_select: list_integer
optionwidgets_buttons: list_integer
optionwidgets_onoff: boolean
number_decimal:
number: decimal
optionwidgets_select: list_float
optionwidgets_buttons: list_float
optionwidgets_onoff: boolean
number_float:
number: float
optionwidgets_select: list_float
optionwidgets_buttons: list_float
optionwidgets_onoff: boolean
email:
email_textfield: email
filefield:
imagefield_widget: image
filefield_widget: file
fr_phone:
phone_textfield: telephone
be_phone:
phone_textfield: telephone
it_phone:
phone_textfield: telephone
el_phone:
phone_textfield: telephone
ch_phone:
phone_textfield: telephone
ca_phone:
phone_textfield: telephone
cr_phone:
phone_textfield: telephone
pa_phone:
phone_textfield: telephone
gb_phone:
phone_textfield: telephone
ru_phone:
phone_textfield: telephone
ua_phone:
phone_textfield: telephone
es_phone:
phone_textfield: telephone
au_phone:
phone_textfield: telephone
cs_phone:
phone_textfield: telephone
hu_phone:
phone_textfield: telephone
pl_phone:
phone_textfield: telephone
nl_phone:
phone_textfield: telephone
se_phone:
phone_textfield: telephone
za_phone:
phone_textfield: telephone
il_phone:
phone_textfield: telephone
nz_phone:
phone_textfield: telephone
br_phone:
phone_textfield: telephone
cl_phone:
phone_textfield: telephone
cn_phone:
phone_textfield: telephone
hk_phone:
phone_textfield: telephone
mo_phone:
phone_textfield: telephone
ph_phone:
phone_textfield: telephone
sg_phone:
phone_textfield: telephone
jo_phone:
phone_textfield: telephone
eg_phone:
phone_textfield: telephone
pk_phone:
phone_textfield: telephone
int_phone:
phone_textfield: telephone
cardinality:
plugin: static_map
bypass: true
source: multiple
map:
0: 1
1: -1
settings:
plugin: field_settings
source:
- '@type'
- global_settings
- type
destination:
plugin: entity:field_storage_config

View File

@@ -0,0 +1,287 @@
# cspell:ignore filefield imagelink nodelink nodereference spamspan
# cspell:ignore userreference
id: d6_field_formatter_settings
label: Field formatter configuration
migration_tags:
- Drupal 6
- Configuration
class: Drupal\migrate_drupal\Plugin\migrate\FieldMigration
field_plugin_method: alterFieldFormatterMigration
source:
plugin: d6_field_instance_per_view_mode
constants:
entity_type: node
third_party_settings: { }
process:
# We skip field types that don't exist because they weren't migrated by the
# field migration.
field_type_exists:
-
plugin: migration_lookup
migration: d6_field
source:
- field_name
-
plugin: skip_on_empty
method: row
-
plugin: extract
index:
- 1
entity_type: 'constants/entity_type'
bundle:
-
plugin: migration_lookup
migration: d6_node_type
source: type_name
-
plugin: skip_on_empty
method: row
view_mode:
-
plugin: migration_lookup
migration: d6_view_modes
source:
- view_mode
-
plugin: skip_on_empty
method: row
-
plugin: extract
index:
- 1
-
plugin: static_map
bypass: true
map:
full: default
field_name: field_name
"options/label": label
"options/weight": weight
"options/type":
-
plugin: static_map
bypass: true
source:
- type
- 'display_settings/format'
map:
number_integer:
default: number_integer
us_0: number_integer
be_0: number_integer
fr_0: number_integer
unformatted: number_unformatted
number_float:
default: number_decimal
us_0: number_decimal
us_1: number_decimal
us_2: number_decimal
be_0: number_decimal
be_1: number_decimal
be_2: number_decimal
fr_0: number_decimal
fr_1: number_decimal
fr_2: number_decimal
unformatted: number_unformatted
number_decimal:
default: number_decimal
us_0: number_decimal
us_1: number_decimal
us_2: number_decimal
be_0: number_decimal
be_1: number_decimal
be_2: number_decimal
fr_0: number_decimal
fr_1: number_decimal
fr_2: number_decimal
unformatted: number_unformatted
email:
default: email_mailto
spamspan: email_mailto
contact: email_mailto
plain: basic_string
fr_phone:
default: basic_string
be_phone:
default: basic_string
it_phone:
default: basic_string
el_phone:
default: basic_string
ch_phone:
default: basic_string
ca_phone:
default: basic_string
cr_phone:
default: basic_string
pa_phone:
default: basic_string
gb_phone:
default: basic_string
ru_phone:
default: basic_string
ua_phone:
default: basic_string
es_phone:
default: basic_string
au_phone:
default: basic_string
cs_phone:
default: basic_string
hu_phone:
default: basic_string
pl_phone:
default: basic_string
nl_phone:
default: basic_string
se_phone:
default: basic_string
za_phone:
default: basic_string
il_phone:
default: basic_string
nz_phone:
default: basic_string
br_phone:
default: basic_string
cl_phone:
default: basic_string
cn_phone:
default: basic_string
hk_phone:
default: basic_string
mo_phone:
default: basic_string
ph_phone:
default: basic_string
sg_phone:
default: basic_string
jo_phone:
default: basic_string
eg_phone:
default: basic_string
pk_phone:
default: basic_string
int_phone:
default: basic_string
nodereference:
default: entity_reference_label
plain: entity_reference_label
full: entity_reference_entity_view
teaser: entity_reference_entity_view
userreference:
default: entity_reference_label
plain: entity_reference_label
-
plugin: d6_field_type_defaults
"options/settings":
-
plugin: static_map
bypass: true
source:
- module
- 'display_settings/format'
map:
nodereference:
default: { }
plain:
link: false
full:
view_mode: full
teaser:
view_mode: teaser
userreference:
default: { }
plain:
link: false
link:
default:
trim_length: '80'
url_only: 0
url_plain: 0
rel: 0
target: 0
plain:
trim_length: '80'
url_only: 1
url_plain: 1
rel: 0
target: 0
absolute:
trim_length: '80'
url_only: 1
url_plain: 1
rel: 0
target: 0
title_plain: #can't support title as plain text.
trim_length: '80'
url_only: 1
url_plain: 1
rel: 0
target: 0
url:
trim_length: '80'
url_only: 1
url_plain: 0
rel: 0
target: 0
short: #can't support hardcoded link text?
trim_length: '80'
url_only: 0
url_plain: 0
rel: 0
target: 0
label: # can't support label as link text?
trim_length: '80'
url_only: 0
url_plain: 0
rel: 0
target: 0
separate:
trim_length: '80'
rel: 0
target: 0
filefield:
image_plain:
image_style: ''
image_link: ''
image_nodelink:
image_style: ''
image_link: content
image_imagelink:
image_style: ''
image_link: file
date:
default:
format_type: fallback
timezone_override: ''
format_interval:
format_type: fallback
timezone_override: ''
long:
format_type: long
timezone_override: ''
medium:
format_type: medium
timezone_override: ''
short:
format_type: short
timezone_override: ''
text:
trimmed:
trim_length: 600
string:
default:
link_to_entity: false
-
plugin: field_formatter_settings_defaults
"options/third_party_settings": 'constants/third_party_settings'
destination:
plugin: component_entity_display
migration_dependencies:
required:
- d6_field_instance
- d6_view_modes

View File

@@ -0,0 +1,62 @@
id: d6_field_instance
label: Field instance configuration
migration_tags:
- Drupal 6
- Configuration
class: Drupal\migrate_drupal\Plugin\migrate\FieldMigration
field_plugin_method: alterFieldInstanceMigration
source:
plugin: d6_field_instance
constants:
entity_type: node
process:
# We skip field types that don't exist because they weren't migrated by the
# field migration.
field_type_exists:
-
plugin: migration_lookup
migration: d6_field
source:
- field_name
-
plugin: extract
index:
- 1
-
plugin: skip_on_empty
method: row
entity_type: 'constants/entity_type'
field_name: field_name
bundle:
-
plugin: migration_lookup
migration: d6_node_type
source: type_name
-
plugin: skip_on_empty
method: row
label: label
description: description
required: required
status: active
settings:
plugin: d6_field_field_settings
source:
- widget_type
- widget_settings
- global_settings
default_value_callback: ''
default_value:
plugin: d6_field_instance_defaults
source:
- widget_type
- widget_settings
translatable: translatable
destination:
plugin: entity:field_config
migration_dependencies:
required:
- d6_node_type
- d6_field

View File

@@ -0,0 +1,77 @@
# cspell:ignore imagefield optionwidgets nodereference onoff userreference
id: d6_field_instance_widget_settings
label: Field instance widget configuration
migration_tags:
- Drupal 6
- Configuration
class: Drupal\migrate_drupal\Plugin\migrate\FieldMigration
field_plugin_method: alterFieldWidgetMigration
source:
plugin: d6_field_instance_per_form_display
constants:
entity_type: node
form_mode: default
third_party_settings: { }
process:
# We skip field types that don't exist because they weren't migrated by the
# field migration.
field_type_exists:
-
plugin: migration_lookup
migration: d6_field
source:
- field_name
-
plugin: extract
index:
- 1
-
plugin: skip_on_empty
method: row
bundle:
-
plugin: migration_lookup
migration: d6_node_type
source: type_name
-
plugin: skip_on_empty
method: row
form_mode: 'constants/form_mode'
field_name: field_name
entity_type: 'constants/entity_type'
'options/weight': weight
'options/type':
type:
plugin: static_map
bypass: true
source: widget_type
map:
number: number
email_textfield: email_default
date_select: datetime_default
date_text: datetime_default
date_popup: datetime_default
imagefield_widget: image_image
phone_textfield: telephone_default
optionwidgets_onoff: boolean_checkbox
optionwidgets_buttons: options_buttons
optionwidgets_select: options_select
nodereference_select: options_select
nodereference_url: entity_reference_autocomplete
nodereference_buttons: options_buttons
nodereference_autocomplete: entity_reference_autocomplete_tags
userreference_select: options_select
'options/settings':
-
plugin: field_instance_widget_settings
source:
- widget_type
- widget_settings
'options/third_party_settings': 'constants/third_party_settings'
destination:
plugin: component_entity_form_display
migration_dependencies:
required:
- d6_field_instance

View File

@@ -0,0 +1,30 @@
id: d7_field
label: Field configuration
migration_tags:
- Drupal 7
- Configuration
class: Drupal\migrate_drupal\Plugin\migrate\FieldMigration
field_plugin_method: alterFieldMigration
source:
plugin: d7_field
constants:
status: true
langcode: und
process:
entity_type: entity_type
status: 'constants/status'
langcode: 'constants/langcode'
field_name: field_name
type:
plugin: process_field
source: type
method: getFieldType
# Translatable is not migrated and the Drupal 8 default of true is used.
# If translatable is false in field storage then the field can not be
# set to translatable via the UI.
#translatable: translatable
cardinality: cardinality
settings:
plugin: d7_field_settings
destination:
plugin: entity:field_storage_config

View File

@@ -0,0 +1,105 @@
id: d7_field_formatter_settings
label: Field formatter configuration
migration_tags:
- Drupal 7
- Configuration
class: Drupal\migrate_drupal\Plugin\migrate\FieldMigration
field_plugin_method: alterFieldFormatterMigration
source:
plugin: d7_field_instance_per_view_mode
constants:
third_party_settings: { }
process:
# We skip field types that don't exist because they weren't migrated by the
# field migration.
field_type_exists:
-
plugin: migration_lookup
migration: d7_field
source:
- field_name
- entity_type
-
plugin: extract
index:
- 0
-
plugin: skip_on_empty
method: row
entity_type: entity_type
# The bundle needs to be statically mapped in order to support comment types
# that might already exist before this migration is run. See
# d7_comment_type.yml for more information.
bundle:
-
plugin: migration_lookup
migration: d7_field_instance
source:
- entity_type
- bundle
- field_name
-
plugin: extract
index:
- 1
view_mode:
-
plugin: migration_lookup
migration: d7_view_modes
source:
- entity_type
- view_mode
-
plugin: extract
index:
- 1
-
plugin: static_map
bypass: true
map:
full: default
field_name: field_name
"options/label": 'formatter/label'
"options/weight": 'formatter/weight'
# The field plugin ID.
plugin_id:
plugin: process_field
source: type
method: getPluginId
# The formatter to use.
formatter_type:
plugin: process_field
source: type
method: getFieldFormatterType
"options/type":
-
plugin: static_map
bypass: true
source:
- '@plugin_id'
- '@formatter_type'
# The map is generated by the getFieldFormatterMap() method from the
# migrate field plugins.
map: []
-
plugin: d7_field_type_defaults
-
plugin: skip_on_empty
method: row
hidden:
plugin: static_map
source: "@options/type"
map:
hidden: true
default_value: false
"options/settings":
plugin: default_value
source: 'formatter/settings'
default_value: []
"options/third_party_settings": 'constants/third_party_settings'
destination:
plugin: component_entity_display
migration_dependencies:
required:
- d7_field_instance
- d7_view_modes

View File

@@ -0,0 +1,84 @@
id: d7_field_instance
label: Field instance configuration
migration_tags:
- Drupal 7
- Configuration
class: Drupal\migrate_drupal\Plugin\migrate\FieldMigration
field_plugin_method: alterFieldInstanceMigration
source:
plugin: d7_field_instance
constants:
status: true
comment_node: comment_node_
process:
type:
plugin: process_field
source: type
method: getFieldType
entity_type: entity_type
field_name: field_name
# The bundle needs to be statically mapped in order to support comment types
# that might already exist before this migration is run. See
# d7_comment_type.yml for more information.
bundle_mapped:
plugin: static_map
source: bundle
bypass: true
map:
comment_node_forum: comment_forum
_comment_type:
-
plugin: explode
source: bundle
delimiter: comment_node_
-
plugin: extract
index: [1]
default: false
-
plugin: skip_on_empty
method: process
-
plugin: migration_lookup
migration:
- d7_comment_type
bundle:
plugin: field_bundle
source:
- entity_type
- '@bundle_mapped'
label: label
description: description
required: required
status: 'constants/status'
allowed_values:
-
plugin: sub_process
source: allowed_vid
process:
-
plugin: migration_lookup
migration: d7_taxonomy_vocabulary
source: vid
settings:
plugin: d7_field_instance_settings
source:
- settings
- widget
- field_definition
default_value_function: ''
default_value:
plugin: d7_field_instance_defaults
source:
- default_value
- widget
translatable: translatable
destination:
plugin: entity:field_config
migration_dependencies:
required:
- d7_field
optional:
- d7_node_type
- d7_comment_type
- d7_taxonomy_vocabulary

View File

@@ -0,0 +1,81 @@
# cspell:ignore entityreference onoff
id: d7_field_instance_widget_settings
label: Field instance widget configuration
migration_tags:
- Drupal 7
- Configuration
class: Drupal\migrate_drupal\Plugin\migrate\FieldMigration
field_plugin_method: alterFieldWidgetMigration
source:
plugin: d7_field_instance_per_form_display
constants:
form_mode: default
third_party_settings: { }
process:
# We skip field types that don't exist because they weren't migrated by the
# field migration.
field_type_exists:
-
plugin: migration_lookup
migration: d7_field
source:
- field_name
- entity_type
-
plugin: extract
index:
- 0
-
plugin: skip_on_empty
method: row
# The bundle needs to be statically mapped in order to support comment types
# that might already exist before this migration is run. See
# d7_comment_type.yml for more information.
bundle:
-
plugin: migration_lookup
migration: d7_field_instance
source:
- entity_type
- bundle
- field_name
-
plugin: extract
index:
- 1
form_mode: 'constants/form_mode'
field_name: field_name
entity_type: entity_type
'options/weight': 'widget/weight'
widget_type:
plugin: process_field
source: type
method: getFieldWidgetType
'options/type':
type:
plugin: static_map
bypass: true
source: '@widget_type'
map:
link_field: link_default
email_textfield: email_default
date_select: datetime_default
date_text: datetime_default
date_popup: datetime_default
media_generic: file_generic
phone_textfield: telephone_default
options_onoff: boolean_checkbox
entityreference_autocomplete: entity_reference_autocomplete
entityreference_autocomplete_tags: entity_reference_autocomplete_tags
taxonomy_autocomplete: entity_reference_autocomplete
'options/settings':
plugin: field_instance_widget_settings
source:
- 'widget/type'
- 'widget/settings'
'options/third_party_settings': 'constants/third_party_settings'
destination:
plugin: component_entity_form_display
migration_dependencies:
required:
- d7_field_instance

View File

@@ -0,0 +1,29 @@
id: d7_view_modes
label: View modes
migration_tags:
- Drupal 7
- Configuration
source:
plugin: d7_view_mode
process:
mode:
plugin: static_map
source: view_mode
bypass: true
map:
default: full
label:
plugin: static_map
source: view_mode
bypass: true
map:
search_index: "Search index"
search_result: "Search result"
rss: "RSS"
print: "Print"
teaser: "Teaser"
full: "Full"
default: "Full"
targetEntityType: entity_type
destination:
plugin: entity:entity_view_mode

View File

@@ -0,0 +1,17 @@
# cspell:ignore entityreference nodereference userreference
finished:
6:
nodereference: core
nodereference_url: core
userreference: core
content: field
email: core
# Phone does not use a migrate field plugin so it is added here.
phone: field
7:
email: core
entityreference: core
field: field
field_sql_storage: field
i18n_sync: field
number: core

View File

@@ -0,0 +1,146 @@
<?php
namespace Drupal\field;
use Drupal\Core\Config\ConfigImporter;
use Drupal\Core\Config\Entity\ConfigEntityStorage;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Processes field purges before a configuration synchronization.
*/
class ConfigImporterFieldPurger {
/**
* Processes fields targeted for purge as part of a configuration sync.
*
* This takes care of deleting the field if necessary, and purging the data on
* the fly.
*
* @param array $context
* The batch context.
* @param \Drupal\Core\Config\ConfigImporter $config_importer
* The config importer.
*/
public static function process(array &$context, ConfigImporter $config_importer) {
if (!isset($context['sandbox']['field'])) {
static::initializeSandbox($context, $config_importer);
}
// Get the list of field storages to purge.
$field_storages = static::getFieldStoragesToPurge($context['sandbox']['field']['extensions'], $config_importer->getUnprocessedConfiguration('delete'));
// Get the first field storage to process.
$field_storage = reset($field_storages);
if (!isset($context['sandbox']['field']['current_storage_id']) || $context['sandbox']['field']['current_storage_id'] != $field_storage->id()) {
$context['sandbox']['field']['current_storage_id'] = $field_storage->id();
// If the storage has not been deleted yet we need to do that. This is the
// case when the storage deletion is staged.
if (!$field_storage->isDeleted()) {
$field_storage->delete();
}
}
field_purge_batch($context['sandbox']['field']['purge_batch_size'], $field_storage->getUniqueStorageIdentifier());
$context['sandbox']['field']['current_progress']++;
$fields_to_delete_count = count(static::getFieldStoragesToPurge($context['sandbox']['field']['extensions'], $config_importer->getUnprocessedConfiguration('delete')));
if ($fields_to_delete_count == 0) {
$context['finished'] = 1;
}
else {
$context['finished'] = $context['sandbox']['field']['current_progress'] / $context['sandbox']['field']['steps_to_delete'];
$context['message'] = \Drupal::translation()->translate('Purging field @field_label', ['@field_label' => $field_storage->label()]);
}
}
/**
* Initializes the batch context sandbox for processing field deletions.
*
* This calculates the number of steps necessary to purge all the field data
* and saves data for later use.
*
* @param array $context
* The batch context.
* @param \Drupal\Core\Config\ConfigImporter $config_importer
* The config importer.
*/
protected static function initializeSandbox(array &$context, ConfigImporter $config_importer) {
$context['sandbox']['field']['purge_batch_size'] = \Drupal::config('field.settings')->get('purge_batch_size');
// Save the future list of installed extensions to limit the amount of times
// the configuration is read from disk.
$context['sandbox']['field']['extensions'] = $config_importer->getStorageComparer()->getSourceStorage()->read('core.extension');
$context['sandbox']['field']['steps_to_delete'] = 0;
$fields = static::getFieldStoragesToPurge($context['sandbox']['field']['extensions'], $config_importer->getUnprocessedConfiguration('delete'));
foreach ($fields as $field) {
$row_count = \Drupal::entityTypeManager()->getStorage($field->getTargetEntityTypeId())
->countFieldData($field);
if ($row_count > 0) {
// The number of steps to delete each field is determined by the
// purge_batch_size setting. For example if the field has 9 rows and the
// batch size is 10 then this will add 1 step to $number_of_steps.
$how_many_steps = ceil($row_count / $context['sandbox']['field']['purge_batch_size']);
$context['sandbox']['field']['steps_to_delete'] += $how_many_steps;
}
}
// Each field possibly needs one last field_purge_batch() call to remove the
// last field and the field storage itself.
$context['sandbox']['field']['steps_to_delete'] += count($fields);
$context['sandbox']['field']['current_progress'] = 0;
}
/**
* Gets the list of fields to purge before configuration synchronization.
*
* If, during a configuration synchronization, a field is being deleted and
* the module that provides the field type is being uninstalled then the field
* data must be purged before the module is uninstalled. Also, if deleted
* fields exist whose field types are provided by modules that are being
* uninstalled their data need to be purged too.
*
* @param array $extensions
* The list of extensions that will be enabled after the configuration
* synchronization has finished.
* @param array $deletes
* The configuration that will be deleted by the configuration
* synchronization.
*
* @return \Drupal\field\Entity\FieldStorageConfig[]
* An array of field storages that need purging before configuration can be
* synchronized.
*/
public static function getFieldStoragesToPurge(array $extensions, array $deletes) {
$providers = array_keys($extensions['module']);
$providers[] = 'core';
$storages_to_delete = [];
// Gather fields that will be deleted during configuration synchronization
// where the module that provides the field type is also being uninstalled.
$field_storage_ids = [];
foreach ($deletes as $config_name) {
$field_storage_config_prefix = \Drupal::entityTypeManager()->getDefinition('field_storage_config')->getConfigPrefix();
if (str_starts_with($config_name, $field_storage_config_prefix . '.')) {
$field_storage_ids[] = ConfigEntityStorage::getIDFromConfigName($config_name, $field_storage_config_prefix);
}
}
if (!empty($field_storage_ids)) {
$field_storages = \Drupal::entityQuery('field_storage_config')
->condition('id', $field_storage_ids, 'IN')
->condition('module', $providers, 'NOT IN')
->execute();
if (!empty($field_storages)) {
$storages_to_delete = FieldStorageConfig::loadMultiple($field_storages);
}
}
// Gather deleted fields from modules that are being uninstalled.
/** @var \Drupal\field\FieldStorageConfigInterface[] $deleted_storage_definitions */
$deleted_storage_definitions = \Drupal::service('entity_field.deleted_fields_repository')->getFieldStorageDefinitions();
foreach ($deleted_storage_definitions as $field_storage_definition) {
if ($field_storage_definition instanceof FieldStorageConfigInterface && !in_array($field_storage_definition->getTypeProvider(), $providers)) {
$storages_to_delete[$field_storage_definition->id()] = $field_storage_definition;
}
}
return $storages_to_delete;
}
}

View File

@@ -0,0 +1,381 @@
<?php
namespace Drupal\field\Entity;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\FieldableEntityStorageInterface;
use Drupal\Core\Field\FieldConfigBase;
use Drupal\Core\Field\FieldException;
use Drupal\field\FieldStorageConfigInterface;
use Drupal\field\FieldConfigInterface;
/**
* Defines the Field entity.
*
* @ConfigEntityType(
* id = "field_config",
* label = @Translation("Field"),
* label_collection = @Translation("Fields"),
* label_singular = @Translation("field"),
* label_plural = @Translation("fields"),
* label_count = @PluralTranslation(
* singular = "@count field",
* plural = "@count fields",
* ),
* handlers = {
* "access" = "Drupal\field\FieldConfigAccessControlHandler",
* "storage" = "Drupal\field\FieldConfigStorage"
* },
* config_prefix = "field",
* entity_keys = {
* "id" = "id",
* "label" = "label"
* },
* config_export = {
* "id",
* "field_name",
* "entity_type",
* "bundle",
* "label",
* "description",
* "required",
* "translatable",
* "default_value",
* "default_value_callback",
* "settings",
* "field_type",
* },
* constraints = {
* "RequiredConfigDependencies" = {
* "field_storage_config"
* },
* "ImmutableProperties" = {"id", "entity_type", "field_name", "bundle", "field_type"},
* }
* )
*/
class FieldConfig extends FieldConfigBase implements FieldConfigInterface {
/**
* Flag indicating whether the field is deleted.
*
* The delete() method marks the field as "deleted" and removes the
* corresponding entry from the config storage, but keeps its definition in
* the state storage while field data is purged by a separate
* garbage-collection process.
*
* Deleted fields stay out of the regular entity lifecycle (notably, their
* values are not populated in loaded entities, and are not saved back).
*
* @var bool
*/
protected $deleted = FALSE;
/**
* The associated FieldStorageConfig entity.
*
* @var \Drupal\field\Entity\FieldStorageConfig
*/
protected $fieldStorage;
/**
* The original FieldConfig entity.
*/
public FieldConfig $original;
/**
* Constructs a FieldConfig object.
*
* In most cases, Field entities are created via
* FieldConfig::create($values), where $values is the same
* parameter as in this constructor.
*
* @param array $values
* An array of field properties, keyed by property name. The
* storage associated with the field can be specified either with:
* - field_storage: the FieldStorageConfigInterface object,
* or by referring to an existing field storage in the current configuration
* with:
* - field_name: The field name.
* - entity_type: The entity type.
* Additionally, a 'bundle' property is required to indicate the entity
* bundle to which the field is attached to. Other array elements will be
* used to set the corresponding properties on the class; see the class
* property documentation for details.
* @param string $entity_type
* (optional) The entity type on which the field should be created.
* Defaults to "field_config".
*/
public function __construct(array $values, $entity_type = 'field_config') {
// Allow either an injected FieldStorageConfig object, or a field_name and
// entity_type.
if (isset($values['field_storage'])) {
if (!$values['field_storage'] instanceof FieldStorageConfigInterface) {
throw new FieldException('Attempt to create a configurable field for a non-configurable field storage.');
}
$field_storage = $values['field_storage'];
$values['field_name'] = $field_storage->getName();
$values['entity_type'] = $field_storage->getTargetEntityTypeId();
// The internal property is fieldStorage, not field_storage.
unset($values['field_storage']);
$values['fieldStorage'] = $field_storage;
}
else {
if (empty($values['field_name'])) {
throw new FieldException('Attempt to create a field without a field_name.');
}
if (empty($values['entity_type'])) {
throw new FieldException("Attempt to create a field '{$values['field_name']}' without an entity_type.");
}
}
// 'bundle' is required in either case.
if (empty($values['bundle'])) {
throw new FieldException("Attempt to create a field '{$values['field_name']}' without a bundle.");
}
parent::__construct($values, $entity_type);
}
/**
* {@inheritdoc}
*/
public function postCreate(EntityStorageInterface $storage) {
parent::postCreate($storage);
// Validate that we have a valid storage for this field. This throws an
// exception if the storage is invalid.
$this->getFieldStorageDefinition();
// 'Label' defaults to the field name (mostly useful for fields created in
// tests).
if (empty($this->label)) {
$this->label = $this->getName();
}
}
/**
* Overrides \Drupal\Core\Entity\Entity::preSave().
*
* @throws \Drupal\Core\Field\FieldException
* If the field definition is invalid.
* @throws \Drupal\Core\Entity\EntityStorageException
* In case of failures at the configuration storage level.
*/
public function preSave(EntityStorageInterface $storage) {
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
$storage_definition = $this->getFieldStorageDefinition();
// Filter out unknown settings and make sure all settings are present, so
// that a complete field definition is passed to the various hooks and
// written to config.
$default_settings = $field_type_manager->getDefaultFieldSettings($storage_definition->getType());
$this->settings = array_intersect_key($this->settings, $default_settings) + $default_settings;
if ($this->isNew()) {
// Notify the entity storage.
\Drupal::service('field_definition.listener')->onFieldDefinitionCreate($this);
}
else {
// Some updates are always disallowed.
if ($this->entity_type != $this->original->entity_type) {
throw new FieldException("Cannot change an existing field's entity_type.");
}
if ($this->bundle != $this->original->bundle) {
throw new FieldException("Cannot change an existing field's bundle.");
}
if ($storage_definition->uuid() != $this->original->getFieldStorageDefinition()->uuid()) {
throw new FieldException("Cannot change an existing field's storage.");
}
// Notify the entity storage.
\Drupal::service('field_definition.listener')->onFieldDefinitionUpdate($this, $this->original);
}
parent::preSave($storage);
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
parent::calculateDependencies();
// Mark the field_storage_config as a dependency.
$this->addDependency('config', $this->getFieldStorageDefinition()->getConfigDependencyName());
return $this;
}
/**
* {@inheritdoc}
*/
public static function preDelete(EntityStorageInterface $storage, array $fields) {
/** @var \Drupal\Core\Field\DeletedFieldsRepositoryInterface $deleted_fields_repository */
$deleted_fields_repository = \Drupal::service('entity_field.deleted_fields_repository');
$entity_type_manager = \Drupal::entityTypeManager();
parent::preDelete($storage, $fields);
// Keep the field definitions in the deleted fields repository so we can use
// them later during field_purge_batch().
/** @var \Drupal\field\FieldConfigInterface $field */
foreach ($fields as $field) {
// Only mark a field for purging if there is data. Otherwise, just remove
// it.
$target_entity_storage = $entity_type_manager->getStorage($field->getTargetEntityTypeId());
if (!$field->deleted && $target_entity_storage instanceof FieldableEntityStorageInterface && $target_entity_storage->countFieldData($field->getFieldStorageDefinition(), TRUE)) {
$field = clone $field;
$field->deleted = TRUE;
$field->fieldStorage = NULL;
$deleted_fields_repository->addFieldDefinition($field);
}
}
}
/**
* {@inheritdoc}
*/
public static function postDelete(EntityStorageInterface $storage, array $fields) {
parent::postDelete($storage, $fields);
// If this is part of a configuration synchronization then the following
// configuration updates are not necessary.
$entity = reset($fields);
if ($entity->isSyncing()) {
return;
}
// Delete the associated field storages if they are not used anymore and are
// not persistent.
$storages_to_delete = [];
foreach ($fields as $field) {
$storage_definition = $field->getFieldStorageDefinition();
if (!$field->deleted && !$field->isUninstalling() && $storage_definition->isDeletable()) {
// Key by field UUID to avoid deleting the same storage twice.
$storages_to_delete[$storage_definition->uuid()] = $storage_definition;
}
}
if ($storages_to_delete) {
\Drupal::entityTypeManager()->getStorage('field_storage_config')->delete($storages_to_delete);
}
}
/**
* {@inheritdoc}
*/
protected function linkTemplates() {
$link_templates = parent::linkTemplates();
if (\Drupal::moduleHandler()->moduleExists('field_ui')) {
$link_templates["{$this->entity_type}-field-edit-form"] = 'entity.field_config.' . $this->entity_type . '_field_edit_form';
$link_templates["{$this->entity_type}-field-delete-form"] = 'entity.field_config.' . $this->entity_type . '_field_delete_form';
if (isset($link_templates['config-translation-overview'])) {
$link_templates["config-translation-overview.{$this->entity_type}"] = "entity.field_config.config_translation_overview.{$this->entity_type}";
}
}
return $link_templates;
}
/**
* {@inheritdoc}
*/
protected function urlRouteParameters($rel) {
$parameters = parent::urlRouteParameters($rel);
$entity_type = \Drupal::entityTypeManager()->getDefinition($this->entity_type);
$bundle_parameter_key = $entity_type->getBundleEntityType() ?: 'bundle';
$parameters[$bundle_parameter_key] = $this->bundle;
return $parameters;
}
/**
* {@inheritdoc}
*/
public function isDeleted() {
return $this->deleted;
}
/**
* {@inheritdoc}
*/
public function getFieldStorageDefinition() {
if (!$this->fieldStorage) {
$field_storage_definition = NULL;
$field_storage_definitions = \Drupal::service('entity_field.manager')->getFieldStorageDefinitions($this->entity_type);
if (isset($field_storage_definitions[$this->field_name])) {
$field_storage_definition = $field_storage_definitions[$this->field_name];
}
// If this field has been deleted, try to find its field storage
// definition in the deleted fields repository.
elseif ($this->deleted) {
$deleted_storage_definitions = \Drupal::service('entity_field.deleted_fields_repository')->getFieldStorageDefinitions();
foreach ($deleted_storage_definitions as $deleted_storage_definition) {
if ($deleted_storage_definition->getName() === $this->field_name) {
$field_storage_definition = $deleted_storage_definition;
}
}
}
if (!$field_storage_definition) {
throw new FieldException("Attempted to create, modify or delete an instance of field with name {$this->field_name} on entity type {$this->entity_type} when the field storage does not exist.");
}
if (!$field_storage_definition instanceof FieldStorageConfigInterface) {
throw new FieldException("Attempted to create, modify or delete a configurable field of non-configurable field storage {$this->field_name}.");
}
$this->fieldStorage = $field_storage_definition;
}
return $this->fieldStorage;
}
/**
* {@inheritdoc}
*/
public function isDisplayConfigurable($context) {
return TRUE;
}
/**
* {@inheritdoc}
*/
public function getDisplayOptions($display_context) {
// Hide configurable fields by default.
return ['region' => 'hidden'];
}
/**
* {@inheritdoc}
*/
public function isReadOnly() {
return FALSE;
}
/**
* {@inheritdoc}
*/
public function isComputed() {
return FALSE;
}
/**
* {@inheritdoc}
*/
public function getUniqueIdentifier() {
return $this->uuid();
}
/**
* Loads a field config entity based on the entity type and field name.
*
* @param string $entity_type_id
* ID of the entity type.
* @param string $bundle
* Bundle name.
* @param string $field_name
* Name of the field.
*
* @return \Drupal\field\FieldConfigInterface|null
* The field config entity if one exists for the provided field
* name, otherwise NULL.
*/
public static function loadByName($entity_type_id, $bundle, $field_name) {
return \Drupal::entityTypeManager()->getStorage('field_config')->load($entity_type_id . '.' . $bundle . '.' . $field_name);
}
}

View File

@@ -0,0 +1,871 @@
<?php
namespace Drupal\field\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Entity\FieldableEntityStorageInterface;
use Drupal\Core\Entity\Plugin\DataType\EntityAdapter;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldException;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\OptionsProviderInterface;
use Drupal\field\FieldStorageConfigInterface;
/**
* Defines the Field storage configuration entity.
*
* @ConfigEntityType(
* id = "field_storage_config",
* label = @Translation("Field storage"),
* label_collection = @Translation("Field storages"),
* label_singular = @Translation("field storage"),
* label_plural = @Translation("field storages"),
* label_count = @PluralTranslation(
* singular = "@count field storage",
* plural = "@count field storages",
* ),
* handlers = {
* "access" = "Drupal\field\FieldStorageConfigAccessControlHandler",
* "storage" = "Drupal\field\FieldStorageConfigStorage"
* },
* config_prefix = "storage",
* entity_keys = {
* "id" = "id",
* "label" = "id"
* },
* config_export = {
* "id",
* "field_name",
* "entity_type",
* "type",
* "settings",
* "module",
* "locked",
* "cardinality",
* "translatable",
* "indexes",
* "persist_with_no_fields",
* "custom_storage",
* },
* constraints = {
* "ImmutableProperties" = {"id", "entity_type", "field_name", "type"},
* }
* )
*/
class FieldStorageConfig extends ConfigEntityBase implements FieldStorageConfigInterface {
/**
* The maximum length of the field name, in characters.
*
* For fields created through Field UI, this includes the 'field_' prefix.
*/
const NAME_MAX_LENGTH = 32;
/**
* The field ID.
*
* The ID consists of 2 parts: the entity type and the field name.
*
* Example: node.body, user.field_main_image.
*
* @var string
*/
protected $id;
/**
* The field name.
*
* This is the name of the property under which the field values are placed in
* an entity: $entity->{$field_name}. The maximum length is
* Field:NAME_MAX_LENGTH.
*
* Example: body, field_main_image.
*
* @var string
*/
protected $field_name;
/**
* The name of the entity type the field can be attached to.
*
* @var string
*/
protected $entity_type;
/**
* The field type.
*
* Example: text, integer.
*
* @var string
*/
protected $type;
/**
* The name of the module that provides the field type.
*
* @var string
*/
protected $module;
/**
* Field-type specific settings.
*
* An array of key/value pairs, The keys and default values are defined by the
* field type.
*
* @var array
*/
protected $settings = [];
/**
* The field cardinality.
*
* The maximum number of values the field can hold. Possible values are
* positive integers or
* FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED. Defaults to 1.
*
* @var int
*/
protected $cardinality = 1;
/**
* Flag indicating whether the field is translatable.
*
* Defaults to TRUE.
*
* @var bool
*/
protected $translatable = TRUE;
/**
* Flag indicating whether the field is available for editing.
*
* If TRUE, some actions not available though the UI (but are still possible
* through direct API manipulation):
* - field settings cannot be changed,
* - new fields cannot be created
* - existing fields cannot be deleted.
* Defaults to FALSE.
*
* @var bool
*/
protected $locked = FALSE;
/**
* Flag indicating whether the field storage should be deleted when orphaned.
*
* By default field storages for configurable fields are removed when there
* are no remaining fields using them. If multiple modules provide bundles
* which need to use the same field storage then setting this to TRUE will
* preserve the field storage regardless of what happens to the bundles. The
* classic use case for this is node body field storage, since the Standard
* profile and bundle (node type) creation through the UI both use same field
* storage.
*
* @var bool
*/
protected $persist_with_no_fields = FALSE;
/**
* A boolean indicating whether or not the field item uses custom storage.
*
* @var bool
*/
public $custom_storage = FALSE;
/**
* The custom storage indexes for the field data storage.
*
* This set of indexes is merged with the "default" indexes specified by the
* field type in the class implementing
* \Drupal\Core\Field\FieldItemInterface::schema() method to determine the
* actual set of indexes that get created.
*
* The indexes are defined using the same definition format as Schema API
* index specifications. Only columns that are part of the field schema, as
* defined by the field type in the class implementing
* \Drupal\Core\Field\FieldItemInterface::schema() method, are allowed.
*
* Some storage backends might not support indexes, and discard that
* information.
*
* @var array
*/
protected $indexes = [];
/**
* Flag indicating whether the field is deleted.
*
* The delete() method marks the field as "deleted" and removes the
* corresponding entry from the config storage, but keeps its definition in
* the state storage while field data is purged by a separate
* garbage-collection process.
*
* Deleted fields stay out of the regular entity lifecycle (notably, their
* values are not populated in loaded entities, and are not saved back).
*
* @var bool
*/
protected $deleted = FALSE;
/**
* The field schema.
*
* @var array
*/
protected $schema;
/**
* An array of field property definitions.
*
* @var \Drupal\Core\TypedData\DataDefinitionInterface[]
*
* @see \Drupal\Core\TypedData\ComplexDataDefinitionInterface::getPropertyDefinitions()
*/
protected $propertyDefinitions;
/**
* Static flag set to prevent recursion during field deletes.
*
* @var bool
*/
protected static $inDeletion = FALSE;
/**
* Copy of the field before changes.
*/
public FieldStorageConfigInterface $original;
/**
* Constructs a FieldStorageConfig object.
*
* In most cases, Field entities are created via
* FieldStorageConfig::create($values)), where $values is the same parameter
* as in this constructor.
*
* @param array $values
* An array of field properties, keyed by property name. Most array
* elements will be used to set the corresponding properties on the class;
* see the class property documentation for details. Some array elements
* have special meanings and a few are required. Special elements are:
* - name: required. As a temporary Backwards Compatibility layer right now,
* a 'field_name' property can be accepted in place of 'id'.
* - entity_type: required.
* - type: required.
* @param string $entity_type
* (optional) The entity type on which the field should be created.
* Defaults to "field_storage_config".
*/
public function __construct(array $values, $entity_type = 'field_storage_config') {
// Check required properties.
if (empty($values['field_name'])) {
throw new FieldException('Attempt to create a field storage without a field name.');
}
if (!preg_match('/^[_a-z]+[_a-z0-9]*$/', $values['field_name'])) {
throw new FieldException("Attempt to create a field storage {$values['field_name']} with invalid characters. Only lowercase alphanumeric characters and underscores are allowed, and only lowercase letters and underscore are allowed as the first character");
}
if (empty($values['type'])) {
throw new FieldException("Attempt to create a field storage {$values['field_name']} with no type.");
}
if (empty($values['entity_type'])) {
throw new FieldException("Attempt to create a field storage {$values['field_name']} with no entity_type.");
}
parent::__construct($values, $entity_type);
}
/**
* {@inheritdoc}
*/
public function id() {
return $this->getTargetEntityTypeId() . '.' . $this->getName();
}
/**
* {@inheritdoc}
*/
public function postCreate(EntityStorageInterface $storage) {
parent::postCreate($storage);
// Check that the field type is known.
$field_type = \Drupal::service('plugin.manager.field.field_type')->getDefinition($this->type, FALSE);
if (!$field_type) {
throw new FieldException("Attempt to create a field storage of unknown type {$this->type}.");
}
$this->module = $field_type['provider'];
// Make sure all expected runtime settings are present.
$default_settings = \Drupal::service('plugin.manager.field.field_type')
->getDefaultStorageSettings($this->getType());
// Filter out any unknown (unsupported) settings.
$supported_settings = array_intersect_key($this->getSettings(), $default_settings);
$this->set('settings', $supported_settings + $default_settings);
}
/**
* Overrides \Drupal\Core\Entity\Entity::preSave().
*
* @throws \Drupal\Core\Field\FieldException
* If the field definition is invalid.
* @throws \Drupal\Core\Entity\EntityStorageException
* In case of failures at the configuration storage level.
*/
public function preSave(EntityStorageInterface $storage) {
// Clear the derived data about the field.
unset($this->schema);
// Filter out unknown settings and make sure all settings are present, so
// that a complete field definition is passed to the various hooks and
// written to config.
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
$default_settings = $field_type_manager->getDefaultStorageSettings($this->type);
$this->settings = array_intersect_key($this->settings, $default_settings) + $default_settings;
if ($this->isNew()) {
$this->preSaveNew($storage);
}
else {
$this->preSaveUpdated($storage);
}
parent::preSave($storage);
}
/**
* Prepares saving a new field definition.
*
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* The entity storage.
*
* @throws \Drupal\Core\Field\FieldException
* If the field definition is invalid.
*/
protected function preSaveNew(EntityStorageInterface $storage) {
$entity_field_manager = \Drupal::service('entity_field.manager');
// Assign the ID.
$this->id = $this->id();
// Field name cannot be longer than FieldStorageConfig::NAME_MAX_LENGTH
// characters. We use mb_strlen() because the DB layer assumes that column
// widths are given in characters rather than bytes.
if (mb_strlen($this->getName()) > static::NAME_MAX_LENGTH) {
throw new FieldException('Attempt to create a field storage with an name longer than ' . static::NAME_MAX_LENGTH . ' characters: ' . $this->getName());
}
// Disallow reserved field names.
$disallowed_field_names = array_keys($entity_field_manager->getBaseFieldDefinitions($this->getTargetEntityTypeId()));
if (in_array($this->getName(), $disallowed_field_names)) {
throw new FieldException("Attempt to create field storage {$this->getName()} which is reserved by entity type {$this->getTargetEntityTypeId()}.");
}
// Notify the field storage definition listener.
\Drupal::service('field_storage_definition.listener')->onFieldStorageDefinitionCreate($this);
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
parent::calculateDependencies();
// Ensure the field is dependent on the providing module.
$this->addDependency('module', $this->getTypeProvider());
// Ask the field type for any additional storage dependencies.
// @see \Drupal\Core\Field\FieldItemInterface::calculateStorageDependencies()
$definition = \Drupal::service('plugin.manager.field.field_type')->getDefinition($this->getType(), FALSE);
$this->addDependencies($definition['class']::calculateStorageDependencies($this));
// Ensure the field is dependent on the provider of the entity type.
$entity_type = \Drupal::entityTypeManager()->getDefinition($this->entity_type);
$this->addDependency('module', $entity_type->getProvider());
return $this;
}
/**
* Prepares saving an updated field definition.
*
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* The entity storage.
*/
protected function preSaveUpdated(EntityStorageInterface $storage) {
$module_handler = \Drupal::moduleHandler();
// Some updates are always disallowed.
if ($this->getType() != $this->original->getType()) {
throw new FieldException(sprintf('Cannot change the field type for an existing field storage. The field storage %s has the type %s.', $this->id(), $this->original->getType()));
}
if ($this->getTargetEntityTypeId() != $this->original->getTargetEntityTypeId()) {
throw new FieldException(sprintf('Cannot change the entity type for an existing field storage. The field storage %s has the type %s.', $this->id(), $this->original->getTargetEntityTypeId()));
}
// See if any module forbids the update by throwing an exception. This
// invokes hook_field_storage_config_update_forbid().
$module_handler->invokeAll('field_storage_config_update_forbid', [$this, $this->original]);
// Notify the field storage definition listener. A listener can reject the
// definition update as invalid by raising an exception, which stops
// execution before the definition is written to config.
\Drupal::service('field_storage_definition.listener')->onFieldStorageDefinitionUpdate($this, $this->original);
}
/**
* {@inheritdoc}
*/
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
if ($update) {
// Invalidate the render cache for all affected entities.
$entity_type_manager = \Drupal::entityTypeManager();
$entity_type = $this->getTargetEntityTypeId();
if ($entity_type_manager->hasHandler($entity_type, 'view_builder')) {
$entity_type_manager->getViewBuilder($entity_type)->resetCache();
}
}
}
/**
* {@inheritdoc}
*/
public static function preDelete(EntityStorageInterface $storage, array $field_storages) {
/** @var \Drupal\Core\Field\DeletedFieldsRepositoryInterface $deleted_fields_repository */
$deleted_fields_repository = \Drupal::service('entity_field.deleted_fields_repository');
// Set the static flag so that we don't delete field storages whilst
// deleting fields.
static::$inDeletion = TRUE;
// Delete or fix any configuration that is dependent, for example, fields.
parent::preDelete($storage, $field_storages);
// Keep the field storage definitions in the deleted fields repository so we
// can use them later during field_purge_batch().
/** @var \Drupal\field\FieldStorageConfigInterface $field_storage */
foreach ($field_storages as $field_storage) {
// Only mark a field for purging if there is data. Otherwise, just remove
// it.
$target_entity_storage = \Drupal::entityTypeManager()->getStorage($field_storage->getTargetEntityTypeId());
if (!$field_storage->deleted && $target_entity_storage instanceof FieldableEntityStorageInterface && $target_entity_storage->countFieldData($field_storage, TRUE)) {
$storage_definition = clone $field_storage;
$storage_definition->deleted = TRUE;
$deleted_fields_repository->addFieldStorageDefinition($storage_definition);
}
}
}
/**
* {@inheritdoc}
*/
public static function postDelete(EntityStorageInterface $storage, array $fields) {
// Notify the storage.
foreach ($fields as $field) {
if (!$field->deleted) {
\Drupal::service('field_storage_definition.listener')->onFieldStorageDefinitionDelete($field);
$field->deleted = TRUE;
}
}
// Unset static flag.
static::$inDeletion = FALSE;
}
/**
* {@inheritdoc}
*/
public function getSchema() {
if (!isset($this->schema)) {
// Get the schema from the field item class.
$class = $this->getFieldItemClass();
$schema = $class::schema($this);
// Fill in default values for optional entries.
$schema += [
'columns' => [],
'unique keys' => [],
'indexes' => [],
'foreign keys' => [],
];
// Merge custom indexes with those specified by the field type. Custom
// indexes prevail.
$schema['indexes'] = $this->indexes + $schema['indexes'];
$this->schema = $schema;
}
return $this->schema;
}
/**
* {@inheritdoc}
*/
public function hasCustomStorage() {
return $this->custom_storage;
}
/**
* {@inheritdoc}
*/
public function isBaseField() {
return FALSE;
}
/**
* {@inheritdoc}
*/
public function getColumns() {
$schema = $this->getSchema();
return $schema['columns'];
}
/**
* {@inheritdoc}
*/
public function getBundles() {
if (!$this->isDeleted()) {
$map = \Drupal::service('entity_field.manager')->getFieldMap();
if (isset($map[$this->getTargetEntityTypeId()][$this->getName()]['bundles'])) {
return $map[$this->getTargetEntityTypeId()][$this->getName()]['bundles'];
}
}
return [];
}
/**
* {@inheritdoc}
*/
public function getName() {
return $this->field_name;
}
/**
* {@inheritdoc}
*/
public function isDeleted() {
return $this->deleted;
}
/**
* {@inheritdoc}
*/
public function getTypeProvider() {
return $this->module;
}
/**
* {@inheritdoc}
*/
public function getType() {
return $this->type;
}
/**
* {@inheritdoc}
*/
public function getSettings() {
// @todo FieldTypePluginManager maintains its own static cache. However, do
// some CPU and memory profiling to see if it's worth statically caching
// $field_type_info, or the default field storage and field settings,
// within $this.
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
$settings = $field_type_manager->getDefaultStorageSettings($this->getType());
return $this->settings + $settings;
}
/**
* {@inheritdoc}
*/
public function getSetting($setting_name) {
// @todo See getSettings() about potentially statically caching this.
// We assume here that one call to array_key_exists() is more efficient
// than calling getSettings() when all we need is a single setting.
if (array_key_exists($setting_name, $this->settings)) {
return $this->settings[$setting_name];
}
$settings = $this->getSettings();
if (array_key_exists($setting_name, $settings)) {
return $settings[$setting_name];
}
else {
return NULL;
}
}
/**
* {@inheritdoc}
*/
public function setSetting($setting_name, $value) {
$this->settings[$setting_name] = $value;
return $this;
}
/**
* {@inheritdoc}
*/
public function setSettings(array $settings) {
$this->settings = $settings + $this->settings;
return $this;
}
/**
* {@inheritdoc}
*/
public function isTranslatable() {
return $this->translatable;
}
/**
* {@inheritdoc}
*/
public function isRevisionable() {
// All configurable fields are revisionable.
return TRUE;
}
/**
* {@inheritdoc}
*/
public function setTranslatable($translatable) {
$this->translatable = $translatable;
return $this;
}
/**
* {@inheritdoc}
*/
public function getProvider() {
return 'field';
}
/**
* {@inheritdoc}
*/
public function getLabel() {
return $this->label();
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return NULL;
}
/**
* {@inheritdoc}
*/
public function getCardinality() {
/** @var \Drupal\Core\Field\FieldTypePluginManager $field_type_manager */
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
$definition = $field_type_manager->getDefinition($this->getType());
$enforced_cardinality = isset($definition['cardinality']) ? (int) $definition['cardinality'] : NULL;
// Enforced cardinality is a positive integer or -1.
if ($enforced_cardinality !== NULL && $enforced_cardinality < 1 && $enforced_cardinality !== FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
throw new FieldException("Invalid enforced cardinality '{$definition['cardinality']}'. Allowed values: a positive integer or -1.");
}
return $enforced_cardinality ?: $this->cardinality;
}
/**
* {@inheritdoc}
*/
public function setCardinality($cardinality) {
$this->cardinality = $cardinality;
return $this;
}
/**
* {@inheritdoc}
*/
public function getOptionsProvider($property_name, FieldableEntityInterface $entity) {
// If the field item class implements the interface, create an orphaned
// runtime item object, so that it can be used as the options provider
// without modifying the entity being worked on.
if (is_subclass_of($this->getFieldItemClass(), OptionsProviderInterface::class)) {
try {
$items = $entity->get($this->getName());
}
catch (\InvalidArgumentException $e) {
// When a field doesn't exist, create a new field item list using a
// temporary base field definition. This step is necessary since there
// may not be a field configuration for the storage when creating a new
// field.
// @todo Simplify in https://www.drupal.org/project/drupal/issues/3347291.
$field_storage = BaseFieldDefinition::createFromFieldStorageDefinition($this);
$entity_adapter = EntityAdapter::createFromEntity($entity);
$items = \Drupal::typedDataManager()->create($field_storage, name: $field_storage->getName(), parent: $entity_adapter);
}
return \Drupal::service('plugin.manager.field.field_type')->createFieldItem($items, 0);
}
// @todo Allow setting custom options provider.
// https://www.drupal.org/node/2002138.
}
/**
* {@inheritdoc}
*/
public function isMultiple() {
$cardinality = $this->getCardinality();
return ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) || ($cardinality > 1);
}
/**
* {@inheritdoc}
*/
public function isLocked() {
return $this->locked;
}
/**
* {@inheritdoc}
*/
public function setLocked($locked) {
$this->locked = $locked;
return $this;
}
/**
* {@inheritdoc}
*/
public function getTargetEntityTypeId() {
return $this->entity_type;
}
/**
* Determines whether a field has any data.
*
* @return bool
* TRUE if the field has data for any entity; FALSE otherwise.
*/
public function hasData() {
return !$this->isNew() && \Drupal::entityTypeManager()->getStorage($this->entity_type)->countFieldData($this, TRUE);
}
/**
* Implements the magic __sleep() method.
*
* Using the Serialize interface and serialize() / unserialize() methods
* breaks entity forms in PHP 5.4.
* @todo Investigate in https://www.drupal.org/node/1977206.
*/
public function __sleep() {
// Only serialize necessary properties, excluding those that can be
// recalculated.
$properties = get_object_vars($this);
unset($properties['schema'], $properties['propertyDefinitions'], $properties['original']);
return array_keys($properties);
}
/**
* {@inheritdoc}
*/
public function getConstraints() {
return [];
}
/**
* {@inheritdoc}
*/
public function getConstraint($constraint_name) {
return NULL;
}
/**
* {@inheritdoc}
*/
public function getPropertyDefinition($name) {
if (!isset($this->propertyDefinitions)) {
$this->getPropertyDefinitions();
}
if (isset($this->propertyDefinitions[$name])) {
return $this->propertyDefinitions[$name];
}
}
/**
* {@inheritdoc}
*/
public function getPropertyDefinitions() {
if (!isset($this->propertyDefinitions)) {
$class = $this->getFieldItemClass();
$this->propertyDefinitions = $class::propertyDefinitions($this);
}
return $this->propertyDefinitions;
}
/**
* {@inheritdoc}
*/
public function getPropertyNames() {
return array_keys($this->getPropertyDefinitions());
}
/**
* {@inheritdoc}
*/
public function getMainPropertyName() {
$class = $this->getFieldItemClass();
return $class::mainPropertyName();
}
/**
* {@inheritdoc}
*/
public function getUniqueStorageIdentifier() {
return $this->uuid();
}
/**
* Helper to retrieve the field item class.
*/
protected function getFieldItemClass() {
$type_definition = \Drupal::typedDataManager()
->getDefinition('field_item:' . $this->getType());
return $type_definition['class'];
}
/**
* Loads a field config entity based on the entity type and field name.
*
* @param string $entity_type_id
* ID of the entity type.
* @param string $field_name
* Name of the field.
*
* @return \Drupal\field\FieldStorageConfigInterface|null
* The field config entity if one exists for the provided field name,
* otherwise NULL.
*/
public static function loadByName($entity_type_id, $field_name) {
return \Drupal::entityTypeManager()->getStorage('field_storage_config')->load($entity_type_id . '.' . $field_name);
}
/**
* {@inheritdoc}
*/
public function isDeletable() {
// The field storage is not deleted, is configured to be removed when there
// are no fields, the field storage has no bundles, and field storages are
// not in the process of being deleted.
return !$this->deleted && !$this->persist_with_no_fields && count($this->getBundles()) == 0 && !static::$inDeletion;
}
/**
* {@inheritdoc}
*/
public function getIndexes() {
return $this->indexes;
}
/**
* {@inheritdoc}
*/
public function setIndexes(array $indexes) {
$this->indexes = $indexes;
return $this;
}
}

View File

@@ -0,0 +1,97 @@
<?php
namespace Drupal\field;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Rebuilds all form and view modes for a passed entity bundle.
*
* @see field_field_config_insert()
*
* @internal
*/
class EntityDisplayRebuilder implements ContainerInjectionInterface {
/**
* The field storage config storage.
*
* @var \Drupal\Core\Entity\EntityTypeManager
*/
protected $entityTypeManager;
/**
* The display repository.
*
* @var \Drupal\Core\Entity\EntityDisplayRepository
*/
protected $entityDisplayRepository;
/**
* The entity type bundle info.
*
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
*/
protected $entityTypeBundleInfo;
/**
* Constructs a new EntityDisplayRebuilder.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity manager.
* @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository
* The entity display repository.
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
* The entity type bundle info.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityDisplayRepositoryInterface $entity_display_repository, EntityTypeBundleInfoInterface $entity_type_bundle_info) {
$this->entityTypeManager = $entity_type_manager;
$this->entityDisplayRepository = $entity_display_repository;
$this->entityTypeBundleInfo = $entity_type_bundle_info;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity_type.manager'),
$container->get('entity_display.repository'),
$container->get('entity_type.bundle.info')
);
}
/**
* Rebuild displays for single Entity Type.
*
* @param string $entity_type_id
* The entity type machine name.
* @param string $bundle
* The bundle we need to rebuild.
*/
public function rebuildEntityTypeDisplays($entity_type_id, $bundle) {
// Get the displays.
$view_modes = $this->entityDisplayRepository->getViewModeOptions($entity_type_id);
$form_modes = $this->entityDisplayRepository->getFormModeOptions($entity_type_id);
// Save view mode displays.
$view_mode_ids = array_map(function ($view_mode) use ($entity_type_id, $bundle) {
return "$entity_type_id.$bundle.$view_mode";
}, array_keys($view_modes));
foreach ($this->entityTypeManager->getStorage('entity_view_display')->loadMultiple($view_mode_ids) as $display) {
$display->save();
}
// Save form mode displays.
$form_mode_ids = array_map(function ($form_mode) use ($entity_type_id, $bundle) {
return "$entity_type_id.$bundle.$form_mode";
}, array_keys($form_modes));
foreach ($this->entityTypeManager->getStorage('entity_form_display')->loadMultiple($form_mode_ids) as $display) {
$display->save();
}
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Drupal\field;
use Drupal\Core\Entity\EntityAccessControlHandler;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Defines the access control handler for the field config entity type.
*
* @see \Drupal\field\Entity\FieldConfig
*/
class FieldConfigAccessControlHandler extends EntityAccessControlHandler {
/**
* {@inheritdoc}
*/
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
// Delegate access control to the underlying field storage config entity:
// the field config entity merely handles configuration for a particular
// bundle of an entity type, the bulk of the logic and configuration is with
// the field storage config entity. Therefore, if an operation is allowed on
// a certain field storage config entity, it should also be allowed for all
// associated field config entities.
// @see \Drupal\Core\Field\FieldDefinitionInterface
/** \Drupal\field\FieldConfigInterface $entity */
$field_storage_entity = $entity->getFieldStorageDefinition();
return $field_storage_entity->access($operation, $account, TRUE);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Drupal\field;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
/**
* Provides an interface defining a field entity.
*/
interface FieldConfigInterface extends ConfigEntityInterface, FieldDefinitionInterface {
/**
* Gets the deleted flag of the field.
*
* @return bool
* Returns TRUE if the field is deleted.
*/
public function isDeleted();
}

View File

@@ -0,0 +1,181 @@
<?php
namespace Drupal\field;
use Drupal\Core\Cache\MemoryCache\MemoryCacheInterface;
use Drupal\Core\Config\Config;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\DeletedFieldsRepositoryInterface;
use Drupal\Core\Field\FieldConfigStorageBase;
use Drupal\Core\Field\FieldTypePluginManagerInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Component\Uuid\UuidInterface;
/**
* Storage handler for field config.
*/
class FieldConfigStorage extends FieldConfigStorageBase {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The field type plugin manager.
*
* @var \Drupal\Core\Field\FieldTypePluginManagerInterface
*/
protected $fieldTypeManager;
/**
* The deleted fields repository.
*
* @var \Drupal\Core\Field\DeletedFieldsRepositoryInterface
*/
protected $deletedFieldsRepository;
/**
* Constructs a FieldConfigStorage object.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory service.
* @param \Drupal\Component\Uuid\UuidInterface $uuid_service
* The UUID service.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager
* The field type plugin manager.
* @param \Drupal\Core\Field\DeletedFieldsRepositoryInterface $deleted_fields_repository
* The deleted fields repository.
* @param \Drupal\Core\Cache\MemoryCache\MemoryCacheInterface $memory_cache
* The memory cache.
*/
public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, EntityTypeManagerInterface $entity_type_manager, FieldTypePluginManagerInterface $field_type_manager, DeletedFieldsRepositoryInterface $deleted_fields_repository, MemoryCacheInterface $memory_cache) {
parent::__construct($entity_type, $config_factory, $uuid_service, $language_manager, $memory_cache);
$this->entityTypeManager = $entity_type_manager;
$this->fieldTypeManager = $field_type_manager;
$this->deletedFieldsRepository = $deleted_fields_repository;
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('config.factory'),
$container->get('uuid'),
$container->get('language_manager'),
$container->get('entity_type.manager'),
$container->get('plugin.manager.field.field_type'),
$container->get('entity_field.deleted_fields_repository'),
$container->get('entity.memory_cache')
);
}
/**
* {@inheritdoc}
*/
public function importDelete($name, Config $new_config, Config $old_config) {
// If the field storage has been deleted in the same import, the field will
// be deleted by then, and there is nothing left to do. Just return TRUE so
// that the file does not get written to active store.
if (!$old_config->get()) {
return TRUE;
}
return parent::importDelete($name, $new_config, $old_config);
}
/**
* {@inheritdoc}
*/
public function loadByProperties(array $conditions = []) {
// Include deleted fields if specified in the $conditions parameters.
$include_deleted = $conditions['include_deleted'] ?? FALSE;
unset($conditions['include_deleted']);
$fields = [];
// Get fields stored in configuration. If we are explicitly looking for
// deleted fields only, this can be skipped, because they will be
// retrieved from the deleted fields repository below.
if (empty($conditions['deleted'])) {
if (isset($conditions['entity_type']) && isset($conditions['bundle']) && isset($conditions['field_name'])) {
// Optimize for the most frequent case where we do have a specific ID.
$id = $conditions['entity_type'] . '.' . $conditions['bundle'] . '.' . $conditions['field_name'];
$fields = $this->loadMultiple([$id]);
}
else {
// No specific ID, we need to examine all existing fields.
$fields = $this->loadMultiple();
}
}
// Merge deleted fields from the deleted fields repository if needed.
if ($include_deleted || !empty($conditions['deleted'])) {
$deleted_field_definitions = $this->deletedFieldsRepository->getFieldDefinitions();
foreach ($deleted_field_definitions as $id => $field_definition) {
if ($field_definition instanceof FieldConfigInterface) {
$fields[$id] = $field_definition;
}
}
}
// Collect matching fields.
$matching_fields = [];
foreach ($fields as $field) {
// Some conditions are checked against the field storage.
$field_storage = $field->getFieldStorageDefinition();
// Only keep the field if it matches all conditions.
foreach ($conditions as $key => $value) {
// Extract the actual value against which the condition is checked.
switch ($key) {
case 'field_name':
$checked_value = $field_storage->getName();
break;
case 'field_id':
case 'field_storage_uuid':
$checked_value = $field_storage->uuid();
break;
case 'uuid';
$checked_value = $field->uuid();
break;
case 'deleted';
$checked_value = $field->isDeleted();
break;
default:
$checked_value = $field->get($key);
break;
}
// Skip to the next field as soon as one condition does not match.
if ($checked_value != $value) {
continue 2;
}
}
// When returning deleted fields, key the results by UUID since they
// can include several fields with the same ID.
$key = $include_deleted ? $field->uuid() : $field->id();
$matching_fields[$key] = $field;
}
return $matching_fields;
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace Drupal\field;
/**
* Provides a trait for the valid field label options.
*/
trait FieldLabelOptionsTrait {
/**
* Returns an array of visibility options for field labels.
*
* @return array
* An array of visibility options.
*/
protected function getFieldLabelOptions(): array {
return [
'above' => $this->t('Above'),
'inline' => $this->t('Inline'),
'hidden' => '- ' . $this->t('Hidden') . ' -',
'visually_hidden' => '- ' . $this->t('Visually Hidden') . ' -',
];
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Drupal\field;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityAccessControlHandler;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Defines the access control handler for the field storage config entity type.
*
* @see \Drupal\field\Entity\FieldStorageConfig
*/
class FieldStorageConfigAccessControlHandler extends EntityAccessControlHandler {
/**
* {@inheritdoc}
*/
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
/** \Drupal\field\FieldStorageConfigInterface $entity */
if ($operation === 'delete') {
if ($entity->isLocked()) {
return AccessResult::forbidden()->addCacheableDependency($entity);
}
else {
return AccessResult::allowedIfHasPermission($account, 'administer ' . $entity->getTargetEntityTypeId() . ' fields')->addCacheableDependency($entity);
}
}
return AccessResult::allowedIfHasPermission($account, 'administer ' . $entity->getTargetEntityTypeId() . ' fields');
}
}

View File

@@ -0,0 +1,145 @@
<?php
namespace Drupal\field;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
/**
* Provides an interface defining a field storage entity.
*/
interface FieldStorageConfigInterface extends ConfigEntityInterface, FieldStorageDefinitionInterface {
/**
* Returns the field type.
*
* @return string
* The field type, i.e. the id of a field type plugin. For example 'text'.
*/
public function getType();
/**
* Returns the name of the module providing the field type.
*
* @return string
* The name of the module that provides the field type.
*/
public function getTypeProvider();
/**
* Returns the list of bundles where the field storage has fields.
*
* @return array
* An array of bundle names.
*/
public function getBundles();
/**
* Checks if the field storage can be deleted.
*
* @return bool
* TRUE if the field storage can be deleted.
*/
public function isDeletable();
/**
* Returns whether the field storage is locked or not.
*
* @return bool
* TRUE if the field storage is locked.
*/
public function isLocked();
/**
* Sets the locked flag.
*
* @param bool $locked
* Sets value of locked flag.
*
* @return $this
*/
public function setLocked($locked);
/**
* Sets the maximum number of items allowed for the field.
*
* @param int $cardinality
* The cardinality value.
*
* @return $this
*/
public function setCardinality($cardinality);
/**
* Sets the value for a field setting by name.
*
* @param string $setting_name
* The name of the setting.
* @param mixed $value
* The value of the setting.
*
* @return $this
*/
public function setSetting($setting_name, $value);
/**
* Sets field storage settings.
*
* Note that the method does not unset existing settings not specified in the
* incoming $settings array.
*
* For example:
* @code
* // Given these are the default settings.
* $storage_definition->getSettings() === [
* 'fruit' => 'apple',
* 'season' => 'summer',
* ];
* // Change only the 'fruit' setting.
* $storage_definition->setSettings(['fruit' => 'banana']);
* // The 'season' setting persists unchanged.
* $storage_definition->getSettings() === [
* 'fruit' => 'banana',
* 'season' => 'summer',
* ];
* @endcode
*
* For clarity, it is preferred to use setSetting() if not all available
* settings are supplied.
*
* @param array $settings
* The array of storage settings.
*
* @return $this
*/
public function setSettings(array $settings);
/**
* Sets whether the field is translatable.
*
* @param bool $translatable
* Whether the field is translatable.
*
* @return $this
*/
public function setTranslatable($translatable);
/**
* Returns the custom storage indexes for the field data storage.
*
* @return array
* An array of custom indexes.
*/
public function getIndexes();
/**
* Sets the custom storage indexes for the field data storage..
*
* @param array $indexes
* The array of custom indexes.
*
* @return $this
*/
public function setIndexes(array $indexes);
}

View File

@@ -0,0 +1,184 @@
<?php
namespace Drupal\field;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Component\Uuid\UuidInterface;
use Drupal\Core\Cache\MemoryCache\MemoryCacheInterface;
use Drupal\Core\Config\Entity\ConfigEntityStorage;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\DeletedFieldsRepositoryInterface;
use Drupal\Core\Field\FieldTypePluginManagerInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
/**
* Storage handler for "field storage" configuration entities.
*/
class FieldStorageConfigStorage extends ConfigEntityStorage {
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The field type plugin manager.
*
* @var \Drupal\Core\Field\FieldTypePluginManagerInterface
*/
protected $fieldTypeManager;
/**
* The deleted fields repository.
*
* @var \Drupal\Core\Field\DeletedFieldsRepositoryInterface
*/
protected $deletedFieldsRepository;
/**
* Constructs a FieldStorageConfigStorage object.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory service.
* @param \Drupal\Component\Uuid\UuidInterface $uuid_service
* The UUID service.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
* @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager
* The field type plugin manager.
* @param \Drupal\Core\Field\DeletedFieldsRepositoryInterface $deleted_fields_repository
* The deleted fields repository.
* @param \Drupal\Core\Cache\MemoryCache\MemoryCacheInterface $memory_cache
* The memory cache.
*/
public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, EntityTypeManagerInterface $entity_type_manager, ModuleHandlerInterface $module_handler, FieldTypePluginManagerInterface $field_type_manager, DeletedFieldsRepositoryInterface $deleted_fields_repository, MemoryCacheInterface $memory_cache) {
parent::__construct($entity_type, $config_factory, $uuid_service, $language_manager, $memory_cache);
$this->entityTypeManager = $entity_type_manager;
$this->moduleHandler = $module_handler;
$this->fieldTypeManager = $field_type_manager;
$this->deletedFieldsRepository = $deleted_fields_repository;
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('config.factory'),
$container->get('uuid'),
$container->get('language_manager'),
$container->get('entity_type.manager'),
$container->get('module_handler'),
$container->get('plugin.manager.field.field_type'),
$container->get('entity_field.deleted_fields_repository'),
$container->get('entity.memory_cache')
);
}
/**
* {@inheritdoc}
*/
public function loadByProperties(array $conditions = []) {
// Include deleted fields if specified in the $conditions parameters.
$include_deleted = $conditions['include_deleted'] ?? FALSE;
unset($conditions['include_deleted']);
/** @var \Drupal\field\FieldStorageConfigInterface[] $storages */
$storages = [];
// Get field storages living in configuration. If we are explicitly looking
// for deleted storages only, this can be skipped, because they will be
// retrieved from the deleted fields repository below.
if (empty($conditions['deleted'])) {
if (isset($conditions['entity_type']) && isset($conditions['field_name'])) {
// Optimize for the most frequent case where we do have a specific ID.
$id = $conditions['entity_type'] . '.' . $conditions['field_name'];
$storages = $this->loadMultiple([$id]);
}
else {
// No specific ID, we need to examine all existing storages.
$storages = $this->loadMultiple();
}
}
// Merge deleted field storage definitions from the deleted fields
// repository if needed.
if ($include_deleted || !empty($conditions['deleted'])) {
$deleted_storage_definitions = $this->deletedFieldsRepository->getFieldStorageDefinitions();
foreach ($deleted_storage_definitions as $id => $field_storage_definition) {
if ($field_storage_definition instanceof FieldStorageConfigInterface) {
$storages[$id] = $field_storage_definition;
}
}
}
// Collect matching fields.
$matches = [];
foreach ($storages as $field) {
foreach ($conditions as $key => $value) {
// Extract the actual value against which the condition is checked.
$checked_value = $field->get($key);
// Skip to the next field as soon as one condition does not match.
if ($checked_value != $value) {
continue 2;
}
}
// When returning deleted fields, key the results by UUID since they can
// include several fields with the same ID.
$key = $include_deleted ? $field->uuid() : $field->id();
$matches[$key] = $field;
}
return $matches;
}
/**
* {@inheritdoc}
*/
protected function mapFromStorageRecords(array $records) {
foreach ($records as $id => &$record) {
try {
$class = $this->fieldTypeManager->getPluginClass($record['type']);
}
catch (PluginNotFoundException $e) {
$config_id = $this->getPrefix() . $id;
throw new PluginNotFoundException($record['type'], "Unable to determine class for field type '{$record['type']}' found in the '$config_id' configuration", $e->getCode(), $e);
}
$record['settings'] = $class::storageSettingsFromConfigData($record['settings']);
}
return parent::mapFromStorageRecords($records);
}
/**
* {@inheritdoc}
*/
protected function mapToStorageRecord(EntityInterface $entity) {
$record = parent::mapToStorageRecord($entity);
$class = $this->fieldTypeManager->getPluginClass($record['type']);
$record['settings'] = $class::storageSettingsToConfigData($record['settings']);
return $record;
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Drupal\field;
use Drupal\Core\Field\FieldException;
/**
* Exception class thrown by hook_field_storage_config_update_forbid().
*/
class FieldStorageConfigUpdateForbiddenException extends FieldException {}

View File

@@ -0,0 +1,111 @@
<?php
namespace Drupal\field;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ConfigImportModuleUninstallValidatorInterface;
use Drupal\Core\Field\FieldTypePluginManagerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
/**
* Prevents uninstallation of modules providing active field storage.
*/
class FieldUninstallValidator implements ConfigImportModuleUninstallValidatorInterface {
use StringTranslationTrait;
/**
* The field storage config storage.
*
* @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface
*/
protected $fieldStorageConfigStorage;
/**
* The field type plugin manager.
*
* @var \Drupal\Core\Field\FieldTypePluginManagerInterface
*/
protected $fieldTypeManager;
/**
* Constructs a new FieldUninstallValidator.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The string translation service.
* @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager
* The field type plugin manager.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, TranslationInterface $string_translation, FieldTypePluginManagerInterface $field_type_manager) {
$this->fieldStorageConfigStorage = $entity_type_manager->getStorage('field_storage_config');
$this->stringTranslation = $string_translation;
$this->fieldTypeManager = $field_type_manager;
}
/**
* {@inheritdoc}
*/
public function validate($module) {
$reasons = [];
if ($field_storages = $this->getFieldStoragesByModule($module)) {
// Provide an explanation message (only mention pending deletions if there
// remain no actual, non-deleted fields.)
$fields_in_use = [];
foreach ($field_storages as $field_storage) {
if (!$field_storage->isDeleted()) {
$fields_in_use[$field_storage->getType()][] = $field_storage->getLabel();
}
}
if (!empty($fields_in_use)) {
foreach ($fields_in_use as $field_type => $field_storages) {
$field_type_label = $this->getFieldTypeLabel($field_type);
$reasons[] = $this->formatPlural(count($fields_in_use[$field_type]), 'The %field_type_label field type is used in the following field: @fields', 'The %field_type_label field type is used in the following fields: @fields', ['%field_type_label' => $field_type_label, '@fields' => implode(', ', $field_storages)]);
}
}
else {
$reasons[] = $this->t('Fields pending deletion');
}
}
return $reasons;
}
/**
* {@inheritdoc}
*/
public function validateConfigImport(string $module, StorageInterface $source_storage): array {
// The field_config_import_steps_alter() method removes field data prior to
// configuration import so the checks in ::validate() are unnecessary.
return [];
}
/**
* Returns all field storages for a specified module.
*
* @param string $module
* The module to filter field storages by.
*
* @return \Drupal\field\FieldStorageConfigInterface[]
* An array of field storages for a specified module.
*/
protected function getFieldStoragesByModule($module) {
return $this->fieldStorageConfigStorage->loadByProperties(['module' => $module, 'include_deleted' => TRUE]);
}
/**
* Returns the label for a specified field type.
*
* @param string $field_type
* The field type.
*
* @return string
* The field type label.
*/
protected function getFieldTypeLabel($field_type) {
return $this->fieldTypeManager->getDefinitions()[$field_type]['label'];
}
}

View File

@@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
namespace Drupal\field\Plugin\ConfigAction;
use Drupal\Core\Config\Action\Attribute\ConfigAction;
use Drupal\Core\Config\Action\ConfigActionException;
use Drupal\Core\Config\Action\ConfigActionPluginInterface;
use Drupal\Core\Config\ConfigManagerInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\field\FieldStorageConfigInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Adds a field to all bundles of its target entity type.
*
* @internal
* This API is experimental.
*/
#[ConfigAction(
id: 'field_storage_config:addToAllBundles',
admin_label: new TranslatableMarkup('Add a field to all bundles'),
entity_types: ['field_storage_config'],
)]
final class AddToAllBundles implements ConfigActionPluginInterface, ContainerFactoryPluginInterface {
public function __construct(
private readonly EntityTypeManagerInterface $entityTypeManager,
private readonly EntityTypeBundleInfoInterface $entityTypeBundleInfo,
private readonly ConfigManagerInterface $configManager,
) {}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
return new static(
$container->get(EntityTypeManagerInterface::class),
$container->get(EntityTypeBundleInfoInterface::class),
$container->get(ConfigManagerInterface::class),
);
}
/**
* {@inheritdoc}
*/
public function apply(string $configName, mixed $value): void {
assert(is_array($value));
$field_storage = $this->configManager->loadConfigEntityByName($configName);
assert($field_storage instanceof FieldStorageConfigInterface);
$storage = $this->entityTypeManager->getStorage('field_config');
$entity_type_id = $field_storage->getTargetEntityTypeId();
$field_name = $field_storage->getName();
$existing_fields = $storage->getQuery()
->condition('entity_type', $entity_type_id)
->condition('field_name', $field_name)
->execute();
// Get all bundles of the target entity type.
$bundles = array_keys($this->entityTypeBundleInfo->getBundleInfo($entity_type_id));
foreach ($bundles as $bundle) {
$id = "$entity_type_id.$bundle.$field_name";
if (in_array($id, $existing_fields, TRUE)) {
if (empty($value['fail_if_exists'])) {
continue;
}
throw new ConfigActionException(sprintf('Field %s already exists.', $id));
}
$storage->create([
'label' => $value['label'],
'bundle' => $bundle,
'description' => $value['description'],
'field_storage' => $field_storage,
])->save();
}
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace Drupal\field\Plugin\migrate\field;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate_drupal\Attribute\MigrateField;
use Drupal\migrate_drupal\Plugin\migrate\field\FieldPluginBase;
// cspell:ignore spamspan
/**
* MigrateField Plugin for Drupal 6 and 7 email fields.
*/
#[MigrateField(
id: 'email',
core: [6, 7],
type_map: [
'email' => 'email',
],
source_module: 'email',
destination_module: 'core',
)]
class Email extends FieldPluginBase {
/**
* {@inheritdoc}
*/
public function getFieldWidgetMap() {
return [
'email_textfield' => 'email_default',
];
}
/**
* {@inheritdoc}
*/
public function getFieldFormatterMap() {
return [
'email_formatter_default' => 'email_mailto',
'email_formatter_contact' => 'basic_string',
'email_formatter_plain' => 'basic_string',
'email_formatter_spamspan' => 'basic_string',
'email_default' => 'email_mailto',
'email_contact' => 'basic_string',
'email_plain' => 'basic_string',
'email_spamspan' => 'basic_string',
];
}
/**
* {@inheritdoc}
*/
public function defineValueProcessPipeline(MigrationInterface $migration, $field_name, $data) {
$process = [
'plugin' => 'sub_process',
'source' => $field_name,
'process' => [
'value' => 'email',
],
];
$migration->setProcessOfProperty($field_name, $process);
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Drupal\field\Plugin\migrate\field\d7;
use Drupal\migrate_drupal\Attribute\MigrateField;
use Drupal\migrate_drupal\Plugin\migrate\field\FieldPluginBase;
// cspell:ignore entityreference
/**
* MigrateField plugin for Drupal 7 entity_reference fields.
*/
#[MigrateField(
id: 'entityreference',
core: [7],
type_map: [
'entityreference' => 'entity_reference',
],
source_module: 'entityreference',
destination_module: 'core',
)]
class EntityReference extends FieldPluginBase {
/**
* {@inheritdoc}
*/
public function getFieldFormatterMap() {
return [
'entityreference_label' => 'entity_reference_label',
'entityreference_entity_id' => 'entity_reference_entity_id',
'entityreference_entity_view' => 'entity_reference_entity_view',
];
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace Drupal\field\Plugin\migrate\field\d7;
use Drupal\migrate_drupal\Attribute\MigrateField;
use Drupal\migrate_drupal\Plugin\migrate\field\FieldPluginBase;
/**
* MigrateField plugin for Drupal 7 number fields.
*/
#[MigrateField(
id: 'number_default',
core: [7],
type_map: [
'number_integer' => 'integer',
'number_decimal' => 'decimal',
'number_float' => 'float',
],
source_module: 'number',
destination_module: 'core',
)]
class NumberField extends FieldPluginBase {}

View File

@@ -0,0 +1,79 @@
<?php
namespace Drupal\field\Plugin\migrate\process;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\migrate\Attribute\MigrateProcess;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Plugin\migrate\process\StaticMap;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
#[MigrateProcess('field_type')]
class FieldType extends StaticMap implements ContainerFactoryPluginInterface {
/**
* The field plugin manager.
*
* @var \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface
*/
protected $fieldPluginManager;
/**
* The migration object.
*
* @var \Drupal\migrate\Plugin\MigrationInterface
*/
protected $migration;
/**
* Constructs a FieldType plugin.
*
* @param array $configuration
* The plugin configuration.
* @param string $plugin_id
* The plugin ID.
* @param mixed $plugin_definition
* The plugin definition.
* @param \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface $field_plugin_manager
* The field plugin manager.
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
* The migration being run.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrateFieldPluginManagerInterface $field_plugin_manager, ?MigrationInterface $migration = NULL) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->fieldPluginManager = $field_plugin_manager;
$this->migration = $migration;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, ?MigrationInterface $migration = NULL) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('plugin.manager.migrate.field'),
$migration
);
}
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
$field_type = is_array($value) ? $value[0] : $value;
try {
$plugin_id = $this->fieldPluginManager->getPluginIdFromFieldType($field_type, [], $this->migration);
return $this->fieldPluginManager->createInstance($plugin_id, [], $this->migration)->getFieldType($row);
}
catch (PluginNotFoundException $e) {
return parent::transform($value, $migrate_executable, $row, $destination_property);
}
}
}

View File

@@ -0,0 +1,136 @@
<?php
namespace Drupal\field\Plugin\migrate\process;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\migrate\Attribute\MigrateProcess;
use Drupal\migrate\MigrateException;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Get the value from a method call on a field plugin instance.
*
* This process plugin will instantiate a field plugin based on the given
* field type and then call the given method on it for the return value.
*
* Available configuration keys:
* - source: The source field type to use to instantiate a field plugin.
* - method: The method to be called on the field plugin instance.
*
* If no field plugin for the given field type is found, NULL will be returned.
*
* Example:
*
* @code
* process:
* type:
* plugin: process_field
* source: type
* method: getFieldType
* @endcode
*
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
* @see \Drupal\migrate_drupal\Plugin\MigrateFieldInterface;
*/
#[MigrateProcess('process_field')]
class ProcessField extends ProcessPluginBase implements ContainerFactoryPluginInterface {
/**
* The field plugin manager.
*
* @var \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface
*/
protected $fieldPluginManager;
/**
* The migration being run.
*
* @var \Drupal\migrate\Plugin\MigrationInterface
*/
protected $migration;
/**
* Constructs a ProcessField plugin.
*
* @param array $configuration
* The plugin configuration.
* @param string $plugin_id
* The plugin ID.
* @param mixed $plugin_definition
* The plugin definition.
* @param \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface $field_plugin_manager
* The field plugin manager.
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
* The migration being run.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrateFieldPluginManagerInterface $field_plugin_manager, ?MigrationInterface $migration = NULL) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->fieldPluginManager = $field_plugin_manager;
$this->migration = $migration;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, ?MigrationInterface $migration = NULL) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('plugin.manager.migrate.field'),
$migration
);
}
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
if (!is_string($value)) {
throw new MigrateException('The input value must be a string.');
}
if (empty($this->configuration['method'])) {
throw new MigrateException('You need to specify the name of a method to be called on the Field plugin.');
}
$method = $this->configuration['method'];
try {
return $this->callMethodOnFieldPlugin($this->fieldPluginManager, $value, $method, $row);
}
catch (PluginNotFoundException $e) {
return NULL;
}
}
/**
* Instantiate a field plugin and call a method on it.
*
* @param \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface $field_plugin_manager
* The field plugin manager.
* @param string $field_type
* The field type for which to get the field plugin.
* @param string $method
* The method to call on the field plugin.
* @param \Drupal\migrate\Row $row
* The row from the source to process.
*
* @return mixed
* The return value from the method called on the field plugin.
*/
protected function callMethodOnFieldPlugin(MigrateFieldPluginManagerInterface $field_plugin_manager, $field_type, $method, Row $row) {
$plugin_id = $field_plugin_manager->getPluginIdFromFieldType($field_type, [], $this->migration);
$plugin_instance = $field_plugin_manager->createInstance($plugin_id, [], $this->migration);
if (!is_callable([$plugin_instance, $method])) {
throw new MigrateException('The specified method does not exist or is not callable.');
}
return call_user_func_array([$plugin_instance, $method], [$row]);
}
}

View File

@@ -0,0 +1,130 @@
<?php
namespace Drupal\field\Plugin\migrate\process\d6;
use Drupal\migrate\Attribute\MigrateProcess;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Row;
/**
* Set the default field settings.
*/
#[MigrateProcess('field_formatter_settings_defaults')]
class FieldFormatterSettingsDefaults extends ProcessPluginBase {
/**
* {@inheritdoc}
*
* Set field formatter settings when the map didn't map: for date
* formatters, the fallback format, for everything else, empty array.
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
// If the 1 index is set then the map missed.
if (isset($value[1])) {
$module = $row->getSourceProperty('module');
if ($module === 'date') {
$value = ['format_type' => 'fallback'];
}
elseif ($module === 'number') {
// We have to do the lookup here in the process plugin because for
// number we need to calculated the settings based on the type not just
// the module which works well for other field types.
return $this->numberSettings($row->getDestinationProperty('options/type'), $value[1]);
}
else {
$value = [];
}
}
return $value;
}
/**
* @param string $type
* The field type.
* @param $format
* The format selected for the field on the display.
*
* @return array
* The correct default settings.
*
* @throws \Drupal\migrate\MigrateException
*/
protected function numberSettings($type, $format) {
$map = [
'number_decimal' => [
'us_0' => [
'scale' => 0,
'decimal_separator' => '.',
'thousand_separator' => ',',
'prefix_suffix' => TRUE,
],
'us_1' => [
'scale' => 1,
'decimal_separator' => '.',
'thousand_separator' => ',',
'prefix_suffix' => TRUE,
],
'us_2' => [
'scale' => 2,
'decimal_separator' => '.',
'thousand_separator' => ',',
'prefix_suffix' => TRUE,
],
'be_0' => [
'scale' => 0,
'decimal_separator' => ',',
'thousand_separator' => '.',
'prefix_suffix' => TRUE,
],
'be_1' => [
'scale' => 1,
'decimal_separator' => ',',
'thousand_separator' => '.',
'prefix_suffix' => TRUE,
],
'be_2' => [
'scale' => 2,
'decimal_separator' => ',',
'thousand_separator' => '.',
'prefix_suffix' => TRUE,
],
'fr_0' => [
'scale' => 0,
'decimal_separator' => ',',
'thousand_separator' => ' ',
'prefix_suffix' => TRUE,
],
'fr_1' => [
'scale' => 1,
'decimal_separator' => ',',
'thousand_separator' => ' ',
'prefix_suffix' => TRUE,
],
'fr_2' => [
'scale' => 2,
'decimal_separator' => ',',
'thousand_separator' => ' ',
'prefix_suffix' => TRUE,
],
],
'number_integer' => [
'us_0' => [
'thousand_separator' => ',',
'prefix_suffix' => TRUE,
],
'be_0' => [
'thousand_separator' => '.',
'prefix_suffix' => TRUE,
],
'fr_0' => [
'thousand_separator' => ' ',
'prefix_suffix' => TRUE,
],
],
];
return $map[$type][$format] ?? [];
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace Drupal\field\Plugin\migrate\process\d6;
use Drupal\migrate\Attribute\MigrateProcess;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
// cspell:ignore imagefield
/**
* Determines the default field settings.
*/
#[MigrateProcess('d6_field_instance_defaults')]
class FieldInstanceDefaults extends ProcessPluginBase {
/**
* {@inheritdoc}
*
* Set the field instance defaults.
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
[$widget_type, $widget_settings] = $value;
$default = [];
switch ($widget_type) {
case 'text_textfield':
case 'number':
case 'phone_textfield':
if (!empty($widget_settings['default_value'][0]['value'])) {
$default['value'] = $widget_settings['default_value'][0]['value'];
}
break;
case 'imagefield_widget':
// @todo load the image and populate the defaults.
// $default['default_image'] = $widget_settings['default_image'];
break;
case 'date_select':
if (!empty($widget_settings['default_value'])) {
$default['default_date_type'] = 'relative';
$default['default_date'] = $widget_settings['default_value'];
}
break;
case 'email_textfield':
if (!empty($widget_settings['default_value'][0]['email'])) {
$default['value'] = $widget_settings['default_value'][0]['email'];
}
break;
case 'link':
if (!empty($widget_settings['default_value'][0]['url'])) {
$default['title'] = $widget_settings['default_value'][0]['title'];
$default['uri'] = $widget_settings['default_value'][0]['url'];
$default['options'] = ['attributes' => []];
}
break;
}
if (!empty($default)) {
$default = [$default];
}
return $default;
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace Drupal\field\Plugin\migrate\process\d6;
use Drupal\migrate\Attribute\MigrateProcess;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* Determines the settings property and translation for boolean fields.
*/
#[MigrateProcess(
id: "d6_field_instance_option_translation",
handle_multiples: TRUE,
)]
class FieldInstanceOptionTranslation extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
[$field_type, $global_settings] = $value;
$option_key = 0;
$translation = '';
if (isset($global_settings['allowed_values'])) {
$list = explode("\n", $global_settings['allowed_values']);
$list = array_map('trim', $list);
$list = array_filter($list, 'strlen');
switch ($field_type) {
case 'boolean';
$option = preg_replace('/^option_/', '', $row->getSourceProperty('property'));
for ($i = 0; $i < 2; $i++) {
$value = $list[$i];
$tmp = explode("|", $value);
$original_option_key = $tmp[0] ?? NULL;
$option_key = ($i === 0) ? 'off_label' : 'on_label';
// Find property with name matching the original option.
if ($option == $original_option_key) {
$translation = $row->getSourceProperty('translation');
break;
}
}
break;
default:
}
}
return ['settings.' . $option_key, $translation];
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace Drupal\field\Plugin\migrate\process\d6;
use Drupal\migrate\Attribute\MigrateProcess;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
// cspell:ignore filefield imagefield
/**
* Determines the field instance settings.
*/
#[MigrateProcess('d6_field_field_settings')]
class FieldInstanceSettings extends ProcessPluginBase {
/**
* {@inheritdoc}
*
* Set the field instance defaults.
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
[$widget_type, $widget_settings, $field_settings] = $value;
$settings = [];
switch ($widget_type) {
case 'number':
$settings['min'] = $field_settings['min'];
$settings['max'] = $field_settings['max'];
$settings['prefix'] = $field_settings['prefix'];
$settings['suffix'] = $field_settings['suffix'];
break;
case 'link':
// $settings['url'] = $widget_settings['default_value'][0]['url'];
// D6 has optional, required, value and none. D8 only has disabled (0)
// optional (1) and required (2).
$map = ['disabled' => 0, 'optional' => 1, 'required' => 2];
$settings['title'] = $map[$field_settings['title']];
break;
case 'filefield_widget':
$settings['file_extensions'] = $widget_settings['file_extensions'];
$settings['file_directory'] = $widget_settings['file_path'];
$settings['description_field'] = $field_settings['description_field'];
$settings['max_filesize'] = $this->convertSizeUnit($widget_settings['max_filesize_per_file']);
break;
case 'imagefield_widget':
$settings['file_extensions'] = $widget_settings['file_extensions'];
$settings['file_directory'] = $widget_settings['file_path'];
$settings['max_filesize'] = $this->convertSizeUnit($widget_settings['max_filesize_per_file'] ?? '');
$settings['alt_field'] = $widget_settings['alt'];
$settings['alt_field_required'] = $widget_settings['custom_alt'];
$settings['title_field'] = $widget_settings['title'];
$settings['title_field_required'] = $widget_settings['custom_title'];
// With nothing entered for min or max resolution in Drupal 6, zero is
// stored. For Drupal 8 this should be an empty string.
$settings['max_resolution'] = !empty($widget_settings['max_resolution']) ? $widget_settings['max_resolution'] : '';
$settings['min_resolution'] = !empty($widget_settings['min_resolution']) ? $widget_settings['min_resolution'] : '';
break;
}
return $settings;
}
/**
* Convert file size strings into their D8 format.
*
* D6 stores file size using a "K" for kilobytes and "M" for megabytes where
* as D8 uses "KB" and "MB" respectively.
*
* @param string $size_string
* The size string, eg 10M
*
* @return string
* The D8 version of the size string.
*/
protected function convertSizeUnit($size_string) {
$size_unit = substr($size_string, strlen($size_string) - 1);
if ($size_unit == "M" || $size_unit == "K") {
return $size_string . "B";
}
return $size_string;
}
}

View File

@@ -0,0 +1,81 @@
<?php
namespace Drupal\field\Plugin\migrate\process\d6;
use Drupal\migrate\Attribute\MigrateProcess;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
// cspell:ignore filefield imagefield optionwidgets
/**
* Get the field instance widget settings.
*/
#[MigrateProcess('field_instance_widget_settings')]
class FieldInstanceWidgetSettings extends ProcessPluginBase {
/**
* {@inheritdoc}
*
* Get the field instance default/mapped widget settings.
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
[$widget_type, $widget_settings] = $value;
return $this->getSettings($widget_type, $widget_settings);
}
/**
* Merge the default D8 and specified D6 settings for a widget type.
*
* @param string $widget_type
* The widget type.
* @param array $widget_settings
* The widget settings from D6 for this widget.
*
* @return array
* A valid array of settings.
*/
public function getSettings($widget_type, $widget_settings) {
$progress = $widget_settings['progress_indicator'] ?? 'throbber';
$size = $widget_settings['size'] ?? 60;
$rows = $widget_settings['rows'] ?? 5;
$settings = [
'text_textfield' => [
'size' => $size,
'placeholder' => '',
],
'text_textarea' => [
'rows' => $rows,
'placeholder' => '',
],
'number' => [
'placeholder' => '',
],
'email_textfield' => [
'placeholder' => '',
],
'link' => [
'placeholder_url' => '',
'placeholder_title' => '',
],
'filefield_widget' => [
'progress_indicator' => $progress,
],
'imagefield_widget' => [
'progress_indicator' => $progress,
'preview_image_style' => 'thumbnail',
],
'optionwidgets_onoff' => [
'display_label' => FALSE,
],
'phone_textfield' => [
'placeholder' => '',
],
];
return $settings[$widget_type] ?? [];
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace Drupal\field\Plugin\migrate\process\d6;
use Drupal\migrate\Attribute\MigrateProcess;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* Determines the allowed values translation for select lists.
*/
#[MigrateProcess(
id: "d6_field_option_translation",
handle_multiples: TRUE,
)]
class FieldOptionTranslation extends ProcessPluginBase {
/**
* {@inheritdoc}
*
* Get the field default/mapped settings.
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
[$field_type, $global_settings] = $value;
$allowed_values = '';
$i = 0;
if (isset($global_settings['allowed_values'])) {
$list = explode("\n", $global_settings['allowed_values']);
$list = array_map('trim', $list);
$list = array_filter($list, 'strlen');
switch ($field_type) {
case 'list_string':
case 'list_integer':
case 'list_float':
// Remove the prefix used in the i18n_strings table for field options
// to get the option value.
$option = preg_replace('/^option_/', '', $row->getSourceProperty('property'));
$i = 0;
foreach ($list as $allowed_value) {
// Get the key for this allowed value which may be a key|label pair
// or just key.
$value = explode("|", $allowed_value);
if (isset($value[0]) && ($value[0] == $option)) {
$allowed_values = ['label' => $row->getSourceProperty('translation')];
break;
}
$i++;
}
break;
default:
}
}
return ["settings.allowed_values.$i", $allowed_values];
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace Drupal\field\Plugin\migrate\process\d6;
use Drupal\migrate\Attribute\MigrateProcess;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
// cspell:ignore userreference
/**
* Get the field settings.
*/
#[MigrateProcess('field_settings')]
class FieldSettings extends ProcessPluginBase {
/**
* {@inheritdoc}
*
* Get the field default/mapped settings.
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
// To maintain backwards compatibility, ensure that $value contains at least
// three elements.
if (count($value) == 2) {
$value[] = NULL;
}
[$field_type, $global_settings, $original_field_type] = $value;
return $this->getSettings($field_type, $global_settings, $original_field_type);
}
/**
* Merge the default D8 and specified D6 settings.
*
* @param string $field_type
* The destination field type.
* @param array $global_settings
* The field settings.
* @param string $original_field_type
* (optional) The original field type before migration.
*
* @return array
* A valid array of settings.
*/
public function getSettings($field_type, $global_settings, $original_field_type = NULL) {
$max_length = $global_settings['max_length'] ?? '';
$max_length = empty($max_length) ? 255 : $max_length;
$allowed_values = [];
if (isset($global_settings['allowed_values'])) {
$list = explode("\n", $global_settings['allowed_values']);
$list = array_map('trim', $list);
$list = array_filter($list, 'strlen');
switch ($field_type) {
case 'list_string':
case 'list_integer':
case 'list_float':
foreach ($list as $value) {
$value = explode("|", $value);
$allowed_values[$value[0]] = $value[1] ?? $value[0];
}
break;
default:
$allowed_values = $list;
}
}
$settings = [
'text' => [
'max_length' => $max_length,
],
'datetime' => ['datetime_type' => 'datetime'],
'list_string' => [
'allowed_values' => $allowed_values,
],
'list_integer' => [
'allowed_values' => $allowed_values,
],
'list_float' => [
'allowed_values' => $allowed_values,
],
'boolean' => [
'allowed_values' => $allowed_values,
],
];
if ($original_field_type == 'userreference') {
return ['target_type' => 'user'];
}
else {
return $settings[$field_type] ?? [];
}
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Drupal\field\Plugin\migrate\process\d6;
use Drupal\migrate\Attribute\MigrateProcess;
use Drupal\migrate\MigrateException;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Row;
/**
* Gives us a chance to set per field defaults.
*/
#[MigrateProcess('d6_field_type_defaults')]
class FieldTypeDefaults extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
if (is_array($value)) {
if ($row->getSourceProperty('module') == 'date') {
$value = 'datetime_default';
}
else {
throw new MigrateException(sprintf('Failed to lookup field type %s in the static map.', var_export($value, TRUE)));
}
}
return $value;
}
}

View File

@@ -0,0 +1,116 @@
<?php
namespace Drupal\field\Plugin\migrate\process\d7;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\migrate\Attribute\MigrateProcess;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\MigrateLookupInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Determines the bundle for a field.
*
* The field bundle process plugin is used to get the destination bundle name
* for a field. This is necessary because the bundle names used for comments in
* legacy versions of Drupal are no longer used.
*
* Available configuration keys:
* - source: The input value - must be an array.
*
* Examples:
*
* @code
* process:
* bundle:
* plugin: field_bundle
* source
* - entity_type
* - bundle
* @endcode
*
* If 'bundle' is 'article' and 'entity_type' is node then 'article' is
* returned.
*
* @code
* process:
* bundle:
* plugin: field_bundle
* source
* - entity_type
* - bundle
* @endcode
*
* If 'bundle' is 'comment_node_a_thirty_two_character_name' and 'entity_type'
* is blog then a lookup is performed on the comment type migration so that the
* migrated bundle name for 'comment_node_a_thirty_two_character_name' is
* returned. That name will be truncated to 'comment_node_a_thirty_two_char'.
*
* @see core/modules/comment/migrations/d7_comment_type.yml
* @see core/modules/field/migrations/d7_field_instance.yml
*/
#[MigrateProcess('field_bundle')]
class FieldBundle extends ProcessPluginBase implements ContainerFactoryPluginInterface {
/**
* The migrate lookup service.
*
* @var \Drupal\migrate\MigrateLookupInterface
*/
protected $migrateLookup;
/**
* Constructs a ProcessField plugin.
*
* @param array $configuration
* The plugin configuration.
* @param string $plugin_id
* The plugin ID.
* @param mixed $plugin_definition
* The plugin definition.
* @param \Drupal\migrate\MigrateLookupInterface $migrate_lookup
* The migrate lookup service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrateLookupInterface $migrate_lookup) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->migrateLookup = $migrate_lookup;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, ?MigrationInterface $migration = NULL) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('migrate.lookup')
);
}
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
[$entity_type, $bundle] = $value;
$lookup_result = NULL;
// For comment entity types get the destination bundle from the
// d7_comment_type migration, if it exists.
if ($entity_type === 'comment' && $bundle != 'comment_forum') {
$lookup_result = $row->get('@_comment_type');
// Legacy generated migrations will not have the destination property
// '_comment_type'.
if (!$row->hasDestinationProperty('_comment_type')) {
$value = str_replace('comment_node_', '', $bundle);
$migration = 'd7_comment_type';
$lookup_result = $this->migrateLookup->lookup($migration, [$value]);
$lookup_result = empty($lookup_result) ? NULL : reset($lookup_result[0]);
}
}
return $lookup_result ? $lookup_result : $bundle;
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Drupal\field\Plugin\migrate\process\d7;
use Drupal\migrate\Attribute\MigrateProcess;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
#[MigrateProcess('d7_field_instance_defaults')]
class FieldInstanceDefaults extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
[$default_value, $widget_settings] = $value;
$widget_type = $widget_settings['type'];
$default_value = $default_value ?: [];
// In Drupal 7, the default value for email fields is stored in the key
// 'email' while in Drupal 8 it is stored in the key 'value'.
if ($widget_type == 'email_textfield' && $default_value) {
$default_value[0]['value'] = $default_value[0]['email'];
unset($default_value[0]['email']);
}
if ($widget_type == 'link_field' && $default_value) {
$default_value[0]['uri'] = $default_value[0]['url'];
$default_value[0]['options'] = ['attributes' => []];
unset($default_value[0]['url']);
}
return $default_value;
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Drupal\field\Plugin\migrate\process\d7;
use Drupal\migrate\Attribute\MigrateProcess;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* Determines the settings property and translation for boolean fields.
*/
#[MigrateProcess(
id: "d7_field_instance_option_translation",
handle_multiples: TRUE,
)]
class FieldInstanceOptionTranslation extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
[$type, $data] = $value;
$data = unserialize($data);
$property = $row->getSourceProperty('property');
$option_key = ($property == 0) ? 'off_label' : 'on_label';
$translation = '';
if (isset($data['settings']['allowed_values'])) {
$allowed_values = $data['settings']['allowed_values'];
switch ($type) {
case 'boolean';
if (isset($allowed_values[$property])) {
$translation = $row->getSourceProperty('translation');
break;
}
break;
default:
}
}
return ['settings.' . $option_key, $translation];
}
}

View File

@@ -0,0 +1,139 @@
<?php
namespace Drupal\field\Plugin\migrate\process\d7;
use Drupal\migrate\Attribute\MigrateProcess;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
// cspell:ignore entityreference
/**
* Determines the field instance settings.
*/
#[MigrateProcess(
id: "d7_field_instance_settings"
)]
class FieldInstanceSettings extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
[$instance_settings, $widget_settings, $field_definition] = $value;
$widget_type = $widget_settings['type'];
$field_data = unserialize($field_definition['data']);
// Get taxonomy term reference handler settings from allowed values.
if ($row->getSourceProperty('type') == 'taxonomy_term_reference') {
$instance_settings['handler_settings']['sort'] = [
'field' => '_none',
];
$allowed_values = $row->get('@allowed_values');
foreach ($allowed_values as $allowed_value) {
foreach ($allowed_value as $vocabulary) {
$instance_settings['handler_settings']['target_bundles'][$vocabulary] = $vocabulary;
}
}
}
// Get entityreference handler settings from source field configuration.
if ($row->getSourceProperty('type') == "entityreference") {
$field_settings = $field_data['settings'];
$instance_settings['handler'] = 'default:' . $field_settings['target_type'];
// Transform the sort settings to D8 structure.
$sort = [
'field' => '_none',
'direction' => 'ASC',
];
if (!empty(array_filter($field_settings['handler_settings']['sort']))) {
if ($field_settings['handler_settings']['sort']['type'] == "property") {
$sort = [
'field' => $field_settings['handler_settings']['sort']['property'],
'direction' => $field_settings['handler_settings']['sort']['direction'],
];
}
elseif ($field_settings['handler_settings']['sort']['type'] == "field") {
$sort = [
'field' => $field_settings['handler_settings']['sort']['field'],
'direction' => $field_settings['handler_settings']['sort']['direction'],
];
}
}
if (empty($field_settings['handler_settings']['target_bundles'])) {
$field_settings['handler_settings']['target_bundles'] = NULL;
}
$field_settings['handler_settings']['sort'] = $sort;
$instance_settings['handler_settings'] = $field_settings['handler_settings'];
}
if ($row->getSourceProperty('type') == 'node_reference') {
$instance_settings['handler'] = 'default:node';
$instance_settings['handler_settings'] = [
'sort' => [
'field' => '_none',
'direction' => 'ASC',
],
'target_bundles' => array_filter($field_data['settings']['referenceable_types'] ?? []),
];
}
if ($row->getSourceProperty('type') == 'user_reference') {
$instance_settings['handler'] = 'default:user';
$instance_settings['handler_settings'] = [
'include_anonymous' => TRUE,
'filter' => [
'type' => '_none',
],
'sort' => [
'field' => '_none',
'direction' => 'ASC',
],
'auto_create' => FALSE,
];
if ($row->hasSourceProperty('roles')) {
$instance_settings['handler_settings']['filter']['type'] = 'role';
foreach ($row->get('roles') as $role) {
$instance_settings['handler_settings']['filter']['role'] = [
$role['name'] => $role['name'],
];
}
}
}
// Get the labels for the list_boolean type.
if ($row->getSourceProperty('type') === 'list_boolean') {
if (isset($field_data['settings']['allowed_values'][1])) {
$instance_settings['on_label'] = $field_data['settings']['allowed_values'][1];
}
if (isset($field_data['settings']['allowed_values'][0])) {
$instance_settings['off_label'] = $field_data['settings']['allowed_values'][0];
}
}
switch ($widget_type) {
case 'image_image':
case 'image_miw':
$settings = $instance_settings;
$settings['default_image'] = [
'alt' => '',
'title' => '',
'width' => NULL,
'height' => NULL,
'uuid' => '',
];
break;
default:
$settings = $instance_settings;
}
return $settings;
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace Drupal\field\Plugin\migrate\process\d7;
use Drupal\migrate\Attribute\MigrateProcess;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* Determines the allowed values translation for select lists.
*/
#[MigrateProcess(
id: "d7_field_option_translation",
handle_multiples: TRUE,
)]
class FieldOptionTranslation extends ProcessPluginBase {
/**
* {@inheritdoc}
*
* Get the field default/mapped settings.
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
[$type, $data] = $value;
$data = unserialize($data);
$new_allowed_values = '';
$translation_key = $row->getSourceProperty('property');
if (isset($data['settings']['allowed_values'])) {
$allowed_values = $data['settings']['allowed_values'];
switch ($type) {
case 'list_string':
case 'list_integer':
case 'list_float':
case 'list_text':
if (isset($allowed_values[$translation_key])) {
$new_allowed_values = ['label' => $row->getSourceProperty('translation')];
$translation_key = array_search($translation_key, array_keys($allowed_values));
break;
}
break;
default:
$new_allowed_values = $allowed_values;
}
}
return ["settings.allowed_values.$translation_key", $new_allowed_values];
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace Drupal\field\Plugin\migrate\process\d7;
use Drupal\migrate\Attribute\MigrateProcess;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
#[MigrateProcess('d7_field_settings')]
class FieldSettings extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
$value = $row->getSourceProperty('settings');
switch ($row->getSourceProperty('type')) {
case 'image':
if (!is_array($value['default_image'])) {
$value['default_image'] = ['uuid' => ''];
}
break;
case 'date':
case 'datetime':
case 'datestamp':
$collected_date_attributes = is_numeric(array_keys($value['granularity'])[0])
? $value['granularity']
: array_keys(array_filter($value['granularity']));
if (empty(array_intersect($collected_date_attributes, ['hour', 'minute', 'second']))) {
$value['datetime_type'] = 'date';
}
break;
case 'taxonomy_term_reference':
$value['target_type'] = 'taxonomy_term';
break;
case 'user_reference':
$value['target_type'] = 'user';
break;
default:
break;
}
return $value;
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Drupal\field\Plugin\migrate\process\d7;
use Drupal\migrate\Attribute\MigrateProcess;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Row;
/**
* Gives us a chance to set per field defaults.
*/
#[MigrateProcess('d7_field_type_defaults')]
class FieldTypeDefaults extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
if (is_array($value) && isset($value[1])) {
return $value[1];
}
return $value;
}
}

View File

@@ -0,0 +1,114 @@
<?php
namespace Drupal\field\Plugin\migrate\source\d6;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
// cspell:ignore cnfi
/**
* Drupal 6 field source from database.
*
* For available configuration keys, refer to the parent classes.
*
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
*
* @MigrateSource(
* id = "d6_field",
* source_module = "content"
* )
*/
class Field extends DrupalSqlBase {
/**
* {@inheritdoc}
*/
public function query() {
$query = $this->select('content_node_field', 'cnf')
->fields('cnf', [
'field_name',
'type',
'global_settings',
'required',
'multiple',
'db_storage',
'module',
'db_columns',
'active',
'locked',
])
->distinct();
// Only import fields which are actually being used.
$query->innerJoin('content_node_field_instance', 'cnfi', '[cnfi].[field_name] = [cnf].[field_name]');
return $query;
}
/**
* {@inheritdoc}
*/
public function fields() {
return [
'field_name' => $this->t('Field name'),
'type' => $this->t('Type (text, integer, ....)'),
'widget_type' => $this->t('An instance-specific widget type'),
'global_settings' => $this->t('Global settings. Shared with every field instance.'),
'required' => $this->t('Required'),
'multiple' => $this->t('Multiple'),
'db_storage' => $this->t('DB storage'),
'module' => $this->t('Module'),
'db_columns' => $this->t('DB Columns'),
'active' => $this->t('Active'),
'locked' => $this->t('Locked'),
];
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
// The instance widget_type helps determine what D8 field type we'll use.
// Identify the distinct widget_types being used in D6.
$widget_types = $this->select('content_node_field_instance', 'cnfi')
->fields('cnfi', ['widget_type'])
->condition('field_name', $row->getSourceProperty('field_name'))
->distinct()
->orderBy('widget_type')
->execute()
->fetchCol();
// Arbitrarily use the first widget_type - if there are multiples, let the
// migrator know.
$row->setSourceProperty('widget_type', $widget_types[0]);
if (count($widget_types) > 1) {
$this->migration->getIdMap()->saveMessage(
['field_name' => $row->getSourceProperty('field_name')],
$this->t('Widget types @types are used in Drupal 6 field instances: widget type @selected_type applied to the Drupal 8 base field', [
'@types' => implode(', ', $widget_types),
'@selected_type' => $widget_types[0],
])
);
}
// Unserialize data.
$global_settings = unserialize($row->getSourceProperty('global_settings'));
$db_columns = $row->getSourceProperty('db_columns');
$db_columns = is_string($db_columns) ? unserialize($db_columns) : FALSE;
$row->setSourceProperty('global_settings', $global_settings);
$row->setSourceProperty('db_columns', $db_columns);
return parent::prepareRow($row);
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['field_name'] = [
'type' => 'string',
'alias' => 'cnf',
];
return $ids;
}
}

View File

@@ -0,0 +1,117 @@
<?php
namespace Drupal\field\Plugin\migrate\source\d6;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
// cspell:ignore cnfi nodeapi nsync
/**
* Drupal 6 field instances source from database.
*
* Available configuration keys:
* - node_type: (optional) The content type (machine name) to filter field
* instances retrieved from the source. If omitted, all field instances are
* retrieved.
*
* Example:
*
* @code
* source:
* plugin: d6_field_instance
* node_type: page
* @endcode
*
* In this example field instances of type page are retrieved from the source
* database.
*
* For additional configuration keys, refer to the parent classes.
*
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
*
* @MigrateSource(
* id = "d6_field_instance",
* source_module = "content"
* )
*/
class FieldInstance extends DrupalSqlBase {
/**
* {@inheritdoc}
*/
public function query() {
$query = $this->select('content_node_field_instance', 'cnfi')->fields('cnfi');
if (isset($this->configuration['node_type'])) {
$query->condition('cnfi.type_name', $this->configuration['node_type']);
}
$query->join('content_node_field', 'cnf', '[cnf].[field_name] = [cnfi].[field_name]');
$query->fields('cnf');
$query->orderBy('cnfi.field_name');
$query->orderBy('cnfi.type_name');
return $query;
}
/**
* {@inheritdoc}
*/
public function fields() {
return [
'field_name' => $this->t('The machine name of field.'),
'type_name' => $this->t('Content type where this field is in use.'),
'weight' => $this->t('Weight.'),
'label' => $this->t('A name to show.'),
'widget_type' => $this->t('Widget type.'),
'widget_settings' => $this->t('Serialize data with widget settings.'),
'display_settings' => $this->t('Serialize data with display settings.'),
'description' => $this->t('A description of field.'),
'widget_module' => $this->t('Module that implements widget.'),
'widget_active' => $this->t('Status of widget'),
'module' => $this->t('The module that provides the field.'),
];
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
// Unserialize data.
$widget_settings = unserialize($row->getSourceProperty('widget_settings'));
$display_settings = unserialize($row->getSourceProperty('display_settings'));
$global_settings = unserialize($row->getSourceProperty('global_settings'));
$row->setSourceProperty('widget_settings', $widget_settings);
$row->setSourceProperty('display_settings', $display_settings);
$row->setSourceProperty('global_settings', $global_settings);
// Determine the translatable setting.
$translatable = TRUE;
$synchronized_fields = $this->variableGet('i18nsync_nodeapi_' . $row->getSourceProperty('type_name'), NULL);
if ($synchronized_fields) {
if (in_array($row->getSourceProperty('field_name'), $synchronized_fields)) {
$translatable = FALSE;
}
}
$row->setSourceProperty('translatable', $translatable);
return parent::prepareRow($row);
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids = [
'field_name' => [
'type' => 'string',
'alias' => 'cnfi',
],
'type_name' => [
'type' => 'string',
],
];
return $ids;
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Drupal\field\Plugin\migrate\source\d6;
// cspell:ignore cnfi
/**
* Drupal 6 i18n field instance option labels source from database.
*
* For available configuration keys, refer to the parent classes.
*
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
*
* @MigrateSource(
* id = "d6_field_instance_option_translation",
* source_module = "i18ncck"
* )
*/
class FieldInstanceOptionTranslation extends FieldOptionTranslation {
/**
* {@inheritdoc}
*/
public function query() {
$query = parent::query();
$query->join('content_node_field_instance', 'cnfi', '[cnfi].[field_name] = [cnf].[field_name]');
$query->addField('cnfi', 'type_name');
return $query;
}
/**
* {@inheritdoc}
*/
public function fields() {
$fields = [
'type_name' => $this->t('Type (article, page, ....)'),
];
return parent::fields() + $fields;
}
}

View File

@@ -0,0 +1,103 @@
<?php
namespace Drupal\field\Plugin\migrate\source\d6;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
// cspell:ignore cnfi
/**
* Drupal 6 field instance per form display source from database.
*
* For available configuration keys, refer to the parent classes.
*
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
*
* @MigrateSource(
* id = "d6_field_instance_per_form_display",
* source_module = "content"
* )
*/
class FieldInstancePerFormDisplay extends DrupalSqlBase {
/**
* {@inheritdoc}
*/
protected function initializeIterator() {
$rows = [];
$result = $this->prepareQuery()->execute();
while ($field_row = $result->fetchAssoc()) {
$bundle = $field_row['type_name'];
$field_name = $field_row['field_name'];
$index = "$bundle.$field_name";
$rows[$index]['type_name'] = $bundle;
$rows[$index]['widget_active'] = (bool) $field_row['widget_active'];
$rows[$index]['field_name'] = $field_name;
$rows[$index]['type'] = $field_row['type'];
$rows[$index]['module'] = $field_row['module'];
$rows[$index]['weight'] = $field_row['weight'];
$rows[$index]['widget_type'] = $field_row['widget_type'];
$rows[$index]['widget_settings'] = unserialize($field_row['widget_settings']);
$rows[$index]['display_settings'] = unserialize($field_row['display_settings']);
}
return new \ArrayIterator($rows);
}
/**
* {@inheritdoc}
*/
public function query() {
$query = $this->select('content_node_field_instance', 'cnfi')
->fields('cnfi', [
'field_name',
'type_name',
'weight',
'label',
'widget_type',
'widget_settings',
'display_settings',
'description',
'widget_module',
'widget_active',
])
->fields('cnf', [
'type',
'module',
]);
$query->join('content_node_field', 'cnf', '[cnfi].[field_name] = [cnf].[field_name]');
$query->orderBy('cnfi.weight');
return $query;
}
/**
* {@inheritdoc}
*/
public function fields() {
return [
'field_name' => $this->t('The machine name of field.'),
'type_name' => $this->t('Content type where this field is used.'),
'weight' => $this->t('Weight.'),
'label' => $this->t('A name to show.'),
'widget_type' => $this->t('Widget type.'),
'widget_settings' => $this->t('Serialize data with widget settings.'),
'display_settings' => $this->t('Serialize data with display settings.'),
'description' => $this->t('A description of field.'),
'widget_module' => $this->t('Module that implements widget.'),
'widget_active' => $this->t('Status of widget'),
];
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['type_name']['type'] = 'string';
$ids['field_name']['type'] = 'string';
return $ids;
}
}

View File

@@ -0,0 +1,112 @@
<?php
namespace Drupal\field\Plugin\migrate\source\d6;
use Drupal\node\Plugin\migrate\source\d6\ViewModeBase;
// cspell:ignore cnfi
/**
* Drupal 6 field instance per view mode source from database.
*
* For available configuration keys, refer to the parent classes.
*
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
*
* @MigrateSource(
* id = "d6_field_instance_per_view_mode",
* source_module = "content"
* )
*/
class FieldInstancePerViewMode extends ViewModeBase {
/**
* {@inheritdoc}
*/
protected function initializeIterator() {
$rows = [];
$result = $this->prepareQuery()->execute();
while ($field_row = $result->fetchAssoc()) {
// These are added to every view mode row.
$field_row['display_settings'] = unserialize($field_row['display_settings']);
$field_row['widget_settings'] = unserialize($field_row['widget_settings']);
$bundle = $field_row['type_name'];
$field_name = $field_row['field_name'];
foreach ($this->getViewModes() as $view_mode) {
// Append to the return value if the row has display settings for this
// view mode and the view mode is neither hidden nor excluded.
// @see \Drupal\node\Plugin\migrate\source\d6\ViewMode::initializeIterator()
if (isset($field_row['display_settings'][$view_mode]) && $field_row['display_settings'][$view_mode]['format'] != 'hidden' && empty($field_row['display_settings'][$view_mode]['exclude'])) {
$index = $view_mode . "." . $bundle . "." . $field_name;
$rows[$index]['entity_type'] = 'node';
$rows[$index]['view_mode'] = $view_mode;
$rows[$index]['type_name'] = $bundle;
$rows[$index]['field_name'] = $field_name;
$rows[$index]['type'] = $field_row['type'];
$rows[$index]['module'] = $field_row['module'];
$rows[$index]['weight'] = $field_row['weight'];
$rows[$index]['label'] = $field_row['display_settings']['label']['format'];
$rows[$index]['display_settings'] = $field_row['display_settings'][$view_mode];
$rows[$index]['widget_settings'] = $field_row['widget_settings'];
}
}
}
return new \ArrayIterator($rows);
}
/**
* {@inheritdoc}
*/
public function query() {
$query = $this->select('content_node_field_instance', 'cnfi')
->fields('cnfi', [
'field_name',
'type_name',
'weight',
'label',
'display_settings',
'widget_settings',
])
->fields('cnf', [
'type',
'module',
]);
$query->join('content_node_field', 'cnf', '[cnfi].[field_name] = [cnf].[field_name]');
$query->orderBy('cnfi.weight');
return $query;
}
/**
* {@inheritdoc}
*/
public function fields() {
return [
'field_name' => $this->t('The machine name of field.'),
'type_name' => $this->t('Content type where this field is used.'),
'weight' => $this->t('Weight.'),
'label' => $this->t('A name to show.'),
'widget_type' => $this->t('Widget type.'),
'widget_settings' => $this->t('Serialize data with widget settings.'),
'display_settings' => $this->t('Serialize data with display settings.'),
'description' => $this->t('A description of field.'),
'widget_module' => $this->t('Module that implements widget.'),
'widget_active' => $this->t('Status of widget'),
];
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['type_name']['type'] = 'string';
$ids['view_mode']['type'] = 'string';
$ids['entity_type']['type'] = 'string';
$ids['field_name']['type'] = 'string';
return $ids;
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Drupal\field\Plugin\migrate\source\d6;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
// cspell:ignore objectid
/**
* Drupal 6 i18n field label and description source from database.
*
* For available configuration keys, refer to the parent classes.
*
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
*
* @MigrateSource(
* id = "d6_field_instance_label_description_translation",
* source_module = "i18ncck"
* )
*/
class FieldLabelDescriptionTranslation extends DrupalSqlBase {
/**
* {@inheritdoc}
*/
public function query() {
// Get translations for field labels and descriptions.
$query = $this->select('i18n_strings', 'i18n')
->fields('i18n', ['property', 'objectid', 'type'])
->fields('lt', ['lid', 'translation', 'language'])
->condition('i18n.type', 'field');
$condition = $query->orConditionGroup()
->condition('property', 'widget_label')
->condition('property', 'widget_description');
$query->condition($condition);
$query->innerJoin('locales_target', 'lt', '[lt].[lid] = [i18n].[lid]');
return $query;
}
/**
* {@inheritdoc}
*/
public function fields() {
return [
'property' => $this->t('Profile field ID.'),
'lid' => $this->t('Locales target language ID.'),
'language' => $this->t('Language for this field.'),
'translation' => $this->t('Translation of either the title or explanation.'),
];
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['property']['type'] = 'string';
$ids['language']['type'] = 'string';
$ids['lid']['type'] = 'integer';
$ids['lid']['alias'] = 'lt';
return $ids;
}
}

View File

@@ -0,0 +1,92 @@
<?php
namespace Drupal\field\Plugin\migrate\source\d6;
// cspell:ignore objectid objectindex plid
/**
* Drupal 6 i18n field option labels source from database.
*
* For available configuration keys, refer to the parent classes.
*
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
*
* @MigrateSource(
* id = "d6_field_option_translation",
* source_module = "i18ncck"
* )
*/
class FieldOptionTranslation extends Field {
/**
* {@inheritdoc}
*/
public function query() {
// Get the fields that have field options translations.
$query = $this->select('i18n_strings', 'i18n')
->fields('i18n')
->fields('lt', [
'translation',
'language',
'plid',
'plural',
])
->condition('i18n.type', 'field')
->condition('property', 'option\_%', 'LIKE');
$query->innerJoin('locales_target', 'lt', '[lt].[lid] = [i18n].[lid]');
$query->leftJoin('content_node_field', 'cnf', '[cnf].[field_name] = [i18n].[objectid]');
$query->addField('cnf', 'field_name');
$query->addField('cnf', 'global_settings');
// Minimize changes to the d6_field_option_translation.yml, which is copied
// from d6_field.yml, by ensuring the 'type' property is from
// content_node_field table.
$query->addField('cnf', 'type');
$query->addField('i18n', 'type', 'i18n_type');
// The i18n_string module adds a status column to locale_target. It was
// originally 'status' in a later revision it was named 'i18n_status'.
/** @var \Drupal\Core\Database\Schema $db */
if ($this->getDatabase()->schema()->fieldExists('locales_target', 'status')) {
$query->addField('lt', 'status', 'i18n_status');
}
if ($this->getDatabase()->schema()->fieldExists('locales_target', 'i18n_status')) {
$query->addField('lt', 'i18n_status');
}
return $query;
}
/**
* {@inheritdoc}
*/
public function fields() {
$fields = [
'property' => $this->t('Option ID.'),
'objectid' => $this->t('Object ID'),
'objectindex' => $this->t('Integer value of Object ID'),
'format' => $this->t('The input format used by this string'),
'lid' => $this->t('Source string ID'),
'language' => $this->t('Language code'),
'translation' => $this->t('Translation of the option'),
'plid' => $this->t('Parent lid'),
'plural' => $this->t('Plural index number in case of plural strings'),
];
return parent::fields() + $fields;
}
/**
* {@inheritdoc}
*/
/**
* {@inheritdoc}
*/
public function getIds() {
return parent::getIds() +
[
'language' => ['type' => 'string'],
'property' => ['type' => 'string'],
];
}
}

View File

@@ -0,0 +1,113 @@
<?php
namespace Drupal\field\Plugin\migrate\source\d7;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
/**
* Drupal 7 field source from database.
*
* If the Drupal 7 Title module is enabled, the fields it provides are not
* migrated. The values of those fields will be migrated to the base fields they
* were replacing.
*
* For available configuration keys, refer to the parent classes.
*
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
*
* @MigrateSource(
* id = "d7_field",
* source_module = "field_sql_storage"
* )
*/
class Field extends DrupalSqlBase {
/**
* {@inheritdoc}
*/
public function query() {
$query = $this->select('field_config', 'fc')
->distinct()
->fields('fc')
->fields('fci', ['entity_type'])
->condition('fc.active', 1)
->condition('fc.storage_active', 1)
->condition('fc.deleted', 0)
->condition('fci.deleted', 0);
$query->join('field_config_instance', 'fci', '[fc].[id] = [fci].[field_id]');
// The Title module fields are not migrated.
if ($this->moduleExists('title')) {
$title_fields = [
'title_field',
'name_field',
'description_field',
'subject_field',
];
$query->condition('fc.field_name', $title_fields, 'NOT IN');
}
return $query;
}
/**
* {@inheritdoc}
*/
public function fields() {
return [
'id' => $this->t('The field ID.'),
'field_name' => $this->t('The field name.'),
'entity_type' => $this->t('Entity type'),
'type' => $this->t('The field type.'),
'module' => $this->t('The module that implements the field type.'),
'active' => $this->t('The field status.'),
'storage_type' => $this->t('The field storage type.'),
'storage_module' => $this->t('The module that implements the field storage type.'),
'storage_active' => $this->t('The field storage status.'),
'locked' => $this->t('Locked'),
'data' => $this->t('The field data.'),
'cardinality' => $this->t('Cardinality'),
'translatable' => $this->t('Translatable'),
'deleted' => $this->t('Deleted'),
'instances' => $this->t('The field instances.'),
];
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row, $keep = TRUE) {
foreach (unserialize($row->getSourceProperty('data')) as $key => $value) {
$row->setSourceProperty($key, $value);
}
$instances = $this->select('field_config_instance', 'fci')
->fields('fci')
->condition('field_name', $row->getSourceProperty('field_name'))
->condition('entity_type', $row->getSourceProperty('entity_type'))
->execute()
->fetchAll();
$row->setSourceProperty('instances', $instances);
return parent::prepareRow($row);
}
/**
* {@inheritdoc}
*/
public function getIds() {
return [
'field_name' => [
'type' => 'string',
'alias' => 'fc',
],
'entity_type' => [
'type' => 'string',
'alias' => 'fci',
],
];
}
}

View File

@@ -0,0 +1,262 @@
<?php
namespace Drupal\field\Plugin\migrate\source\d7;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
// cspell:ignore localizable
/**
* Drupal 7 field instances source from database.
*
* If the Drupal 7 Title module is enabled, the fields it provides are not
* migrated. The values of those fields will be migrated to the base fields they
* were replacing.
*
* Available configuration keys:
* - entity_type: (optional) The entity type (machine name) to filter field
* instances retrieved from the source. If omitted, all field instances are
* retrieved.
* - bundle: (optional) The bundle machine name to filter field instances
* retrieved from the source. It should be used in combination with
* entity_type property and will be ignored otherwise.
*
* Examples:
*
* @code
* source:
* plugin: d7_field_instance
* entity_type: node
* @endcode
*
* In this example field instances of node entity type are retrieved from the
* source database.
*
* @code
* source:
* plugin: d7_field_instance
* entity_type: node
* bundle: page
* @endcode
*
* In this example field instances of page content type are retrieved from the
* source database.
*
* For additional configuration keys, refer to the parent classes.
*
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
*
* @MigrateSource(
* id = "d7_field_instance",
* source_module = "field"
* )
*/
class FieldInstance extends DrupalSqlBase {
/**
* {@inheritdoc}
*/
public function query() {
$query = $this->select('field_config_instance', 'fci')
->fields('fci')
->fields('fc', ['type', 'translatable'])
->condition('fc.active', 1)
->condition('fc.storage_active', 1)
->condition('fc.deleted', 0)
->condition('fci.deleted', 0);
$query->join('field_config', 'fc', '[fci].[field_id] = [fc].[id]');
// Optionally filter by entity type and bundle.
if (isset($this->configuration['entity_type'])) {
$query->condition('fci.entity_type', $this->configuration['entity_type']);
if (isset($this->configuration['bundle'])) {
$query->condition('fci.bundle', $this->configuration['bundle']);
}
}
// The Title module fields are not migrated.
if ($this->moduleExists('title')) {
$title_fields = [
'title_field',
'name_field',
'description_field',
'subject_field',
];
$query->condition('fc.field_name', $title_fields, 'NOT IN');
}
return $query;
}
/**
* {@inheritdoc}
*/
protected function initializeIterator() {
$results = $this->prepareQuery()->execute()->fetchAll();
// Group all instances by their base field.
$instances = [];
foreach ($results as $result) {
$instances[$result['field_id']][] = $result;
}
// Add the array of all instances using the same base field to each row.
$rows = [];
foreach ($results as $result) {
$result['instances'] = $instances[$result['field_id']];
$rows[] = $result;
}
return new \ArrayIterator($rows);
}
/**
* {@inheritdoc}
*/
public function fields() {
return [
'id' => $this->t('The field instance ID.'),
'field_id' => $this->t('The field ID.'),
'field_name' => $this->t('The field name.'),
'entity_type' => $this->t('The entity type.'),
'bundle' => $this->t('The entity bundle.'),
'data' => $this->t('The field instance data.'),
'deleted' => $this->t('Deleted'),
'type' => $this->t('The field type'),
'instances' => $this->t('The field instances.'),
'field_definition' => $this->t('The field definition.'),
];
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
foreach (unserialize($row->getSourceProperty('data')) as $key => $value) {
$row->setSourceProperty($key, $value);
}
$field_definition = $this->select('field_config', 'fc')
->fields('fc')
->condition('id', $row->getSourceProperty('field_id'))
->execute()
->fetch();
$row->setSourceProperty('field_definition', $field_definition);
// Determine the translatable setting.
$translatable = FALSE;
if ($row->getSourceProperty('entity_type') == 'node') {
$language_content_type_bundle = (int) $this->variableGet('language_content_type_' . $row->getSourceProperty('bundle'), 0);
// language_content_type_[bundle] may be
// - 0: no language support
// - 1: language assignment support
// - 2: node translation support
// - 4: entity translation support
if ($language_content_type_bundle === 2 || ($language_content_type_bundle === 4 && $row->getSourceProperty('translatable'))) {
$translatable = TRUE;
}
}
else {
// This is not a node entity. Get the translatable value from the source
// field_config table.
$field_data = unserialize($field_definition['data']);
$translatable = $field_data['translatable'];
}
// Check if this is an i18n synchronized field.
$synchronized_fields = $this->variableGet('i18n_sync_node_type_' . $row->getSourceProperty('bundle'), NULL);
if ($synchronized_fields) {
if (in_array($row->getSourceProperty('field_name'), $synchronized_fields)) {
$translatable = FALSE;
}
}
$row->setSourceProperty('translatable', $translatable);
// Get the vid for each allowed value for taxonomy term reference fields
// which is used in a migration_lookup in the process pipeline.
if ($row->getSourceProperty('type') == 'taxonomy_term_reference') {
$vocabulary = [];
$data = unserialize($field_definition['data']);
foreach ($data['settings']['allowed_values'] as $allowed_value) {
$vocabulary[] = $allowed_value['vocabulary'];
}
$query = $this->select('taxonomy_vocabulary', 'v')
->fields('v', ['vid'])
->condition('machine_name', $vocabulary, 'IN');
$allowed_vid = $query->execute()->fetchAllAssoc('vid');
$row->setSourceProperty('allowed_vid', $allowed_vid);
// If there is an i18n_mode use it to determine if this field is
// translatable. It is TRUE for i18n_modes 'Vocab Fixed' and 'Translate',
// for all others it is FALSE. When there is a term reference field with
// two vocabularies where one vocabulary is translatable and other is not
// the field itself is set to not translatable. Note mode '5' is not used
// for taxonomy but is listed here for completeness.
// - 0: No multilingual options.
// - 1: Localize. Localizable object.
// - 2: Fixed Language.
// - 4: Translate. Multilingual objects.
// - 5: Objects are translatable, if they have language or localizable
// if not)
if ($this->getDatabase()
->schema()
->fieldExists('taxonomy_vocabulary', 'i18n_mode')) {
$query = $this->select('taxonomy_vocabulary', 'v')
->fields('v', ['i18n_mode'])
->condition('machine_name', $vocabulary, 'IN');
$results = $query->execute()->fetchAllAssoc('i18n_mode');
$translatable = FALSE;
foreach ($results as $result) {
if ($result['i18n_mode'] == '2' || $result['i18n_mode'] == '4') {
$translatable = TRUE;
}
}
$row->setSourceProperty('translatable', $translatable);
}
}
// Get the user roles for user reference fields.
if ($row->getSourceProperty('type') == 'user_reference') {
$data = unserialize($field_definition['data']);
if (!empty($data['settings']['referenceable_roles'])) {
$rid = $data['settings']['referenceable_roles'];
$query = $this->select('role', 'r')->fields('r')
->condition('rid', $rid, 'IN');
$results = $query->execute()->fetchAll();
$row->setSourceProperty('roles', $results);
}
}
return parent::prepareRow($row);
}
/**
* {@inheritdoc}
*/
public function getIds() {
return [
'entity_type' => [
'type' => 'string',
'alias' => 'fci',
],
'bundle' => [
'type' => 'string',
'alias' => 'fci',
],
'field_name' => [
'type' => 'string',
'alias' => 'fci',
],
];
}
/**
* {@inheritdoc}
*/
protected function doCount() {
return $this->initializeIterator()->count();
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Drupal\field\Plugin\migrate\source\d7;
/**
* Drupal 7 field instance per form display source from database.
*
* For available configuration keys, refer to the parent classes.
*
* @see \Drupal\field\Plugin\migrate\source\d7\FieldInstance
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
*
* @MigrateSource(
* id = "d7_field_instance_per_form_display",
* source_module = "field"
* )
*/
class FieldInstancePerFormDisplay extends FieldInstance {
/**
* {@inheritdoc}
*/
public function getIds() {
return [
'bundle' => [
'type' => 'string',
],
'field_name' => [
'type' => 'string',
'alias' => 'fci',
],
];
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace Drupal\field\Plugin\migrate\source\d7;
/**
* Drupal 7 field instance per view mode source class.
*
* For available configuration keys, refer to the parent classes.
*
* @see \Drupal\field\Plugin\migrate\source\d7\FieldInstance
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
*
* @MigrateSource(
* id = "d7_field_instance_per_view_mode",
* source_module = "field"
* )
*/
class FieldInstancePerViewMode extends FieldInstance {
/**
* {@inheritdoc}
*/
protected function initializeIterator() {
$instances = parent::initializeIterator();
$rows = [];
foreach ($instances->getArrayCopy() as $instance) {
$data = unserialize($instance['data']);
foreach ($data['display'] as $view_mode => $formatter) {
$rows[] = array_merge($instance, [
'view_mode' => $view_mode,
'formatter' => $formatter,
]);
}
}
return new \ArrayIterator($rows);
}
/**
* {@inheritdoc}
*/
public function fields() {
return array_merge(parent::fields(), [
'view_mode' => $this->t('The original machine name of the view mode.'),
'formatter' => $this->t('The formatter settings.'),
]);
}
/**
* {@inheritdoc}
*/
public function getIds() {
return [
'entity_type' => [
'type' => 'string',
],
'bundle' => [
'type' => 'string',
],
'view_mode' => [
'type' => 'string',
],
'field_name' => [
'type' => 'string',
],
];
}
}

View File

@@ -0,0 +1,98 @@
<?php
namespace Drupal\field\Plugin\migrate\source\d7;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
// cspell:ignore objectid objectindex plid textgroup
/**
* Drupal 7 i18n field label and description source from database.
*
* For available configuration keys, refer to the parent classes.
*
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
*
* @MigrateSource(
* id = "d7_field_instance_label_description_translation",
* source_module = "i18n_field"
* )
*/
class FieldLabelDescriptionTranslation extends DrupalSqlBase {
/**
* {@inheritdoc}
*/
public function query() {
// Get translations for field labels and descriptions.
$query = $this->select('i18n_string', 'i18n')
->fields('i18n')
->fields('lt', [
'lid',
'translation',
'language',
'plid',
'plural',
'i18n_status',
])
->fields('fci', [
'id',
'field_id',
'field_name',
'entity_type',
'bundle',
'data',
'deleted',
])
->condition('i18n.textgroup', 'field');
$condition = $query->orConditionGroup()
->condition('textgroup', 'field')
->condition('objectid', '#allowed_values', '!=');
$query->condition($condition);
$query->innerJoin('locales_target', 'lt', '[lt].[lid] = [i18n].[lid]');
$query->leftJoin('field_config_instance', 'fci', '[fci].[bundle] = [i18n].[objectid] AND [fci].[field_name] = [i18n].[type]');
return $query;
}
/**
* {@inheritdoc}
*/
public function fields() {
return [
'lid' => $this->t('Locales target language ID.'),
'textgroup' => $this->t('A module defined group of translations'),
'context' => $this->t('Full string ID for quick search: type:objectid:property.'),
'objectid' => $this->t('Object ID'),
'type' => $this->t('Object type for this string'),
'property' => $this->t('Object property for this string'),
'objectindex' => $this->t('Integer value of Object ID'),
'format' => $this->t('The {filter_format}.format of the string'),
'translation' => $this->t('Translation'),
'language' => $this->t('Language code'),
'plid' => $this->t('Parent lid'),
'plural' => $this->t('Plural index number'),
'i18n_status' => $this->t('Translation needs update'),
'id' => $this->t('The field instance ID.'),
'field_id' => $this->t('The field ID.'),
'field_name' => $this->t('The field name.'),
'entity_type' => $this->t('The entity type.'),
'bundle' => $this->t('The entity bundle.'),
'data' => $this->t('The field instance data.'),
'deleted' => $this->t('Deleted'),
];
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['property']['type'] = 'string';
$ids['language']['type'] = 'string';
$ids['lid']['type'] = 'integer';
$ids['lid']['alias'] = 'lt';
return $ids;
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace Drupal\field\Plugin\migrate\source\d7;
// cspell:ignore objectid objectindex plid textgroup
/**
* Drupal 7 i18n field option label source from database.
*
* For available configuration keys, refer to the parent classes.
*
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
*
* @MigrateSource(
* id = "d7_field_option_translation",
* source_module = "i18n_field"
* )
*/
class FieldOptionTranslation extends Field {
/**
* {@inheritdoc}
*/
public function query() {
$query = parent::query();
$query->leftJoin('i18n_string', 'i18n', '[i18n].[type] = [fc].[field_name]');
$query->innerJoin('locales_target', 'lt', '[lt].[lid] = [i18n].[lid]');
$query->condition('i18n.textgroup', 'field')
->condition('objectid', '#allowed_values');
// Add all i18n and locales_target fields.
$query
->fields('i18n', [
// All table fields except lid and type.
'textgroup',
'context',
'objectid',
'property',
'objectindex',
'format',
])
->fields('lt');
$query->addField('fci', 'bundle');
$query->addField('i18n', 'lid', 'i18n_lid');
$query->addField('i18n', 'type', 'i18n_type');
$query->orderBy('i18n.lid');
return $query;
}
/**
* {@inheritdoc}
*/
public function fields() {
$fields = [
'bundle' => $this->t('Entity bundle'),
'lid' => $this->t('Source string ID'),
'textgroup' => $this->t('A module defined group of translations'),
'context' => $this->t('Full string ID'),
'objectid' => $this->t('Object ID'),
'property' => $this->t('Object property for this string'),
'objectindex' => $this->t('Integer value of Object ID'),
'format' => $this->t('The input format used by this string'),
'translation' => $this->t('Translation of the option'),
'language' => $this->t('Language code'),
'plid' => $this->t('Parent lid'),
'plural' => $this->t('Plural index number in case of plural strings'),
'i18n_status' => $this->t('A boolean indicating whether this translation needs to be updated'),
];
return parent::fields() + $fields;
}
/**
* {@inheritdoc}
*/
public function getIds() {
return parent::getIds() +
[
'language' => ['type' => 'string'],
'property' => ['type' => 'string'],
'bundle' => ['type' => 'string'],
];
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace Drupal\field\Plugin\migrate\source\d7;
/**
* Drupal 7 view mode source from database.
*
* For available configuration keys, refer to the parent classes.
*
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
*
* @MigrateSource(
* id = "d7_view_mode",
* source_module = "field"
* )
*/
class ViewMode extends FieldInstance {
/**
* {@inheritdoc}
*/
protected function initializeIterator() {
$instances = parent::initializeIterator();
$rows = [];
foreach ($instances->getArrayCopy() as $instance) {
$data = unserialize($instance['data']);
foreach (array_keys($data['display']) as $view_mode) {
$key = $instance['entity_type'] . '.' . $view_mode;
$rows[$key] = array_merge($instance, [
'view_mode' => $view_mode,
]);
}
}
return new \ArrayIterator($rows);
}
/**
* {@inheritdoc}
*/
public function fields() {
return array_merge(parent::fields(), [
'view_mode' => $this->t('The view mode ID.'),
]);
}
/**
* {@inheritdoc}
*/
public function getIds() {
return [
'entity_type' => [
'type' => 'string',
],
'view_mode' => [
'type' => 'string',
],
];
}
}

View File

@@ -0,0 +1,88 @@
<?php
// phpcs:ignoreFile
/**
* This file was generated via php core/scripts/generate-proxy-class.php 'Drupal\field\FieldUninstallValidator' "core/modules/field/src".
*/
namespace Drupal\field\ProxyClass {
/**
* Provides a proxy class for \Drupal\field\FieldUninstallValidator.
*
* @see \Drupal\Component\ProxyBuilder
*/
class FieldUninstallValidator implements \Drupal\Core\Extension\ModuleUninstallValidatorInterface
{
use \Drupal\Core\DependencyInjection\DependencySerializationTrait;
/**
* The id of the original proxied service.
*
* @var string
*/
protected $drupalProxyOriginalServiceId;
/**
* The real proxied service, after it was lazy loaded.
*
* @var \Drupal\field\FieldUninstallValidator
*/
protected $service;
/**
* The service container.
*
* @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $container;
/**
* Constructs a ProxyClass Drupal proxy object.
*
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
* The container.
* @param string $drupal_proxy_original_service_id
* The service ID of the original service.
*/
public function __construct(\Symfony\Component\DependencyInjection\ContainerInterface $container, $drupal_proxy_original_service_id)
{
$this->container = $container;
$this->drupalProxyOriginalServiceId = $drupal_proxy_original_service_id;
}
/**
* Lazy loads the real service from the container.
*
* @return object
* Returns the constructed real service.
*/
protected function lazyLoadItself()
{
if (!isset($this->service)) {
$this->service = $this->container->get($this->drupalProxyOriginalServiceId);
}
return $this->service;
}
/**
* {@inheritdoc}
*/
public function validate($module)
{
return $this->lazyLoadItself()->validate($module);
}
/**
* {@inheritdoc}
*/
public function setStringTranslation(\Drupal\Core\StringTranslation\TranslationInterface $translation)
{
return $this->lazyLoadItself()->setStringTranslation($translation);
}
}
}

View File

@@ -0,0 +1,6 @@
test_category:
label: 'Test category'
description: 'This is a test field type category.'
weight: -10
libraries:
- field_plugins_test/test_library

View File

@@ -0,0 +1,12 @@
name: 'Field Plugins Test'
type: module
description: 'Support module for the field and entity display tests.'
package: Testing
# version: VERSION
dependencies:
- drupal:text
# Information added by Drupal.org packaging script on 2024-07-04
version: '10.3.1'
project: 'drupal'
datestamp: 1720094222

View File

@@ -0,0 +1,22 @@
<?php
namespace Drupal\field_plugins_test\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\Attribute\FieldFormatter;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\text\Plugin\Field\FieldFormatter\TextTrimmedFormatter;
/**
* Plugin implementation of the 'field_plugins_test_text_formatter' formatter.
*/
#[FieldFormatter(
id: 'field_plugins_test_text_formatter',
label: new TranslatableMarkup('Test Trimmed'),
field_types: [
'text',
'text_long',
'text_with_summary',
],
)]
class TestTextTrimmedFormatter extends TextTrimmedFormatter {
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Drupal\field_plugins_test\Plugin\Field\FieldWidget;
use Drupal\Core\Field\Attribute\FieldWidget;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\text\Plugin\Field\FieldWidget\TextfieldWidget;
/**
* Plugin implementation of the 'field_plugins_test_text_widget' widget.
*/
#[FieldWidget(
id: 'field_plugins_test_text_widget',
label: new TranslatableMarkup('Test Text field'),
field_types: [
'text',
'string',
],
)]
class TestTextfieldWidget extends TextfieldWidget {
}

View File

@@ -0,0 +1,34 @@
<?php
/**
* @file
* Defines an entity type.
*/
/**
* Implements hook_entity_type_alter().
*/
function field_test_entity_type_alter(array &$entity_types) {
/** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */
foreach (field_test_entity_info_translatable() as $entity_type => $translatable) {
$entity_types[$entity_type]->set('translatable', $translatable);
}
}
/**
* Helper function to enable entity translations.
*/
function field_test_entity_info_translatable($entity_type_id = NULL, $translatable = NULL) {
static $stored_value = [];
if (isset($entity_type_id)) {
$entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager();
$entity_type = $entity_definition_update_manager->getEntityType($entity_type_id);
$stored_value[$entity_type_id] = $translatable;
if ($translatable != $entity_type->isTranslatable()) {
$entity_definition_update_manager->uninstallEntityType($entity_type);
$entity_type->set('translatable', $translatable);
$entity_definition_update_manager->installEntityType($entity_type);
}
}
return $stored_value;
}

View File

@@ -0,0 +1,62 @@
<?php
/**
* @file
* Defines a field type and its formatters and widgets.
*/
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\field\FieldStorageConfigInterface;
/**
* Implements hook_field_widget_info_alter().
*/
function field_test_field_widget_info_alter(&$info) {
$info['test_field_widget_multiple']['field_types'][] = 'test_field';
$info['test_field_widget_multiple']['field_types'][] = 'test_field_with_preconfigured_options';
// Add extra widget when needed for tests.
// @see \Drupal\field\Tests\FormTest::widgetAlterTest().
if ($alter_info = \Drupal::state()->get("field_test.widget_alter_test")) {
if ($alter_info['widget'] === 'test_field_widget_multiple_single_value') {
$info['test_field_widget_multiple_single_value']['field_types'][] = 'test_field';
}
}
}
/**
* Implements hook_field_storage_config_update_forbid().
*/
function field_test_field_storage_config_update_forbid(FieldStorageConfigInterface $field_storage, FieldStorageConfigInterface $prior_field_storage) {
if ($field_storage->getType() == 'test_field' && $field_storage->getSetting('unchangeable') != $prior_field_storage->getSetting('unchangeable')) {
throw new FieldStorageDefinitionUpdateForbiddenException("field_test 'unchangeable' setting cannot be changed'");
}
}
/**
* Sample 'default value' callback.
*/
function field_test_default_value(FieldableEntityInterface $entity, FieldDefinitionInterface $definition) {
return [['value' => 99]];
}
/**
* Implements hook_entity_field_access().
*/
function field_test_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, ?FieldItemListInterface $items = NULL) {
if ($field_definition->getName() == "field_no_{$operation}_access") {
return AccessResult::forbidden();
}
// Only grant view access to test_view_field fields when the user has
// 'view test_view_field content' permission.
if ($field_definition->getName() == 'test_view_field' && $operation == 'view') {
return AccessResult::forbiddenIf(!$account->hasPermission('view test_view_field content'))->cachePerPermissions();
}
return AccessResult::allowed();
}

View File

@@ -0,0 +1,3 @@
field_test_descriptions:
label: 'Field Test'
description: 'Fields for testing descriptions.'

View File

@@ -0,0 +1,12 @@
name: 'Field API Test'
type: module
description: 'Support module for the Field API tests.'
package: Testing
# version: VERSION
dependencies:
- drupal:entity_test
# Information added by Drupal.org packaging script on 2024-07-04
version: '10.3.1'
project: 'drupal'
datestamp: 1720094222

View File

@@ -0,0 +1,277 @@
<?php
/**
* @file
* Helper module for the Field API tests.
*
* The module defines
* - a field type and its formatters and widgets (field_test.field.inc)
* - a field storage backend (field_test.storage.inc)
*
* The main field_test.module file implements generic hooks and provides some
* test helper functions
*/
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Query\QueryInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\field\FieldStorageConfigInterface;
require_once __DIR__ . '/field_test.entity.inc';
require_once __DIR__ . '/field_test.field.inc';
/**
* Store and retrieve keyed data for later verification by unit tests.
*
* This function is a simple in-memory key-value store with the
* distinction that it stores all values for a given key instead of
* just the most recently set value. field_test module hooks call
* this function to record their arguments, keyed by hook name. The
* unit tests later call this function to verify that the correct
* hooks were called and were passed the correct arguments.
*
* This function ignores all calls until the first time it is called
* with $key of NULL. Each time it is called with $key of NULL, it
* erases all previously stored data from its internal cache, but also
* returns the previously stored data to the caller. A typical usage
* scenario is:
*
* @code
* // calls to field_test_memorize() here are ignored
*
* // turn on memorization
* field_test_memorize();
*
* // call some Field API functions that invoke field_test hooks
* FieldStorageConfig::create($field_definition)->save();
*
* // retrieve and reset the memorized hook call data
* $mem = field_test_memorize();
*
* // make sure hook_field_storage_config_create() is invoked correctly
* assertEquals(1, count($mem['field_test_field_storage_config_create']));
* assertEquals([$field], $mem['field_test_field_storage_config_create'][0]);
* @endcode
*
* @param $key
* The key under which to store to $value, or NULL as described above.
* @param $value
* A value to store for $key.
*
* @return array|null
* An array mapping each $key to an array of each $value passed in
* for that key.
*/
function field_test_memorize($key = NULL, $value = NULL) {
static $memorize;
if (!isset($key)) {
$return = $memorize;
$memorize = [];
return $return;
}
if (is_array($memorize)) {
$memorize[$key][] = $value;
}
}
/**
* Memorize calls to field_test_field_storage_config_create().
*/
function field_test_field_storage_config_create(FieldStorageConfigInterface $field_storage) {
$args = func_get_args();
field_test_memorize(__FUNCTION__, $args);
}
/**
* Implements hook_entity_display_build_alter().
*/
function field_test_entity_display_build_alter(&$output, $context) {
$display_options = $context['display']->getComponent('test_field');
if (isset($display_options['settings']['alter'])) {
$output['test_field'][] = ['#markup' => 'field_test_entity_display_build_alter'];
}
if (isset($output['test_field'])) {
$output['test_field'][] = ['#markup' => 'entity language is ' . $context['entity']->language()->getId()];
}
}
/**
* Implements hook_field_widget_single_element_form_alter().
*/
function field_test_field_widget_single_element_form_alter(&$element, FormStateInterface $form_state, $context) {
// Set a message if this is for the form displayed to set default value for
// the field.
if ($context['default']) {
\Drupal::messenger()
->addStatus('From hook_field_widget_single_element_form_alter(): Default form is true.');
}
}
/**
* Implements hook_field_widget_complete_form_alter().
*/
function field_test_field_widget_complete_form_alter(array &$field_widget_complete_form, FormStateInterface $form_state, array $context) {
_field_test_alter_widget("hook_field_widget_complete_form_alter", $field_widget_complete_form, $form_state, $context);
}
/**
* Implements hook_field_widget_complete_WIDGET_TYPE_form_alter().
*/
function field_test_field_widget_complete_test_field_widget_multiple_form_alter(array &$field_widget_complete_form, FormStateInterface $form_state, array $context) {
_field_test_alter_widget("hook_field_widget_complete_WIDGET_TYPE_form_alter", $field_widget_complete_form, $form_state, $context);
}
/**
* Implements hook_field_widget_complete_WIDGET_TYPE_form_alter().
*/
function field_test_field_widget_complete_test_field_widget_multiple_single_value_form_alter(array &$field_widget_complete_form, FormStateInterface $form_state, array $context) {
_field_test_alter_widget("hook_field_widget_complete_WIDGET_TYPE_form_alter", $field_widget_complete_form, $form_state, $context);
}
/**
* Sets up alterations for widget alter tests.
*
* @see \Drupal\field\Tests\FormTest::widgetAlterTest()
*/
function _field_test_alter_widget($hook, array &$field_widget_complete_form, FormStateInterface $form_state, array $context) {
$elements = &$field_widget_complete_form['widget'];
// Set a message if this is for the form displayed to set default value for
// the field.
if ($context['default']) {
\Drupal::messenger()->addStatus("From $hook(): Default form is true.");
}
$alter_info = \Drupal::state()->get("field_test.widget_alter_test");
$name = $context['items']->getFieldDefinition()->getName();
if (!empty($alter_info) && $hook === $alter_info['hook'] && $name === $alter_info['field_name']) {
$elements['#prefix'] = "From $hook(): prefix on $name parent element.";
foreach (Element::children($elements) as $delta => $element) {
$elements[$delta]['#suffix'] = "From $hook(): suffix on $name child element.";
}
}
}
/**
* Implements hook_query_TAG_alter() for tag 'efq_table_prefixing_test'.
*
* @see \Drupal\system\Tests\Entity\EntityFieldQueryTest::testTablePrefixing()
*/
function field_test_query_efq_table_prefixing_test_alter(&$query) {
// Add an additional join onto the entity base table. This will cause an
// exception if the EFQ does not properly prefix the base table.
$query->join('entity_test', 'et2', '[%alias].[id] = [entity_test].[id]');
}
/**
* Implements hook_query_TAG_alter() for tag 'efq_metadata_test'.
*
* @see \Drupal\system\Tests\Entity\EntityQueryTest::testMetaData()
*/
function field_test_query_efq_metadata_test_alter(&$query) {
field_test_memorize(__FUNCTION__, $query->getMetadata('foo'));
}
/**
* Implements hook_entity_extra_field_info_alter().
*/
function field_test_entity_extra_field_info_alter(&$info) {
// Remove all extra fields from the 'no_fields' content type;
unset($info['node']['no_fields']);
}
/**
* Implements hook_entity_bundle_field_info_alter().
*/
function field_test_entity_bundle_field_info_alter(&$fields, EntityTypeInterface $entity_type, $bundle) {
if (($field_name = \Drupal::state()->get('field_test_constraint', FALSE)) && $entity_type->id() == 'entity_test' && $bundle == 'entity_test' && !empty($fields[$field_name])) {
// Set a property constraint using
// \Drupal\Core\Field\FieldConfigInterface::setPropertyConstraints().
$fields[$field_name]->setPropertyConstraints('value', [
'TestField' => [
'value' => -2,
'message' => t('%name does not accept the value @value.', ['%name' => $field_name, '@value' => -2]),
],
]);
// Add a property constraint using
// \Drupal\Core\Field\FieldConfigInterface::addPropertyConstraints().
$fields[$field_name]->addPropertyConstraints('value', [
'Range' => [
'min' => 0,
'max' => 32,
],
]);
}
}
/**
* Implements hook_field_ui_preconfigured_options_alter().
*/
function field_test_field_ui_preconfigured_options_alter(array &$options, $field_type) {
if ($field_type === 'test_field_with_preconfigured_options') {
$options['custom_options']['entity_view_display']['settings'] = [
'test_formatter_setting_multiple' => 'altered dummy test string',
];
}
}
/**
* Implements hook_field_info_entity_type_ui_definitions_alter().
*/
function field_test_field_info_entity_type_ui_definitions_alter(array &$ui_definitions, string $entity_type_id) {
if ($entity_type_id === 'node') {
$ui_definitions['boolean']['label'] = new TranslatableMarkup('Boolean (overridden by alter)');
}
}
function field_test_entity_reference_selection_alter(array &$definitions): void {
if (\Drupal::state()->get('field_test_disable_broken_entity_reference_handler')) {
unset($definitions['broken']);
}
}
/**
* Implements hook_entity_query_alter().
*
* @see Drupal\KernelTests\Core\Entity\EntityQueryTest::testAlterHook
*/
function field_test_entity_query_alter(QueryInterface $query): void {
if ($query->hasTag('entity_query_alter_hook_test')) {
$query->condition('id', '5', '<>');
}
}
/**
* Implements hook_entity_query_ENTITY_TYPE_alter() for 'entity_test_mulrev'.
*
* @see Drupal\KernelTests\Core\Entity\EntityQueryTest::testAlterHook
*/
function field_test_entity_query_entity_test_mulrev_alter(QueryInterface $query): void {
if ($query->hasTag('entity_query_entity_test_mulrev_alter_hook_test')) {
$query->condition('id', '7', '<>');
}
}
/**
* Implements hook_entity_query_tag__TAG_alter() for 'entity_query_alter_tag_test'.
*
* @see Drupal\KernelTests\Core\Entity\EntityQueryTest::testAlterHook
*/
function field_test_entity_query_tag__entity_query_alter_tag_test_alter(QueryInterface $query): void {
$query->condition('id', '13', '<>');
}
/**
* Implements hook_entity_query_tag__ENTITY_TYPE__TAG_alter().
*
* Entity type is 'entity_test_mulrev' and tag is
* 'entity_query_entity_test_mulrev_alter_tag_test'.
*
* @see Drupal\KernelTests\Core\Entity\EntityQueryTest::testAlterHook
*/
function field_test_entity_query_tag__entity_test_mulrev__entity_query_entity_test_mulrev_alter_tag_test_alter(QueryInterface $query): void {
$query->condition('id', '15', '<>');
}

View File

@@ -0,0 +1,6 @@
view test_view_field content:
title: 'View test field content'
description: 'View published test_view_field content.'
administer field_test content:
title: 'Administer field_test content'
description: 'Manage field_test content'

View File

@@ -0,0 +1,27 @@
field_test.entity_nested_form:
path: '/test-entity/nested/{entity_1}/{entity_2}'
defaults:
_title: 'Nested entity form'
_form: '\Drupal\field_test\Form\NestedEntityTestForm'
options:
parameters:
entity_1:
type: 'entity:entity_test'
entity_2:
type: 'entity:entity_test'
requirements:
_permission: 'administer entity_test content'
field_test.entity_constraints_nested_form:
path: '/test-entity-constraints/nested/{entity_1}/{entity_2}'
defaults:
_title: 'Nested entity form'
_form: '\Drupal\field_test\Form\NestedEntityTestForm'
options:
parameters:
entity_1:
type: 'entity:entity_test_constraints'
entity_2:
type: 'entity:entity_test_constraints'
requirements:
_permission: 'administer entity_test content'

View File

@@ -0,0 +1,17 @@
<?php
namespace Drupal\field_test;
/**
* Helper class for \Drupal\Tests\field\Functional\FieldDefaultValueCallbackTest.
*/
class FieldDefaultValueCallbackProvider {
/**
* Helper callback calculating a default value.
*/
public static function calculateDefaultValue() {
return [['value' => 'Calculated default value']];
}
}

View File

@@ -0,0 +1,109 @@
<?php
namespace Drupal\field_test\Form;
use Drupal\Core\Entity\EntityChangedInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
/**
* Provides a form for field_test routes.
*
* @internal
*/
class NestedEntityTestForm extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'field_test_entity_nested_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, ?EntityInterface $entity_1 = NULL, ?EntityInterface $entity_2 = NULL) {
// First entity.
$form_state->set('entity_1', $entity_1);
$form_display_1 = EntityFormDisplay::collectRenderDisplay($entity_1, 'default');
$form_state->set('form_display_1', $form_display_1);
$form_display_1->buildForm($entity_1, $form, $form_state);
// Second entity.
$form_state->set('entity_2', $entity_2);
$form_display_2 = EntityFormDisplay::collectRenderDisplay($entity_2, 'default');
$form_state->set('form_display_2', $form_display_2);
$form['entity_2'] = [
'#type' => 'details',
'#title' => t('Second entity'),
'#tree' => TRUE,
'#parents' => ['entity_2'],
'#weight' => 50,
'#attributes' => ['class' => ['entity-2']],
];
$form_display_2->buildForm($entity_2, $form['entity_2'], $form_state);
if ($entity_2 instanceof EntityChangedInterface) {
// Changed must be sent to the client, for later overwrite error checking.
// @see \Drupal\Tests\field\Functional\NestedFormTest::testNestedEntityFormEntityLevelValidation()
$form['entity_2']['changed'] = [
'#type' => 'hidden',
'#default_value' => $entity_1->getChangedTime(),
];
}
$form['save'] = [
'#type' => 'submit',
'#value' => t('Save'),
'#weight' => 100,
];
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
$entity_1 = $form_state->get('entity_1');
/** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display_1 */
$form_display_1 = $form_state->get('form_display_1');
$form_display_1->extractFormValues($entity_1, $form, $form_state);
$form_display_1->validateFormValues($entity_1, $form, $form_state);
$entity_2 = $form_state->get('entity_2');
/** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display_2 */
$form_display_2 = $form_state->get('form_display_2');
$extracted = $form_display_2->extractFormValues($entity_2, $form['entity_2'], $form_state);
// Extract the values of fields that are not rendered through widgets, by
// simply copying from top-level form values. This leaves the fields that
// are not being edited within this form untouched.
// @see \Drupal\Tests\field\Functional\NestedFormTest::testNestedEntityFormEntityLevelValidation()
foreach ($form_state->getValues()['entity_2'] as $name => $values) {
if ($entity_2->hasField($name) && !isset($extracted[$name])) {
$entity_2->set($name, $values);
}
}
$form_display_2->validateFormValues($entity_2, $form['entity_2'], $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
/** @var \Drupal\Core\Entity\EntityInterface $entity_1 */
$entity_1 = $form_state->get('entity_1');
$entity_1->save();
/** @var \Drupal\Core\Entity\EntityInterface $entity_2 */
$entity_2 = $form_state->get('entity_2');
$entity_2->save();
$this->messenger()->addStatus($this->t('test_entities @id_1 and @id_2 have been updated.', ['@id_1' => $entity_1->id(), '@id_2' => $entity_2->id()]));
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace Drupal\field_test\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\Attribute\FieldFormatter;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\StringTranslation\TranslatableMarkup;
/**
* Plugin implementation of the 'field_test_applicable' formatter.
*
* It is applicable to test_field fields unless their name is 'deny_applicable'.
*/
#[FieldFormatter(
id: 'field_test_applicable',
label: new TranslatableMarkup('Applicable'),
description: new TranslatableMarkup('Applicable formatter'),
field_types: [
'test_field',
],
weight: 15,
)]
class TestFieldApplicableFormatter extends FormatterBase {
/**
* {@inheritdoc}
*/
public static function isApplicable(FieldDefinitionInterface $field_definition) {
return $field_definition->getName() != 'deny_applicable';
}
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
return ['#markup' => 'Nothing to see here'];
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace Drupal\field_test\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\Attribute\FieldFormatter;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
/**
* Plugin implementation of the 'field_test_default' formatter.
*/
#[FieldFormatter(
id: 'field_test_default',
label: new TranslatableMarkup('Default'),
description: new TranslatableMarkup('Default formatter'),
field_types: [
'test_field',
'test_field_with_preconfigured_options',
],
weight: 1,
)]
class TestFieldDefaultFormatter extends FormatterBase {
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return [
'test_formatter_setting' => 'dummy test string',
] + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$element['test_formatter_setting'] = [
'#title' => $this->t('Setting'),
'#type' => 'textfield',
'#size' => 20,
'#default_value' => $this->getSetting('test_formatter_setting'),
'#required' => TRUE,
];
return $element;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = [];
$summary[] = $this->t('@setting: @value', ['@setting' => 'test_formatter_setting', '@value' => $this->getSetting('test_formatter_setting')]);
return $summary;
}
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = [];
foreach ($items as $delta => $item) {
$elements[$delta] = ['#markup' => $this->getSetting('test_formatter_setting') . '|' . $item->value];
}
return $elements;
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace Drupal\field_test\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\Attribute\FieldFormatter;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\StringTranslation\TranslatableMarkup;
/**
* Plugin implementation of the 'field_empty_test' formatter.
*/
#[FieldFormatter(
id: 'field_empty_test',
label: new TranslatableMarkup('Field empty test'),
field_types: [
'test_field',
],
weight: -5,
)]
class TestFieldEmptyFormatter extends FormatterBase {
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return [
'test_empty_string' => '**EMPTY FIELD**',
] + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = [];
if ($items->isEmpty()) {
// For fields with no value, just add the configured "empty" value.
$elements[0] = ['#markup' => $this->getSetting('test_empty_string')];
}
else {
foreach ($items as $delta => $item) {
// This formatter only needs to output raw for testing.
$elements[$delta] = ['#markup' => $item->value];
}
}
return $elements;
}
}

View File

@@ -0,0 +1,74 @@
<?php
namespace Drupal\field_test\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\Attribute\FieldFormatter;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
/**
* Plugin implementation of the 'field_empty_setting' formatter.
*/
#[FieldFormatter(
id: 'field_empty_setting',
label: new TranslatableMarkup('Field empty setting'),
field_types: [
'test_field',
],
weight: -1,
)]
class TestFieldEmptySettingFormatter extends FormatterBase {
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return [
'field_empty_setting' => '',
] + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$element['field_empty_setting'] = [
'#title' => $this->t('Setting'),
'#type' => 'textfield',
'#size' => 20,
'#default_value' => $this->getSetting('field_empty_setting'),
'#required' => TRUE,
];
return $element;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = [];
$setting = $this->getSetting('field_empty_setting');
if (!empty($setting)) {
$summary[] = $this->t('Default empty setting now has a value.');
}
return $summary;
}
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = [];
if (!empty($items)) {
foreach ($items as $delta => $item) {
$elements[$delta] = ['#markup' => $this->getSetting('field_empty_setting')];
}
}
return $elements;
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace Drupal\field_test\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\Attribute\FieldFormatter;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
/**
* Plugin implementation of the 'field_test_multiple' formatter.
*/
#[FieldFormatter(
id: 'field_test_multiple',
label: new TranslatableMarkup('Multiple'),
description: new TranslatableMarkup('Multiple formatter'),
field_types: [
'test_field',
'test_field_with_preconfigured_options',
],
weight: 5,
)]
class TestFieldMultipleFormatter extends FormatterBase {
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return [
'test_formatter_setting_multiple' => 'dummy test string',
'alter' => FALSE,
] + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$element['test_formatter_setting_multiple'] = [
'#title' => $this->t('Setting'),
'#type' => 'textfield',
'#size' => 20,
'#default_value' => $this->getSetting('test_formatter_setting_multiple'),
'#required' => TRUE,
];
return $element;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = [];
$summary[] = $this->t('@setting: @value', ['@setting' => 'test_formatter_setting_multiple', '@value' => $this->getSetting('test_formatter_setting_multiple')]);
return $summary;
}
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = [];
if (!empty($items)) {
$array = [];
foreach ($items as $delta => $item) {
$array[] = $delta . ':' . $item->value;
}
$elements[0] = ['#markup' => $this->getSetting('test_formatter_setting_multiple') . '|' . implode('|', $array)];
}
return $elements;
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Drupal\field_test\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\Attribute\FieldFormatter;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\StringTranslation\TranslatableMarkup;
/**
* Plugin implementation of the 'field_no_settings' formatter.
*/
#[FieldFormatter(
id: 'field_no_settings',
label: new TranslatableMarkup('Field no settings'),
field_types: [
'test_field',
],
weight: -10,
)]
class TestFieldNoSettingsFormatter extends FormatterBase {
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = [];
foreach ($items as $delta => $item) {
// This formatter only needs to output raw for testing.
$elements[$delta] = ['#markup' => $item->value];
}
return $elements;
}
}

View File

@@ -0,0 +1,84 @@
<?php
namespace Drupal\field_test\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\Attribute\FieldFormatter;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
/**
* Plugin implementation of the 'field_test_with_prepare_view' formatter.
*/
#[FieldFormatter(
id: 'field_test_with_prepare_view',
label: new TranslatableMarkup('With prepare step'),
description: new TranslatableMarkup('Tests prepareView() method'),
field_types: [
'test_field',
],
weight: 10,
)]
class TestFieldPrepareViewFormatter extends FormatterBase {
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return [
'test_formatter_setting_additional' => 'dummy test string',
] + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$element['test_formatter_setting_additional'] = [
'#title' => $this->t('Setting'),
'#type' => 'textfield',
'#size' => 20,
'#default_value' => $this->getSetting('test_formatter_setting_additional'),
'#required' => TRUE,
];
return $element;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = [];
$summary[] = $this->t('@setting: @value', ['@setting' => 'test_formatter_setting_additional', '@value' => $this->getSetting('test_formatter_setting_additional')]);
return $summary;
}
/**
* {@inheritdoc}
*/
public function prepareView(array $entities_items) {
foreach ($entities_items as $items) {
foreach ($items as $item) {
// Don't add anything on empty values.
if (!$item->isEmpty()) {
$item->additional_formatter_value = $item->value + 1;
}
}
}
}
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = [];
foreach ($items as $delta => $item) {
$elements[$delta] = ['#markup' => $this->getSetting('test_formatter_setting_additional') . '|' . $item->value . '|' . $item->additional_formatter_value];
}
return $elements;
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Drupal\field_test\Plugin\Field\FieldType;
use Drupal\Core\Field\Attribute\FieldType;
use Drupal\Core\StringTranslation\TranslatableMarkup;
/**
* Defines the 'hidden_test' entity field item.
*/
#[FieldType(
id: "hidden_test_field",
label: new TranslatableMarkup("Hidden from UI test field"),
description: new TranslatableMarkup("Dummy hidden field type used for tests."),
default_widget: "test_field_widget",
default_formatter: "field_test_default",
no_ui: TRUE
)]
class HiddenTestItem extends TestItem {
}

View File

@@ -0,0 +1,180 @@
<?php
namespace Drupal\field_test\Plugin\Field\FieldType;
use Drupal\Core\Field\Attribute\FieldType;
use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\TypedData\DataDefinition;
/**
* Defines the 'test_field' entity field item.
*/
#[FieldType(
id: "test_field",
label: new TranslatableMarkup("Test field"),
default_widget: "test_field_widget",
default_formatter: "field_test_default"
)]
class TestItem extends FieldItemBase {
/**
* {@inheritdoc}
*/
public static function defaultStorageSettings() {
return [
'test_field_storage_setting' => 'dummy test string',
'changeable' => 'a changeable field storage setting',
'unchangeable' => 'an unchangeable field storage setting',
'translatable_storage_setting' => 'a translatable field storage setting',
] + parent::defaultStorageSettings();
}
/**
* {@inheritdoc}
*/
public static function defaultFieldSettings() {
return [
'test_field_setting' => 'dummy test string',
'translatable_field_setting' => 'a translatable field setting',
] + parent::defaultFieldSettings();
}
/**
* {@inheritdoc}
*/
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
$properties['value'] = DataDefinition::create('integer')
->setLabel(new TranslatableMarkup('Test integer value'))
->setRequired(TRUE);
return $properties;
}
/**
* {@inheritdoc}
*/
public static function schema(FieldStorageDefinitionInterface $field_definition) {
return [
'columns' => [
'value' => [
'type' => 'int',
'size' => 'medium',
],
],
'indexes' => [
'value' => ['value'],
],
];
}
/**
* {@inheritdoc}
*/
public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
$form['cardinality_container'][] = [
'#type' => 'html_tag',
'#tag' => 'p',
'#value' => 'Greetings from ' . __METHOD__,
];
$element = [];
$element['test_field_storage_setting'] = [
'#type' => 'textfield',
'#title' => $this->t('Field test field storage setting'),
'#default_value' => $this->getSetting('test_field_storage_setting'),
'#required' => FALSE,
'#description' => $this->t('A dummy form element to simulate field storage setting.'),
];
return $element;
}
/**
* {@inheritdoc}
*/
public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
$element = [];
$element['test_field_setting'] = [
'#type' => 'textfield',
'#title' => $this->t('Field test field setting'),
'#default_value' => $this->getSetting('test_field_setting'),
'#required' => FALSE,
'#description' => $this->t('A dummy form element to simulate field setting.'),
];
return $element;
}
/**
* {@inheritdoc}
*/
public function delete() {
// Reports that delete() method is executed for testing purposes.
field_test_memorize('field_test_field_delete', [$this->getEntity()]);
}
/**
* {@inheritdoc}
*/
public function getConstraints() {
$constraint_manager = \Drupal::typedDataManager()->getValidationConstraintManager();
$constraints = parent::getConstraints();
$constraints[] = $constraint_manager->create('ComplexData', [
'value' => [
'TestField' => [
'value' => -1,
'message' => $this->t('%name does not accept the value @value.', ['%name' => $this->getFieldDefinition()->getLabel(), '@value' => -1]),
],
],
]);
return $constraints;
}
/**
* {@inheritdoc}
*/
public function isEmpty() {
return empty($this->value);
}
/**
* {@inheritdoc}
*/
public static function storageSettingsToConfigData(array $settings) {
$settings['config_data_from_storage_setting'] = 'TRUE';
unset($settings['storage_setting_from_config_data']);
return $settings;
}
/**
* {@inheritdoc}
*/
public static function storageSettingsFromConfigData(array $settings) {
$settings['storage_setting_from_config_data'] = 'TRUE';
unset($settings['config_data_from_storage_setting']);
return $settings;
}
/**
* {@inheritdoc}
*/
public static function fieldSettingsToConfigData(array $settings) {
$settings['config_data_from_field_setting'] = 'TRUE';
unset($settings['field_setting_from_config_data']);
return $settings;
}
/**
* {@inheritdoc}
*/
public static function fieldSettingsFromConfigData(array $settings) {
$settings['field_setting_from_config_data'] = 'TRUE';
unset($settings['config_data_from_field_setting']);
return $settings;
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Drupal\field_test\Plugin\Field\FieldType;
use Drupal\Core\Field\Attribute\FieldType;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
/**
* Defines the 'test_field_with_dependencies' entity field item.
*/
#[FieldType(
id: "test_field_with_dependencies",
label: new TranslatableMarkup("Test field with dependencies"),
description: new TranslatableMarkup("Dummy field type used for tests."),
default_widget: "test_field_widget",
default_formatter: "field_test_default",
config_dependencies: [
"module" => ["system"],
]
)]
class TestItemWithDependencies extends TestItem {
/**
* {@inheritdoc}
*/
public static function calculateDependencies(FieldDefinitionInterface $field_definition) {
return ['content' => ['node:article:uuid']];
}
}

View File

@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace Drupal\field_test\Plugin\Field\FieldType;
use Drupal\Core\Field\Attribute\FieldType;
use Drupal\Core\StringTranslation\TranslatableMarkup;
/**
* Defines the 'test_field_with_multiple_descriptions' entity field item.
*/
#[FieldType(
id: "test_field_with_multiple_descriptions",
label: new TranslatableMarkup("Test field (multiple descriptions"),
description: [
new TranslatableMarkup("This multiple line description needs to use an array"),
new TranslatableMarkup("This second line contains important information"),
],
category: "field_test_descriptions",
default_widget: "test_field_widget",
default_formatter: "field_test_default"
)]
class TestItemWithMultipleDescriptions extends TestItem {
}

View File

@@ -0,0 +1,51 @@
<?php
namespace Drupal\field_test\Plugin\Field\FieldType;
use Drupal\Core\Field\Attribute\FieldType;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\PreconfiguredFieldUiOptionsInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
/**
* Defines the 'test_field_with_preconfigured_options' entity field item.
*/
#[FieldType(
id: "test_field_with_preconfigured_options",
label: new TranslatableMarkup("Test field with preconfigured options"),
description: new TranslatableMarkup("Dummy field type used for tests."),
default_widget: "test_field_widget",
default_formatter: "field_test_default"
)]
class TestItemWithPreconfiguredOptions extends TestItem implements PreconfiguredFieldUiOptionsInterface {
/**
* {@inheritdoc}
*/
public static function getPreconfiguredOptions() {
return [
'custom_options' => [
'label' => new TranslatableMarkup('All custom options'),
'field_storage_config' => [
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
'settings' => [
'test_field_storage_setting' => 'preconfigured_storage_setting',
],
],
'field_config' => [
'required' => TRUE,
'settings' => [
'test_field_setting' => 'preconfigured_field_setting',
],
],
'entity_form_display' => [
'type' => 'test_field_widget_multiple',
],
'entity_view_display' => [
'type' => 'field_test_multiple',
],
],
];
}
}

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace Drupal\field_test\Plugin\Field\FieldType;
use Drupal\Core\Field\Attribute\FieldType;
use Drupal\Core\StringTranslation\TranslatableMarkup;
/**
* Defines the 'test_field_with_single_description' entity field item.
*/
#[FieldType(
id: "test_field_with_single_description",
label: new TranslatableMarkup("Test field (single description"),
description: new TranslatableMarkup("This one-line field description is important for testing"),
category: "field_test_descriptions",
default_widget: "test_field_widget",
default_formatter: "field_test_default"
)]
class TestItemWithSingleDescription extends TestItem {
}

View File

@@ -0,0 +1,50 @@
<?php
namespace Drupal\field_test\Plugin\Field\FieldType;
use Drupal\Core\Field\Attribute\FieldType;
use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\TypedData\DataDefinition;
/**
* Defines the 'test_object_field' entity field item.
*/
#[FieldType(
id: "test_object_field",
label: new TranslatableMarkup("Test object field"),
description: new TranslatableMarkup("Test field type that has an object to test serialization"),
default_widget: "test_object_field_widget",
default_formatter: "object_field_test_default"
)]
class TestObjectItem extends FieldItemBase {
/**
* {@inheritdoc}
*/
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
$properties['value'] = DataDefinition::create('any')
->setLabel(t('Value'))
->setRequired(TRUE);
return $properties;
}
/**
* {@inheritdoc}
*/
public static function schema(FieldStorageDefinitionInterface $field_definition) {
return [
'columns' => [
'value' => [
'description' => 'The object item value.',
'type' => 'blob',
'not null' => TRUE,
'serialize' => TRUE,
],
],
];
}
}

View File

@@ -0,0 +1,118 @@
<?php
namespace Drupal\field_test\Plugin\Field\FieldWidget;
use Drupal\Core\Field\Attribute\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Symfony\Component\Validator\ConstraintViolationInterface;
/**
* Plugin implementation of the 'test_field_widget' widget.
*/
#[FieldWidget(
id: 'test_field_widget',
label: new TranslatableMarkup('Test widget'),
field_types: [
'field_test',
'test_field',
'hidden_test_field',
'test_field_with_preconfigured_options',
],
weight: -10,
)]
class TestFieldWidget extends WidgetBase {
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return [
'test_widget_setting' => 'dummy test string',
'role' => 'anonymous',
'role2' => 'anonymous',
] + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$element['test_widget_setting'] = [
'#type' => 'textfield',
'#title' => $this->t('Field test field widget setting'),
'#description' => $this->t('A dummy form element to simulate field widget setting.'),
'#default_value' => $this->getSetting('test_widget_setting'),
'#required' => FALSE,
];
return $element;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = [];
$summary[] = $this->t('@setting: @value', ['@setting' => 'test_widget_setting', '@value' => $this->getSetting('test_widget_setting')]);
return $summary;
}
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$element += [
'#type' => 'textfield',
'#default_value' => $items[$delta]->value ?? '',
];
return ['value' => $element];
}
/**
* {@inheritdoc}
*/
public function errorElement(array $element, ConstraintViolationInterface $violation, array $form, FormStateInterface $form_state) {
return $element['value'];
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
$dependencies = parent::calculateDependencies();
foreach (['role', 'role2'] as $setting) {
if (!empty($role_id = $this->getSetting($setting))) {
// Create a dependency on the role config entity referenced in settings.
$dependencies['config'][] = "user.role.$role_id";
}
}
return $dependencies;
}
/**
* {@inheritdoc}
*/
public function onDependencyRemoval(array $dependencies) {
$changed = parent::onDependencyRemoval($dependencies);
// Only the setting 'role' is resolved here. When the dependency related to
// this setting is removed, is expected that the widget component will be
// update accordingly in the display entity. The 'role2' setting is
// deliberately left out from being updated. When the dependency
// corresponding to this setting is removed, is expected that the widget
// component will be disabled in the display entity.
if (!empty($role_id = $this->getSetting('role'))) {
if (!empty($dependencies['config']["user.role.$role_id"])) {
$this->setSetting('role', 'anonymous');
$changed = TRUE;
}
}
return $changed;
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace Drupal\field_test\Plugin\Field\FieldWidget;
use Drupal\Core\Field\Attribute\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
/**
* Plugin implementation of the 'test_field_widget_multilingual' widget.
*/
#[FieldWidget(
id: 'test_field_widget_multilingual',
label: new TranslatableMarkup('Test widget - multilingual'),
field_types: ['test_field'],
)]
class TestFieldWidgetMultilingual extends TestFieldWidget {
/**
* {@inheritdoc}
*/
public function form(FieldItemListInterface $items, array &$form, FormStateInterface $form_state, $get_delta = NULL) {
$elements = parent::form($items, $form, $form_state, $get_delta);
$elements['#multilingual'] = TRUE;
return $elements;
}
}

View File

@@ -0,0 +1,113 @@
<?php
namespace Drupal\field_test\Plugin\Field\FieldWidget;
use Drupal\Core\Field\Attribute\FieldWidget;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Symfony\Component\Validator\ConstraintViolationInterface;
/**
* Plugin implementation of the 'test_field_widget_multiple' widget.
*
* The 'field_types' entry is left empty, and is populated through
* hook_field_widget_info_alter().
*
* @see field_test_field_widget_info_alter()
*/
#[FieldWidget(
id: 'test_field_widget_multiple',
label: new TranslatableMarkup('Test widget - multiple'),
multiple_values: TRUE,
weight: 10,
)]
class TestFieldWidgetMultiple extends WidgetBase {
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return [
'test_widget_setting_multiple' => 'dummy test string',
] + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$element['test_widget_setting_multiple'] = [
'#type' => 'textfield',
'#title' => $this->t('Field test field widget setting'),
'#description' => $this->t('A dummy form element to simulate field widget setting.'),
'#default_value' => $this->getSetting('test_widget_setting_multiple'),
'#required' => FALSE,
];
return $element;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = [];
$summary[] = $this->t('@setting: @value', ['@setting' => 'test_widget_setting_multiple', '@value' => $this->getSetting('test_widget_setting_multiple')]);
return $summary;
}
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$values = [];
foreach ($items as $item) {
$values[] = $item->value;
}
$element += [
'#type' => 'textfield',
'#default_value' => implode(', ', $values),
'#element_validate' => [[static::class, 'multipleValidate']],
];
return $element;
}
/**
* {@inheritdoc}
*/
public function errorElement(array $element, ConstraintViolationInterface $violation, array $form, FormStateInterface $form_state) {
return $element;
}
/**
* Element validation helper.
*/
public static function multipleValidate($element, FormStateInterface $form_state) {
$values = array_map('trim', explode(',', $element['#value']));
$items = [];
foreach ($values as $value) {
$items[] = ['value' => $value];
}
$form_state->setValueForElement($element, $items);
}
/**
* Test is the widget is applicable to the field definition.
*
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The field definition that should be checked.
*
* @return bool
* TRUE if the machine name of the field is not equals to
* field_onewidgetfield, FALSE otherwise.
*
* @see \Drupal\Tests\field\Functional\EntityReference\EntityReferenceAdminTest::testAvailableFormatters
*/
public static function isApplicable(FieldDefinitionInterface $field_definition) {
// Returns FALSE if machine name of the field equals field_onewidgetfield.
return $field_definition->getName() != "field_onewidgetfield";
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Drupal\field_test\Plugin\Field\FieldWidget;
use Drupal\Core\Field\Attribute\FieldWidget;
use Drupal\Core\StringTranslation\TranslatableMarkup;
/**
* Plugin implementation of the 'test_field_widget_multiple' widget.
*
* The 'field_types' entry is left empty, and is populated through
* hook_field_widget_info_alter().
*
* @see field_test_field_widget_info_alter()
*/
#[FieldWidget(
id: 'test_field_widget_multiple_single_value',
label: new TranslatableMarkup('Test widget - multiple - single value'),
multiple_values: FALSE,
weight: 10,
)]
class TestFieldWidgetMultipleSingleValues extends TestFieldWidgetMultiple {
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Drupal\field_test\Plugin\Validation\Constraint;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Validation\Attribute\Constraint;
use Symfony\Component\Validator\Constraints\NotEqualTo;
/**
* Checks if a value is not equal.
*/
#[Constraint(
id: 'TestField',
label: new TranslatableMarkup('Test Field', [], ['context' => 'Validation']),
type: ['integer']
)]
class TestFieldConstraint extends NotEqualTo {
/**
* {@inheritdoc}
*/
public function getRequiredOptions() {
return ['value'];
}
/**
* {@inheritdoc}
*/
public function validatedBy() {
return '\Symfony\Component\Validator\Constraints\NotEqualToValidator';
}
}

View File

@@ -0,0 +1,12 @@
name: 'Boolean field Test'
type: module
description: 'Support module for the field and entity display tests.'
package: Testing
# version: VERSION
dependencies:
- drupal:field
# Information added by Drupal.org packaging script on 2024-07-04
version: '10.3.1'
project: 'drupal'
datestamp: 1720094222

View File

@@ -0,0 +1,18 @@
<?php
/**
* @file
* Module for testing denying access to boolean fields.
*/
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Implements hook_entity_field_access().
*/
function field_test_boolean_access_denied_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, ?FieldItemListInterface $items = NULL) {
return AccessResult::forbiddenIf($field_definition->getName() === \Drupal::state()->get('field.test_boolean_field_access_field'));
}

Some files were not shown because too many files have changed in this diff Show More