Move all files to 2017/

This commit is contained in:
Oliver Davies 2025-09-29 22:25:17 +01:00
parent ac7370f67f
commit 2875863330
15717 changed files with 0 additions and 0 deletions

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::entityManager()->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::entityManager()->getDefinition('field_storage_config')->getConfigPrefix();
if (strpos($config_name, $field_storage_config_prefix . '.') === 0) {
$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,380 @@
<?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",
* }
* )
*/
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;
/**
* 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.
*
* @see entity_create()
*/
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) {
$entity_manager = \Drupal::entityManager();
$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.
$entity_manager->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.
$entity_manager->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) {
// Clear the cache upfront, to refresh the results of getBundles().
\Drupal::entityManager()->clearCachedFieldDefinitions();
// Notify the entity storage.
foreach ($fields as $field) {
if (!$field->deleted) {
\Drupal::entityManager()->onFieldDefinitionDelete($field);
}
}
// 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::entityManager()->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}-storage-edit-form"] = 'entity.field_config.' . $this->entity_type . '_storage_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::entityManager()->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 = $this->entityManager()->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("Attempt to create a field {$this->field_name} that does not exist on entity type {$this->entity_type}.");
}
if (!$field_storage_definition instanceof FieldStorageConfigInterface) {
throw new FieldException("Attempt to create 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 static
* 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::entityManager()->getStorage('field_config')->load($entity_type_id . '.' . $bundle . '.' . $field_name);
}
}

View file

@ -0,0 +1,846 @@
<?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\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",
* }
* )
*/
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 Book, Forum, the
* Standard profile and bundle (node type) creation through the UI all 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 hook_field_schema() 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 hook_field_schema(), 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;
/**
* 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.
*
* @see entity_create()
*/
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();
}
/**
* 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_manager = \Drupal::entityManager();
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
// 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_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()}.");
}
// Check that the field type is known.
$field_type = $field_type_manager->getDefinition($this->getType(), FALSE);
if (!$field_type) {
throw new FieldException("Attempt to create a field storage of unknown type {$this->getType()}.");
}
$this->module = $field_type['provider'];
// Notify the entity manager.
$entity_manager->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::entityManager()->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();
$entity_manager = \Drupal::entityManager();
// Some updates are always disallowed.
if ($this->getType() != $this->original->getType()) {
throw new FieldException("Cannot change the field type for an existing field storage.");
}
if ($this->getTargetEntityTypeId() != $this->original->getTargetEntityTypeId()) {
throw new FieldException("Cannot change the entity type for an existing field storage.");
}
// 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 entity manager. A listener can reject the definition
// update as invalid by raising an exception, which stops execution before
// the definition is written to config.
$entity_manager->onFieldStorageDefinitionUpdate($this, $this->original);
}
/**
* {@inheritdoc}
*/
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
if ($update) {
// Invalidate the render cache for all affected entities.
$entity_manager = \Drupal::entityManager();
$entity_type = $this->getTargetEntityTypeId();
if ($entity_manager->hasHandler($entity_type, 'view_builder')) {
$entity_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::entityManager()->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();
// A typical use case for the method is to iterate on the columns, while
// some other use cases rely on identifying the first column with the key()
// function. Since the schema is persisted in the Field object, we take care
// of resetting the array pointer so that the former does not interfere with
// the latter.
reset($schema['columns']);
return $schema['columns'];
}
/**
* {@inheritdoc}
*/
public function getBundles() {
if (!$this->isDeleted()) {
$map = \Drupal::entityManager()->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']) ? $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 '$enforced_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)) {
$items = $entity->get($this->getName());
return \Drupal::service('plugin.manager.field.field_type')->createFieldItem($items, 0);
}
// @todo: Allow setting custom options provider, see
// 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;
}
/**
* {@inheritdoc}
*/
public function isQueryable() {
return TRUE;
}
/**
* Determines whether a field has any data.
*
* @return bool
* TRUE if the field has data for any entity; FALSE otherwise.
*/
public function hasData() {
return \Drupal::entityManager()->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/2074253.
*/
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 static
* 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::entityManager()->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,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\EntityManagerInterface;
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 manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* 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\EntityManagerInterface $entity_manager
* The entity 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, EntityManagerInterface $entity_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->entityManager = $entity_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.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 = isset($conditions['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,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,181 @@
<?php
namespace Drupal\field;
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\EntityManagerInterface;
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 manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* 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\EntityManagerInterface $entity_manager
* The entity 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, EntityManagerInterface $entity_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->entityManager = $entity_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.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 = isset($conditions['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) {
$class = $this->fieldTypeManager->getPluginClass($record['type']);
if (empty($class)) {
$config_id = $this->getPrefix() . $id;
throw new \RuntimeException("Unable to determine class for field type '{$record['type']}' found in the '$config_id' configuration");
}
$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,101 @@
<?php
namespace Drupal\field;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleUninstallValidatorInterface;
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 ModuleUninstallValidatorInterface {
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 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;
}
/**
* 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,100 @@
<?php
namespace Drupal\field\Plugin\migrate\process;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
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\MigrateCckFieldPluginManagerInterface;
use Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* @MigrateProcessPlugin(
* id = "field_type"
* )
*/
class FieldType extends StaticMap implements ContainerFactoryPluginInterface {
/**
* The cckfield plugin manager.
*
* @var \Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface
*/
protected $cckPluginManager;
/**
* 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\MigrateCckFieldPluginManagerInterface $cck_plugin_manager
* The cckfield plugin manager.
* @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, MigrateCckFieldPluginManagerInterface $cck_plugin_manager, MigrateFieldPluginManagerInterface $field_plugin_manager, MigrationInterface $migration = NULL) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->cckPluginManager = $cck_plugin_manager;
$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.cckfield'),
$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) {
try {
$plugin_id = $this->cckPluginManager->getPluginIdFromFieldType($field_type, [], $this->migration);
return $this->cckPluginManager->createInstance($plugin_id, [], $this->migration)->getFieldType($row);
}
catch (PluginNotFoundException $e) {
return parent::transform($value, $migrate_executable, $row, $destination_property);
}
}
}
}

View file

@ -0,0 +1,21 @@
<?php
namespace Drupal\field\Plugin\migrate\process;
@trigger_error('The field_type_defaults process plugin is deprecated in Drupal 8.6.0 and will be removed before Drupal 9.0.0. Use d6_field_type_defaults or d7_field_type_defaults instead. See https://www.drupal.org/node/2944589.', E_USER_DEPRECATED);
use Drupal\field\Plugin\migrate\process\d6\FieldTypeDefaults as D6FieldTypeDefaults;
/**
* BC Layer.
*
* @MigrateProcessPlugin(
* id = "field_type_defaults"
* )
*
* @deprecated in Drupal 8.6.x and will be removed before Drupal 9.0.x.
* Use d6_field_type_defaults or d7_field_type_defaults instead.
*
* @see https://www.drupal.org/node/2944589
*/
class FieldTypeDefaults extends D6FieldTypeDefaults {}

View file

@ -0,0 +1,155 @@
<?php
namespace Drupal\field\Plugin\migrate\process;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
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\MigrateCckFieldPluginManagerInterface;
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;
*
* @MigrateProcessPlugin(
* id = "process_field"
* )
*/
class ProcessField extends ProcessPluginBase implements ContainerFactoryPluginInterface {
/**
* The cckfield plugin manager.
*
* @var \Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface
*/
protected $cckPluginManager;
/**
* 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\MigrateCckFieldPluginManagerInterface $cck_plugin_manager
* The cckfield plugin manager.
* @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, MigrateCckFieldPluginManagerInterface $cck_plugin_manager, MigrateFieldPluginManagerInterface $field_plugin_manager, MigrationInterface $migration = NULL) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->cckPluginManager = $cck_plugin_manager;
$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.cckfield'),
$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) {
try {
return $this->callMethodOnFieldPlugin($this->cckPluginManager, $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,132 @@
<?php
namespace Drupal\field\Plugin\migrate\process\d6;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Row;
/**
* Set the default field settings.
*
* @MigrateProcessPlugin(
* id = "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 isset($map[$type][$format]) ? $map[$type][$format] : [];
}
}

View file

@ -0,0 +1,66 @@
<?php
namespace Drupal\field\Plugin\migrate\process\d6;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* @MigrateProcessPlugin(
* id = "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) {
list($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['url'] = $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\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* Determines the settings property and translation for boolean fields.
*
* @MigrateProcessPlugin(
* 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) {
list($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 = isset($tmp[0]) ? $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,85 @@
<?php
namespace Drupal\field\Plugin\migrate\process\d6;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* @MigrateProcessPlugin(
* id = "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) {
list($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\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* Get the field instance widget settings.
*
* @MigrateProcessPlugin(
* id = "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) {
list($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 = isset($widget_settings['progress_indicator']) ? $widget_settings['progress_indicator'] : 'throbber';
$size = isset($widget_settings['size']) ? $widget_settings['size'] : 60;
$rows = isset($widget_settings['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 isset($settings[$widget_type]) ? $settings[$widget_type] : [];
}
}

View file

@ -0,0 +1,59 @@
<?php
namespace Drupal\field\Plugin\migrate\process\d6;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* Determines the allowed values translation for select lists.
*
* @MigrateProcessPlugin(
* 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) {
list($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 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\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* Get the field settings.
*
* @MigrateProcessPlugin(
* id = "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;
}
list($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 = isset($global_settings['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]] = isset($value[1]) ? $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 isset($settings[$field_type]) ? $settings[$field_type] : [];
}
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace Drupal\field\Plugin\migrate\process\d6;
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.
*
* @MigrateProcessPlugin(
* id = "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,34 @@
<?php
namespace Drupal\field\Plugin\migrate\process\d7;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* @MigrateProcessPlugin(
* id = "d7_field_instance_defaults"
* )
*/
class FieldInstanceDefaults extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
list($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']);
}
return $default_value;
}
}

View file

@ -0,0 +1,74 @@
<?php
namespace Drupal\field\Plugin\migrate\process\d7;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* @MigrateProcessPlugin(
* id = "d7_field_instance_settings"
* )
*/
class FieldInstanceSettings extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
list($instance_settings, $widget_settings, $field_definition) = $value;
$widget_type = $widget_settings['type'];
$field_data = unserialize($field_definition['data']);
$field_settings = $field_data['settings'];
// Get entityreference handler settings from source field configuration.
if ($row->getSourceProperty('type') == "entityreference") {
$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'];
}
switch ($widget_type) {
case 'image_image':
$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,50 @@
<?php
namespace Drupal\field\Plugin\migrate\process\d7;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* @MigrateProcessPlugin(
* id = "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':
if ($value['granularity']['hour'] === 0
&& $value['granularity']['minute'] === 0
&& $value['granularity']['second'] === 0) {
$value['datetime_type'] = 'date';
}
break;
case 'taxonomy_term_reference':
$value['target_type'] = 'taxonomy_term';
break;
default:
break;
}
return $value;
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace Drupal\field\Plugin\migrate\process\d7;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Row;
/**
* Gives us a chance to set per field defaults.
*
* @MigrateProcessPlugin(
* id = "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,106 @@
<?php
namespace Drupal\field\Plugin\migrate\source\d6;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
/**
* Drupal 6 field source from database.
*
* @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 = unserialize($row->getSourceProperty('db_columns'));
$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,81 @@
<?php
namespace Drupal\field\Plugin\migrate\source\d6;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
/**
* Drupal 6 field instances source from database.
*
* @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');
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);
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,35 @@
<?php
namespace Drupal\field\Plugin\migrate\source\d6;
/**
* Gets field instance option label translations.
*
* @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', 'cnf.field_name = cnfi.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,96 @@
<?php
namespace Drupal\field\Plugin\migrate\source\d6;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
/**
* The field instance per form display source class.
*
* @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,105 @@
<?php
namespace Drupal\field\Plugin\migrate\source\d6;
use Drupal\node\Plugin\migrate\source\d6\ViewModeBase;
/**
* The field instance per view mode source class.
*
* @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,60 @@
<?php
namespace Drupal\field\Plugin\migrate\source\d6;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
/**
* Gets field label and description translations.
*
* @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')
->isNotNull('language')
->isNotNull('translation');
$condition = $query->orConditionGroup()
->condition('property', 'widget_label')
->condition('property', 'widget_description');
$query->condition($condition);
$query->leftJoin('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,78 @@
<?php
namespace Drupal\field\Plugin\migrate\source\d6;
/**
* Gets field option label translations.
*
* @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',
'i18n_status',
])
->condition('i18n.type', 'field')
->condition('property', 'option\_%', 'LIKE')
->isNotNull('translation');
$query->leftJoin('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');
// Minimise 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');
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,110 @@
<?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.
*
* @internal
*
* This class is marked as internal and should not be extended. Use
* Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase instead.
*
* @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');
// If the Drupal 7 Title module is enabled, we don't want to migrate the
// fields it provides. The values of those fields will be migrated to the
// base fields they were replacing.
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.'),
'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,166 @@
<?php
namespace Drupal\field\Plugin\migrate\source\d7;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
/**
* Drupal 7 field instances source from database.
*
* @internal
*
* This class is marked as internal and should not be extended. Use
* Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase instead.
*
* @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']);
}
}
// If the Drupal 7 Title module is enabled, we don't want to migrate the
// fields it provides. The values of those fields will be migrated to the
// base fields they were replacing.
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);
$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'];
}
$row->setSourceProperty('translatable', $translatable);
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}
*/
public function count($refresh = FALSE) {
return $this->initializeIterator()->count();
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace Drupal\field\Plugin\migrate\source\d7;
/**
* The field instance per form display source class.
*
* @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,64 @@
<?php
namespace Drupal\field\Plugin\migrate\source\d7;
/**
* The field instance per view mode source class.
*
* @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,58 @@
<?php
namespace Drupal\field\Plugin\migrate\source\d7;
/**
* The view mode source class.
*
* @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
// @codingStandardsIgnoreFile
/**
* 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,19 @@
<?php
namespace Drupal\field\Tests\EntityReference;
use Drupal\Tests\field\Traits\EntityReferenceTestTrait as nonDeprecatedEntityReferenceTestTrait;
/**
* Provides common functionality for the EntityReference test classes.
*
* @deprecated in Drupal 8.6.2 for removal before 9.0.0. Use
* Drupal\Tests\field\Traits\EntityReferenceTestTrait instead.
*
* @see https://www.drupal.org/node/2998888
*/
trait EntityReferenceTestTrait {
use nonDeprecatedEntityReferenceTestTrait;
}

View file

@ -0,0 +1,68 @@
<?php
namespace Drupal\field\Tests;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\simpletest\WebTestBase;
/**
* Parent class for Field API tests.
*
* @deprecated Scheduled for removal in Drupal 9.0.0.
* Use \Drupal\Tests\field\Functional\FieldTestBase instead.
*/
abstract class FieldTestBase extends WebTestBase {
/**
* Generate random values for a field_test field.
*
* @param $cardinality
* Number of values to generate.
* @return
* An array of random values, in the format expected for field values.
*/
public function _generateTestFieldValues($cardinality) {
$values = [];
for ($i = 0; $i < $cardinality; $i++) {
// field_test fields treat 0 as 'empty value'.
$values[$i]['value'] = mt_rand(1, 127);
}
return $values;
}
/**
* Assert that a field has the expected values in an entity.
*
* This function only checks a single column in the field values.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to test.
* @param $field_name
* The name of the field to test
* @param $expected_values
* The array of expected values.
* @param $langcode
* (Optional) The language code for the values. Defaults to
* \Drupal\Core\Language\LanguageInterface::LANGCODE_DEFAULT.
* @param $column
* (Optional) The name of the column to check. Defaults to 'value'.
*/
public function assertFieldValues(EntityInterface $entity, $field_name, $expected_values, $langcode = LanguageInterface::LANGCODE_DEFAULT, $column = 'value') {
// Re-load the entity to make sure we have the latest changes.
$storage = $this->container->get('entity_type.manager')
->getStorage($entity->getEntityTypeId());
$storage->resetCache([$entity->id()]);
$e = $storage->load($entity->id());
$field = $values = $e->getTranslation($langcode)->$field_name;
// Filter out empty values so that they don't mess with the assertions.
$field->filterEmptyItems();
$values = $field->getValue();
$this->assertEqual(count($values), count($expected_values), 'Expected number of values were saved.');
foreach ($expected_values as $key => $value) {
$this->assertEqual($values[$key][$column], $value, format_string('Value @value was saved correctly.', ['@value' => $value]));
}
}
}

View file

@ -0,0 +1,89 @@
<?php
namespace Drupal\field\Tests\Views;
@trigger_error(__NAMESPACE__ . '\FieldTestBase is deprecated in Drupal 8.6.0 and will be removed before Drupal 9.0.0. Instead, use \Drupal\Tests\field\Functional\Views\FieldTestBase. See https://www.drupal.org/node/2971931.', E_USER_DEPRECATED);
use Drupal\field\Entity\FieldConfig;
use Drupal\node\Entity\NodeType;
use Drupal\views\Tests\ViewTestBase;
use Drupal\views\Tests\ViewTestData;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Provides some helper methods for testing fieldapi integration into views.
*
* @todo Test on a generic entity not on a node. What has to be tested:
* - Make sure that every wanted field is added to the according entity type.
* - Make sure the joins are done correctly.
* - Use basic fields and make sure that the full wanted object is built.
* - Use relationships between different entity types, for example node and
* the node author(user).
*
* @deprecated in Drupal 8.6.0. Will be removed before Drupal 9.0.0. Use
* \Drupal\Tests\field\Functional\Views\FieldTestBase instead.
*
* @see https://www.drupal.org/node/2989020
*/
abstract class FieldTestBase extends ViewTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node', 'field_test_views'];
/**
* Stores the field definitions used by the test.
*
* @var array
*/
public $fieldStorages;
/**
* Stores the fields of the field storage. They have the same keys as the
* field storages.
*
* @var array
*/
public $fields;
protected function setUp($import_test_views = TRUE) {
parent::setUp($import_test_views);
// Ensure the page node type exists.
NodeType::create([
'type' => 'page',
'name' => 'page',
])->save();
ViewTestData::createTestViews(get_class($this), ['field_test_views']);
}
public function setUpFieldStorages($amount = 3, $type = 'string') {
// Create three fields.
$field_names = [];
for ($i = 0; $i < $amount; $i++) {
$field_names[$i] = 'field_name_' . $i;
$this->fieldStorages[$i] = FieldStorageConfig::create([
'field_name' => $field_names[$i],
'entity_type' => 'node',
'type' => $type,
]);
$this->fieldStorages[$i]->save();
}
return $field_names;
}
public function setUpFields($bundle = 'page') {
foreach ($this->fieldStorages as $key => $field_storage) {
$this->fields[$key] = FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => $bundle,
]);
$this->fields[$key]->save();
}
}
}