diff --git a/core/modules/datetime/lib/Drupal/datetime/Plugin/field/widget/DatetimeDatelistWidget.php b/core/modules/datetime/lib/Drupal/datetime/Plugin/field/widget/DatetimeDatelistWidget.php index 2be0e27..bc3fd06 100644 --- a/core/modules/datetime/lib/Drupal/datetime/Plugin/field/widget/DatetimeDatelistWidget.php +++ b/core/modules/datetime/lib/Drupal/datetime/Plugin/field/widget/DatetimeDatelistWidget.php @@ -12,7 +12,7 @@ use Drupal\Component\Plugin\Discovery\DiscoveryInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\field\Plugin\PluginSettingsBase; -use Drupal\field\FieldInstance; +use Drupal\field\Plugin\Core\Entity\FieldInstance; use Drupal\Core\Datetime\DrupalDateTime; use Drupal\datetime\DateHelper; diff --git a/core/modules/datetime/lib/Drupal/datetime/Plugin/field/widget/DatetimeDefaultWidget.php b/core/modules/datetime/lib/Drupal/datetime/Plugin/field/widget/DatetimeDefaultWidget.php index 75ccad3..5e22217 100644 --- a/core/modules/datetime/lib/Drupal/datetime/Plugin/field/widget/DatetimeDefaultWidget.php +++ b/core/modules/datetime/lib/Drupal/datetime/Plugin/field/widget/DatetimeDefaultWidget.php @@ -12,7 +12,7 @@ use Drupal\Component\Plugin\Discovery\DiscoveryInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\field\Plugin\PluginSettingsBase; -use Drupal\field\FieldInstance; +use Drupal\field\Plugin\Core\Entity\FieldInstance; use Drupal\Core\Datetime\DrupalDateTime; /** diff --git a/core/modules/datetime/lib/Drupal/datetime/Tests/DateTimeFieldTest.php b/core/modules/datetime/lib/Drupal/datetime/Tests/DateTimeFieldTest.php index 232d019..bb3bddf 100644 --- a/core/modules/datetime/lib/Drupal/datetime/Tests/DateTimeFieldTest.php +++ b/core/modules/datetime/lib/Drupal/datetime/Tests/DateTimeFieldTest.php @@ -48,13 +48,12 @@ function setUp() { $this->drupalLogin($web_user); // Create a field with settings to validate. - $this->field = array( + $this->field = field_create_field(array( 'field_name' => drupal_strtolower($this->randomName()), 'type' => 'datetime', 'settings' => array('datetime_type' => 'date'), - ); - field_create_field($this->field); - $this->instance = array( + )); + $this->instance = field_create_instance(array( 'field_name' => $this->field['field_name'], 'entity_type' => 'test_entity', 'bundle' => 'test_bundle', @@ -64,8 +63,7 @@ function setUp() { 'settings' => array( 'default_value' => 'blank', ), - ); - field_create_instance($this->instance); + )); $this->display_options = array( 'type' => 'datetime_default', @@ -304,6 +302,7 @@ function testDefaultValue() { // Set the default value to 'blank'. $this->instance['settings']['default_value'] = 'blank'; + $this->instance['default_value_function'] = 'datetime_default_value'; field_update_instance($this->instance); // Display creation form. diff --git a/core/modules/edit/lib/Drupal/edit/EditorBase.php b/core/modules/edit/lib/Drupal/edit/EditorBase.php index d2e4d09..a0844d1 100644 --- a/core/modules/edit/lib/Drupal/edit/EditorBase.php +++ b/core/modules/edit/lib/Drupal/edit/EditorBase.php @@ -9,7 +9,7 @@ use Drupal\Component\Plugin\PluginBase; use Drupal\edit\EditorInterface; -use Drupal\field\FieldInstance; +use Drupal\field\Plugin\Core\Entity\FieldInstance; /** * Defines a base editor (Create.js PropertyEditor widget) implementation. diff --git a/core/modules/edit/lib/Drupal/edit/EditorInterface.php b/core/modules/edit/lib/Drupal/edit/EditorInterface.php index 251233a..df4c7f9 100644 --- a/core/modules/edit/lib/Drupal/edit/EditorInterface.php +++ b/core/modules/edit/lib/Drupal/edit/EditorInterface.php @@ -8,7 +8,7 @@ namespace Drupal\edit; use Drupal\Component\Plugin\PluginInspectionInterface; -use Drupal\field\FieldInstance; +use Drupal\field\Plugin\Core\Entity\FieldInstance; /** * Defines an interface for in-place editors (Create.js PropertyEditor widgets). diff --git a/core/modules/edit/lib/Drupal/edit/EditorSelector.php b/core/modules/edit/lib/Drupal/edit/EditorSelector.php index 131eea2..fe71340 100644 --- a/core/modules/edit/lib/Drupal/edit/EditorSelector.php +++ b/core/modules/edit/lib/Drupal/edit/EditorSelector.php @@ -9,7 +9,7 @@ use Drupal\Component\Plugin\PluginManagerInterface; use Drupal\Component\Utility\NestedArray; -use Drupal\field\FieldInstance; +use Drupal\field\Plugin\Core\Entity\FieldInstance; /** * Selects an in-place editor (an Editor plugin) for a field. diff --git a/core/modules/edit/lib/Drupal/edit/EditorSelectorInterface.php b/core/modules/edit/lib/Drupal/edit/EditorSelectorInterface.php index 2c180cd..926c7a6 100644 --- a/core/modules/edit/lib/Drupal/edit/EditorSelectorInterface.php +++ b/core/modules/edit/lib/Drupal/edit/EditorSelectorInterface.php @@ -7,7 +7,7 @@ namespace Drupal\edit; -use Drupal\field\FieldInstance; +use Drupal\field\Plugin\Core\Entity\FieldInstance; /** * Interface for selecting an in-place editor (an Editor plugin) for a field. diff --git a/core/modules/edit/lib/Drupal/edit/MetadataGenerator.php b/core/modules/edit/lib/Drupal/edit/MetadataGenerator.php index 6fc1bdc..b613442 100644 --- a/core/modules/edit/lib/Drupal/edit/MetadataGenerator.php +++ b/core/modules/edit/lib/Drupal/edit/MetadataGenerator.php @@ -9,7 +9,7 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Component\Plugin\PluginManagerInterface; -use Drupal\field\FieldInstance; +use Drupal\field\Plugin\Core\Entity\FieldInstance; use Drupal\edit\Access\EditEntityFieldAccessCheckInterface; diff --git a/core/modules/edit/lib/Drupal/edit/MetadataGeneratorInterface.php b/core/modules/edit/lib/Drupal/edit/MetadataGeneratorInterface.php index 2b6b1d8..71e447d 100644 --- a/core/modules/edit/lib/Drupal/edit/MetadataGeneratorInterface.php +++ b/core/modules/edit/lib/Drupal/edit/MetadataGeneratorInterface.php @@ -8,7 +8,7 @@ namespace Drupal\edit; use Drupal\Core\Entity\EntityInterface; -use Drupal\field\FieldInstance; +use Drupal\field\Plugin\Core\Entity\FieldInstance; /** * Interface for generating in-place editing metadata for an entity field. diff --git a/core/modules/edit/lib/Drupal/edit/Plugin/edit/editor/DirectEditor.php b/core/modules/edit/lib/Drupal/edit/Plugin/edit/editor/DirectEditor.php index 28b86f9..ef6e516 100644 --- a/core/modules/edit/lib/Drupal/edit/Plugin/edit/editor/DirectEditor.php +++ b/core/modules/edit/lib/Drupal/edit/Plugin/edit/editor/DirectEditor.php @@ -9,7 +9,7 @@ use Drupal\edit\EditorBase; use Drupal\Component\Annotation\Plugin; -use Drupal\field\FieldInstance; +use Drupal\field\Plugin\Core\Entity\FieldInstance; /** * Defines the "direct" Create.js PropertyEditor widget. diff --git a/core/modules/edit/lib/Drupal/edit/Plugin/edit/editor/FormEditor.php b/core/modules/edit/lib/Drupal/edit/Plugin/edit/editor/FormEditor.php index 65ddc58..988037f 100644 --- a/core/modules/edit/lib/Drupal/edit/Plugin/edit/editor/FormEditor.php +++ b/core/modules/edit/lib/Drupal/edit/Plugin/edit/editor/FormEditor.php @@ -9,7 +9,7 @@ use Drupal\edit\EditorBase; use Drupal\Component\Annotation\Plugin; -use Drupal\field\FieldInstance; +use Drupal\field\Plugin\Core\Entity\FieldInstance; /** * Defines the "form" Create.js PropertyEditor widget. diff --git a/core/modules/edit/lib/Drupal/edit/Tests/EditTestBase.php b/core/modules/edit/lib/Drupal/edit/Tests/EditTestBase.php index 6ac8b3b..bdf56c5 100644 --- a/core/modules/edit/lib/Drupal/edit/Tests/EditTestBase.php +++ b/core/modules/edit/lib/Drupal/edit/Tests/EditTestBase.php @@ -28,7 +28,6 @@ function setUp() { parent::setUp(); $this->installSchema('system', 'variable'); - $this->installSchema('field', array('field_config', 'field_config_instance')); $this->installSchema('entity_test', array('entity_test', 'entity_test_rev')); // Set default storage backend. @@ -58,12 +57,12 @@ function setUp() { */ function createFieldWithInstance($field_name, $type, $cardinality, $label, $instance_settings, $widget_type, $widget_settings, $formatter_type, $formatter_settings) { $field = $field_name . '_field'; - $this->$field = array( + $this->field = array( 'field_name' => $field_name, 'type' => $type, 'cardinality' => $cardinality, ); - $this->$field_name = field_create_field($this->$field); + $this->$field = field_create_field($this->field); $instance = $field_name . '_instance'; $this->$instance = array( diff --git a/core/modules/edit/tests/modules/lib/Drupal/edit_test/Plugin/edit/editor/WysiwygEditor.php b/core/modules/edit/tests/modules/lib/Drupal/edit_test/Plugin/edit/editor/WysiwygEditor.php index 81387f0..acc0445 100644 --- a/core/modules/edit/tests/modules/lib/Drupal/edit_test/Plugin/edit/editor/WysiwygEditor.php +++ b/core/modules/edit/tests/modules/lib/Drupal/edit_test/Plugin/edit/editor/WysiwygEditor.php @@ -9,7 +9,7 @@ use Drupal\edit\EditorBase; use Drupal\Component\Annotation\Plugin; -use Drupal\field\FieldInstance; +use Drupal\field\Plugin\Core\Entity\FieldInstance; /** * Defines the "wysiwyg" Create.js PropertyEditor widget. diff --git a/core/modules/editor/lib/Drupal/editor/Plugin/edit/editor/Editor.php b/core/modules/editor/lib/Drupal/editor/Plugin/edit/editor/Editor.php index da8f189..a35d254 100644 --- a/core/modules/editor/lib/Drupal/editor/Plugin/edit/editor/Editor.php +++ b/core/modules/editor/lib/Drupal/editor/Plugin/edit/editor/Editor.php @@ -11,7 +11,7 @@ use Drupal\Component\Annotation\Plugin; use Drupal\Core\Annotation\Translation; use Drupal\edit\EditorInterface; -use Drupal\field\FieldInstance; +use Drupal\field\Plugin\Core\Entity\FieldInstance; /** diff --git a/core/modules/entity/lib/Drupal/entity/Tests/EntityDisplayTest.php b/core/modules/entity/lib/Drupal/entity/Tests/EntityDisplayTest.php index eb15f23..1257f48 100644 --- a/core/modules/entity/lib/Drupal/entity/Tests/EntityDisplayTest.php +++ b/core/modules/entity/lib/Drupal/entity/Tests/EntityDisplayTest.php @@ -24,12 +24,6 @@ public static function getInfo() { ); } - protected function setUp() { - parent::setUp(); - - $this->installSchema('field', array('field_config', 'field_config_instance')); - } - /** * Tests basic CRUD operations on EntityDisplay objects. */ diff --git a/core/modules/field/field.api.php b/core/modules/field/field.api.php index cba467d..095c28d 100644 --- a/core/modules/field/field.api.php +++ b/core/modules/field/field.api.php @@ -234,6 +234,9 @@ function hook_field_info_alter(&$info) { * indexes specified by the field-type module. Some storage engines might * not support indexes. * - foreign keys: (optional) An array of Schema API foreign key definitions. + * Note, however, that the field data is not necessarily stored in SQL. + * Also, the possible usage is limited, as you cannot specify another field + * as related, only existing SQL tables, such as {taxonomy_term_data}. */ function hook_field_schema($field) { if ($field['type'] == 'text_long') { @@ -1302,7 +1305,7 @@ function hook_field_storage_details_alter(&$details, $field) { * FIELD_LOAD_REVISION to load the version indicated by each entity. * @param $fields * An array listing the fields to be loaded. The keys of the array are field - * IDs, and the values of the array are the entity IDs (or revision IDs, + * UUIDs, and the values of the array are the entity IDs (or revision IDs, * depending on the $age parameter) to add each field to. * @param $options * An associative array of additional options, with the following keys: @@ -1370,7 +1373,7 @@ function hook_field_storage_load($entity_type, $entities, $age, $fields, $option * FIELD_STORAGE_INSERT when inserting a new entity. * @param $fields * An array listing the fields to be written. The keys and values of the - * array are field IDs. + * array are field UUIDs. */ function hook_field_storage_write(\Drupal\Core\Entity\EntityInterface $entity, $op, $fields) { $id = $entity->id(); @@ -1464,7 +1467,7 @@ function hook_field_storage_write(\Drupal\Core\Entity\EntityInterface $entity, $ * The entity on which to operate. * @param $fields * An array listing the fields to delete. The keys and values of the - * array are field IDs. + * array are field UUIDs. */ function hook_field_storage_delete(\Drupal\Core\Entity\EntityInterface $entity, $fields) { foreach (field_info_instances($entity->entityType(), $entity->bundle()) as $instance) { @@ -1488,7 +1491,7 @@ function hook_field_storage_delete(\Drupal\Core\Entity\EntityInterface $entity, * The entity on which to operate. * @param $fields * An array listing the fields to delete. The keys and values of the - * array are field IDs. + * array are field UUIDs. */ function hook_field_storage_delete_revision(\Drupal\Core\Entity\EntityInterface $entity, $fields) { $vid = $entity->getRevisionId(); @@ -1712,13 +1715,13 @@ function hook_field_storage_delete_instance($instance) { * FIELD_LOAD_CURRENT to load the most recent revision for all fields, or * FIELD_LOAD_REVISION to load the version indicated by each entity. * @param $skip_fields - * An array keyed by field IDs whose data has already been loaded and + * An array keyed by field UUIDs whose data has already been loaded and * therefore should not be loaded again. Add a key to this array to indicate * that your module has already loaded a field. * @param $options * An associative array of additional options, with the following keys: - * - field_id: The field ID that should be loaded. If unset, all fields should - * be loaded. + * - field_id: The field UUID that should be loaded. If unset, all fields + * should be loaded. * - deleted: If TRUE, deleted fields should be loaded as well as non-deleted * fields. If unset or FALSE, only non-deleted fields should be loaded. */ @@ -1735,11 +1738,11 @@ function hook_field_storage_pre_load($entity_type, $entities, $age, &$skip_field * @param \Drupal\Core\Entity\EntityInterface $entity * The entity with fields to save. * @param $skip_fields - * An array keyed by field IDs whose data has already been written and + * An array keyed by field UUIDs whose data has already been written and * therefore should not be written again. The values associated with these * keys are not specified. * @return - * Saved field IDs are set set as keys in $skip_fields. + * Saved field UUIDs are set as keys in $skip_fields. */ function hook_field_storage_pre_insert(\Drupal\Core\Entity\EntityInterface $entity, &$skip_fields) { if ($entity->entityType() == 'node' && $entity->status && _forum_node_check_node_type($entity)) { @@ -1770,11 +1773,11 @@ function hook_field_storage_pre_insert(\Drupal\Core\Entity\EntityInterface $enti * @param \Drupal\Core\Entity\EntityInterface $entity * The entity with fields to save. * @param $skip_fields - * An array keyed by field IDs whose data has already been written and + * An array keyed by field UUIDs whose data has already been written and * therefore should not be written again. The values associated with these * keys are not specified. * @return - * Saved field IDs are set set as keys in $skip_fields. + * Saved field UUIDs are set as keys in $skip_fields. */ function hook_field_storage_pre_update(\Drupal\Core\Entity\EntityInterface $entity, &$skip_fields) { $first_call = &drupal_static(__FUNCTION__, array()); diff --git a/core/modules/field/field.attach.inc b/core/modules/field/field.attach.inc index bf55c16..6bd8026 100644 --- a/core/modules/field/field.attach.inc +++ b/core/modules/field/field.attach.inc @@ -1170,7 +1170,7 @@ function field_attach_insert(EntityInterface $entity) { $storages = array(); foreach (field_info_instances($entity->entityType(), $entity->bundle()) as $instance) { $field = field_info_field_by_id($instance['field_id']); - $field_id = $field['id']; + $field_id = $field['uuid']; $field_name = $field['field_name']; if (!empty($entity->$field_name)) { // Collect the storage backend if the field has not been written yet. @@ -1211,7 +1211,7 @@ function field_attach_update(EntityInterface $entity) { $storages = array(); foreach (field_info_instances($entity->entityType(), $entity->bundle()) as $instance) { $field = field_info_field_by_id($instance['field_id']); - $field_id = $field['id']; + $field_id = $field['uuid']; $field_name = $field['field_name']; // Leave the field untouched if $entity comes with no $field_name property, // but empty the field if it comes as a NULL value or an empty array. @@ -1254,7 +1254,7 @@ function field_attach_delete(EntityInterface $entity) { $storages = array(); foreach (field_info_instances($entity->entityType(), $entity->bundle()) as $instance) { $field = field_info_field_by_id($instance['field_id']); - $field_id = $field['id']; + $field_id = $field['uuid']; $storages[$field['storage']['type']][$field_id] = $field_id; } @@ -1287,7 +1287,7 @@ function field_attach_delete_revision(EntityInterface $entity) { $storages = array(); foreach (field_info_instances($entity->entityType(), $entity->bundle()) as $instance) { $field = field_info_field_by_id($instance['field_id']); - $field_id = $field['id']; + $field_id = $field['uuid']; $storages[$field['storage']['type']][$field_id] = $field_id; } @@ -1531,11 +1531,16 @@ function field_entity_bundle_create($entity_type, $bundle) { * Implements hook_entity_bundle_rename(). */ function field_entity_bundle_rename($entity_type, $bundle_old, $bundle_new) { - db_update('field_config_instance') - ->fields(array('bundle' => $bundle_new)) - ->condition('entity_type', $entity_type) - ->condition('bundle', $bundle_old) - ->execute(); + $instances = field_read_instances(); + foreach ($instances as $instance) { + if ($instance->entity_type == $entity_type && $instance->bundle == $bundle_old) { + $id_new = $instance['entity_type'] . '.' . $bundle_new . '.' . $instance['field_name']; + $instance->id = $id_new; + $instance->bundle = $bundle_new; + $instance->bundle_rename_allowed = TRUE; + $instance->save(); + } + } // Clear the cache. field_cache_clear(); diff --git a/core/modules/field/field.crud.inc b/core/modules/field/field.crud.inc index f6ee8a2..a3a8e44 100644 --- a/core/modules/field/field.crud.inc +++ b/core/modules/field/field.crud.inc @@ -6,6 +6,8 @@ */ use Drupal\Core\Entity\EntityInterface; +use Drupal\field\Plugin\Core\Entity\Field; +use Drupal\field\Plugin\Core\Entity\FieldInstance; use Drupal\field\FieldException; /** @@ -28,8 +30,8 @@ * This function does not bind the field to any bundle; use * field_create_instance() for that. * - * @param $field - * A field definition array. The field_name and type properties are required. + * @param array $field + * A field definition. The field_name and type properties are required. * Other properties, if omitted, will be given the following default values: * - cardinality: 1 * - locked: FALSE @@ -48,157 +50,16 @@ * - settings: each omitted setting is given the default value specified in * hook_field_storage_info(). * - * @return - * The $field array with the id property filled in. + * @return \Drupal\field\Plugin\Core\Entity\Field + * The field entity. * * @throws Drupal\field\FieldException * * See: @link field Field API data structures @endlink. */ -function field_create_field($field) { - // Field name is required. - if (empty($field['field_name'])) { - throw new FieldException('Attempt to create an unnamed field.'); - } - // Field type is required. - if (empty($field['type'])) { - throw new FieldException('Attempt to create a field with no type.'); - } - // Field name cannot contain invalid characters. - if (!preg_match('/^[_a-z]+[_a-z0-9]*$/', $field['field_name'])) { - throw new FieldException('Attempt to create a field with invalid characters. Only lowercase alphanumeric characters and underscores are allowed, and only lowercase letters and underscore are allowed as the first character'); - } - - // Field name cannot be longer than 32 characters. We use drupal_strlen() - // because the DB layer assumes that column widths are given in characters, - // not bytes. - if (drupal_strlen($field['field_name']) > 32) { - throw new FieldException(t('Attempt to create a field with a name longer than 32 characters: %name', - array('%name' => $field['field_name']))); - } - - // Ensure the field name is unique over active and disabled fields. - // We do not care about deleted fields. - $prior_field = field_read_field($field['field_name'], array('include_inactive' => TRUE)); - if (!empty($prior_field)) { - $message = $prior_field['active']? - t('Attempt to create field name %name which already exists and is active.', array('%name' => $field['field_name'])): - t('Attempt to create field name %name which already exists, although it is inactive.', array('%name' => $field['field_name'])); - throw new FieldException($message); - } - - // Disallow reserved field names. This can't prevent all field name - // collisions with existing entity properties, but some is better - // than none. - foreach (entity_get_info() as $type => $info) { - if (in_array($field['field_name'], $info['entity_keys'])) { - throw new FieldException(t('Attempt to create field name %name which is reserved by entity type %type.', array('%name' => $field['field_name'], '%type' => $type))); - } - } - - $field += array( - 'entity_types' => array(), - 'cardinality' => 1, - 'translatable' => FALSE, - 'locked' => FALSE, - 'settings' => array(), - 'storage' => array(), - 'deleted' => 0, - ); - - // Check that the field type is known. - $field_type = field_info_field_types($field['type']); - if (!$field_type) { - throw new FieldException(t('Attempt to create a field of unknown type %type.', array('%type' => $field['type']))); - } - // Create all per-field-type properties (needed here as long as we have - // settings that impact column definitions). - $field['settings'] += field_info_field_settings($field['type']); - $field['module'] = $field_type['module']; - $field['active'] = 1; - - // Provide default storage. - $field['storage'] += array( - 'type' => variable_get('field_storage_default', 'field_sql_storage'), - 'settings' => array(), - ); - // Check that the storage type is known. - $storage_type = field_info_storage_types($field['storage']['type']); - if (!$storage_type) { - throw new FieldException(t('Attempt to create a field with unknown storage type %type.', array('%type' => $field['storage']['type']))); - } - // Provide default storage settings. - $field['storage']['settings'] += field_info_storage_settings($field['storage']['type']); - $field['storage']['module'] = $storage_type['module']; - $field['storage']['active'] = 1; - // Collect storage information. - module_load_install($field['module']); - $schema = (array) module_invoke($field['module'], 'field_schema', $field); - $schema += array('columns' => array(), 'indexes' => array(), 'foreign keys' => array()); - // 'columns' are hardcoded in the field type. - $field['columns'] = $schema['columns']; - if (array_intersect(array_keys($field['columns']), field_reserved_columns())) { - throw new FieldException(t('Illegal field type columns.')); - } - // 'foreign keys' are hardcoded in the field type. - $field['foreign keys'] = $schema['foreign keys']; - // 'indexes' can be both hardcoded in the field type, and specified in the - // incoming $field definition. - $field += array( - 'indexes' => array(), - ); - $field['indexes'] += $schema['indexes']; - - // The serialized 'data' column contains everything from $field that does not - // have its own column and is not automatically populated when the field is - // read. - $data = $field; - unset($data['columns'], $data['field_name'], $data['type'], $data['active'], $data['module'], $data['storage_type'], $data['storage_active'], $data['storage_module'], $data['locked'], $data['cardinality'], $data['deleted']); - // Additionally, do not save the 'bundles' property populated by - // field_info_field(). - unset($data['bundles']); - - $record = array( - 'field_name' => $field['field_name'], - 'type' => $field['type'], - 'module' => $field['module'], - 'active' => $field['active'], - 'storage_type' => $field['storage']['type'], - 'storage_module' => $field['storage']['module'], - 'storage_active' => $field['storage']['active'], - 'locked' => $field['locked'], - 'data' => $data, - 'cardinality' => $field['cardinality'], - 'translatable' => $field['translatable'], - 'deleted' => $field['deleted'], - ); - - // Store the field and get the id back. - drupal_write_record('field_config', $record); - $field['id'] = $record['id']; - - // Invoke hook_field_storage_create_field after the field is - // complete (e.g. it has its id). - try { - // Invoke hook_field_storage_create_field after - // drupal_write_record() sets the field id. - module_invoke($storage_type['module'], 'field_storage_create_field', $field); - } - catch (Exception $e) { - // If storage creation failed, remove the field_config record before - // rethrowing the exception. - db_delete('field_config') - ->condition('id', $field['id']) - ->execute(); - throw $e; - } - - // Clear caches - field_cache_clear(TRUE); - - // Invoke external hooks after the cache is cleared for API consistency. - module_invoke_all('field_create_field', $field); - +function field_create_field(array $field) { + $field = entity_create('field_entity', $field); + $field->save(); return $field; } @@ -212,90 +73,33 @@ function field_create_field($field) { * semantics, or if there are external dependencies on field settings * that cannot be updated. * - * @param $field - * A field structure. $field['field_name'] must provided; it - * identifies the field that will be updated to match this - * structure. Any other properties of the field that are not - * specified in $field will be left unchanged, so it is not - * necessary to pass in a fully populated $field structure. + * @param mixed $field + * Either the \Drupal\field\Plugin\Core\Entity\Field object to update, or a + * field array structure. If the latter, $field['field_name'] must provided; + * it identifies the field that will be updated to match this structure. Any + * other properties of the field that are not specified in $field will be left + * unchanged, so it is not necessary to pass in a fully populated $field + * structure. * * @throws Drupal\field\FieldException * * @see field_create_field() */ function field_update_field($field) { - // Check that the specified field exists. - $prior_field = field_read_field($field['field_name']); - if (empty($prior_field)) { - throw new FieldException('Attempt to update a non-existent field.'); - } - - // Use the prior field values for anything not specifically set by the new - // field to be sure that all values are set. - $field += $prior_field; - $field['settings'] += $prior_field['settings']; - - // Some updates are always disallowed. - if ($field['type'] != $prior_field['type']) { - throw new FieldException("Cannot change an existing field's type."); - } - if ($field['entity_types'] != $prior_field['entity_types']) { - throw new FieldException("Cannot change an existing field's entity_types property."); - } - if ($field['storage']['type'] != $prior_field['storage']['type']) { - throw new FieldException("Cannot change an existing field's storage type."); - } - - // Collect the new storage information, since what is in - // $prior_field may no longer be right. - module_load_install($field['module']); - $schema = (array) module_invoke($field['module'], 'field_schema', $field); - $schema += array('columns' => array(), 'indexes' => array()); - // 'columns' are hardcoded in the field type. - $field['columns'] = $schema['columns']; - // 'indexes' can be both hardcoded in the field type, and specified in the - // incoming $field definition. - $field += array( - 'indexes' => array(), - ); - $field['indexes'] += $schema['indexes']; - - $has_data = field_has_data($field); - - // See if any module forbids the update by throwing an exception. - foreach (module_implements('field_update_forbid') as $module) { - $function = $module . '_field_update_forbid'; - $function($field, $prior_field, $has_data); + // Module developers can still pass in an array of properties. + if (is_array($field)) { + $field_loaded = entity_load('field_entity', $field['field_name']); + if (empty($field_loaded)) { + throw new FieldException('Attempt to update a non-existent field.'); + } + // Merge incoming values. + foreach ($field as $key => $value) { + $field_loaded[$key] = $value; + } + $field = $field_loaded; } - // Tell the storage engine to update the field. Do this before - // saving the new definition since it still might fail. - $storage_type = field_info_storage_types($field['storage']['type']); - module_invoke($storage_type['module'], 'field_storage_update_field', $field, $prior_field, $has_data); - - // Save the new field definition. @todo: refactor with - // field_create_field. - - // The serialized 'data' column contains everything from $field that does not - // have its own column and is not automatically populated when the field is - // read. - $data = $field; - unset($data['columns'], $data['field_name'], $data['type'], $data['locked'], $data['module'], $data['cardinality'], $data['active'], $data['deleted']); - // Additionally, do not save the 'bundles' property populated by - // field_info_field(). - unset($data['bundles']); - - $field['data'] = $data; - - // Store the field and create the id. - $primary_key = array('id'); - drupal_write_record('field_config', $field, $primary_key); - - // Clear caches - field_cache_clear(TRUE); - - // Invoke external hooks after the cache is cleared for API consistency. - module_invoke_all('field_update_field', $field, $prior_field, $has_data); + $field->save(); } /** @@ -324,12 +128,9 @@ function field_read_field($field_name, $include_additional = array()) { /** * Reads in fields that match an array of conditions. * - * @param array $params - * An array of conditions to match against. Keys are columns from the - * 'field_config' table, values are conditions to match. Additionally, - * conditions on the 'entity_type' and 'bundle' columns from the - * 'field_config_instance' table are supported (select fields having an - * instance on a given bundle). + * @param array $conditions + * An array of conditions to match against. Keys are names of properties found + * in field configuration files, and values are conditions to match. * @param array $include_additional * The default behavior of this function is to not return fields that are * inactive or have been deleted. Setting @@ -341,69 +142,71 @@ function field_read_field($field_name, $include_additional = array()) { * $include_additional['include_deleted'] is TRUE, the array is keyed by * field ID, otherwise it is keyed by field name. */ -function field_read_fields($params = array(), $include_additional = array()) { - $query = db_select('field_config', 'fc', array('fetch' => PDO::FETCH_ASSOC)); - $query->fields('fc'); - - // Turn the conditions into a query. - foreach ($params as $key => $value) { - // Allow filtering on the 'entity_type' and 'bundle' columns of the - // field_config_instance table. - if ($key == 'entity_type' || $key == 'bundle') { - if (empty($fci_join)) { - $fci_join = $query->join('field_config_instance', 'fci', 'fc.id = fci.field_id'); - } - $key = 'fci.' . $key; - } - else { - $key = 'fc.' . $key; - } +function field_read_fields($conditions = array(), $include_additional = array()) { + // Include inactive fields if specified in the $include_additional parameter. + $include_inactive = isset($include_additional['include_inactive']) && $include_additional['include_inactive']; + // Include deleted fields if specified either in the $include_additional or + // the $conditions parameters. + $include_deleted = (isset($include_additional['include_deleted']) && $include_additional['include_deleted']) || (isset($conditions['deleted']) && $conditions['deleted']); - $query->condition($key, $value); + // Get fields stored in configuration. + if (isset($conditions['field_name'])) { + // Optimize for the most frequent case where we do have a specific ID. + $fields = entity_load_multiple('field_entity', array($conditions['field_name'])); + } + else { + // No specific ID, we need to examine all existing fields. + $fields = entity_load_multiple('field_entity'); } - if (!isset($include_additional['include_inactive']) || !$include_additional['include_inactive']) { - $query - ->condition('fc.active', 1) - ->condition('fc.storage_active', 1); + // Merge deleted fields (stored in state) if needed. + if ($include_deleted) { + $deleted_fields = Drupal::state()->get('field.field.deleted') ?: array(); + foreach ($deleted_fields as $id => $config) { + $fields[$id] = entity_create('field_entity', $config); + } } - $include_deleted = (isset($include_additional['include_deleted']) && $include_additional['include_deleted']); - if (!$include_deleted) { - $query->condition('fc.deleted', 0); + + // Translate "do not include inactive instances" into actual conditions. + if (!$include_inactive) { + $conditions['active'] = 1; + $conditions['storage.active'] = 1; } - $fields = array(); - $results = $query->execute(); - foreach ($results as $record) { - $field = unserialize($record['data']); - $field['id'] = $record['id']; - $field['field_name'] = $record['field_name']; - $field['type'] = $record['type']; - $field['module'] = $record['module']; - $field['active'] = $record['active']; - $field['storage']['type'] = $record['storage_type']; - $field['storage']['module'] = $record['storage_module']; - $field['storage']['active'] = $record['storage_active']; - $field['locked'] = $record['locked']; - $field['cardinality'] = $record['cardinality']; - $field['translatable'] = $record['translatable']; - $field['deleted'] = $record['deleted']; + // Collect matching fields. + $matching_fields = array(); + foreach ($fields as $field) { + foreach ($conditions as $key => $value) { + // Extract the actual value against which the condition is checked. + switch ($key) { + case 'storage.active': + $checked_value = $field->storage['active']; + break; + + case 'field_name'; + $checked_value = $field->id; + break; + + default: + $checked_value = $field->$key; + break; + } - module_invoke_all('field_read_field', $field); + // Skip to the next field as soon as one condition does not match. + if ($checked_value != $value) { + continue 2; + } + } - // Populate storage information. - module_load_install($field['module']); - $schema = (array) module_invoke($field['module'], 'field_schema', $field); - $schema += array('columns' => array(), 'indexes' => array()); - $field['columns'] = $schema['columns']; + module_invoke_all('field_read_field', $field); - $field_name = $field['field_name']; - if ($include_deleted) { - $field_name = $field['id']; - } - $fields[$field_name] = $field; + // 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 $fields; + + return $matching_fields; } /** @@ -413,36 +216,15 @@ function field_read_fields($params = array(), $include_additional = array()) { * The field name to delete. */ function field_delete_field($field_name) { - // Delete all non-deleted instances. - $field = field_info_field($field_name); - if (isset($field['bundles'])) { - foreach ($field['bundles'] as $entity_type => $bundles) { - foreach ($bundles as $bundle) { - $instance = field_info_instance($entity_type, $field_name, $bundle); - field_delete_instance($instance, FALSE); - } - } + if ($field = field_info_field($field_name)) { + $field->delete(); } - - // Mark field data for deletion. - module_invoke($field['storage']['module'], 'field_storage_delete_field', $field); - - // Mark the field for deletion. - db_update('field_config') - ->fields(array('deleted' => 1)) - ->condition('field_name', $field_name) - ->execute(); - - // Clear the cache. - field_cache_clear(TRUE); - - module_invoke_all('field_delete_field', $field); } /** * Creates an instance of a field, binding it to a bundle. * - * @param $instance + * @param array $instance * A field instance definition array. The field_name, entity_type and * bundle properties are required. Other properties, if omitted, * will be given the following default values: @@ -456,80 +238,27 @@ function field_delete_field($field_name) { * - type: the default widget specified in hook_field_info(). * - settings: each omitted setting is given the default value specified in * hook_field_widget_info(). - * - display: - * Settings for the 'default' view mode will be added if not present, and - * each view mode in the definition will be completed with the following - * default values: - * - label: 'above' - * - type: the default formatter specified in hook_field_info(). - * - settings: each omitted setting is given the default value specified in - * hook_field_formatter_info(). - * View modes not present in the definition are left empty, and the field - * will not be displayed in this mode. * - * @return - * The $instance array with the id property filled in. + * @return \Drupal\field\Plugin\Core\Entity\FieldInstance + * The field instance entity. * * @throws Drupal\field\FieldException * * See: @link field Field API data structures @endlink. */ -function field_create_instance(&$instance) { - $field = field_read_field($instance['field_name']); - if (empty($field)) { - throw new FieldException(t("Attempt to create an instance of a field @field_name that doesn't exist or is currently inactive.", array('@field_name' => $instance['field_name']))); - } - // Check that the required properties exists. - if (empty($instance['entity_type'])) { - throw new FieldException(t('Attempt to create an instance of field @field_name without an entity type.', array('@field_name' => $instance['field_name']))); - } - if (empty($instance['bundle'])) { - throw new FieldException(t('Attempt to create an instance of field @field_name without a bundle.', array('@field_name' => $instance['field_name']))); - } - // Check that the field can be attached to this entity type. - if (!empty($field['entity_types']) && !in_array($instance['entity_type'], $field['entity_types'])) { - throw new FieldException(t('Attempt to create an instance of field @field_name on forbidden entity type @entity_type.', array('@field_name' => $instance['field_name'], '@entity_type' => $instance['entity_type']))); - } - - // Set the field id. - $instance['field_id'] = $field['id']; - - // Note that we do *not* prevent creating a field on non-existing bundles, - // because that would break the 'Body as field' upgrade for contrib - // node types. - - // TODO: Check that the widget type is known and can handle the field type ? - // TODO: Check that the formatters are known and can handle the field type ? - // TODO: Check that the display view modes are known for the entity type ? - // Those checks should probably happen in _field_write_instance() ? - // Problem : this would mean that a UI module cannot update an instance with a disabled formatter. - - // Ensure the field instance is unique within the bundle. - // We only check for instances of active fields, since adding an instance of - // a disabled field is not supported. - $prior_instance = field_read_instance($instance['entity_type'], $instance['field_name'], $instance['bundle']); - if (!empty($prior_instance)) { - $message = t('Attempt to create an instance of field @field_name on bundle @bundle that already has an instance of that field.', array('@field_name' => $instance['field_name'], '@bundle' => $instance['bundle'])); - throw new FieldException($message); - } - - _field_write_instance($instance); - - // Clear caches - field_cache_clear(); - - // Invoke external hooks after the cache is cleared for API consistency. - module_invoke_all('field_create_instance', $instance); - +function field_create_instance(array $instance) { + $instance = entity_create('field_instance', $instance); + $instance->save(); return $instance; } /** * Updates an instance of a field. * - * @param $instance - * An associative array representing an instance structure. The required keys - * and values are: + * @param mixed $instance + * Either the \Drupal\field\Plugin\Core\Entity\FieldInstance to update, or an + * associative array representing an instance structure. If the latter, the + * required keys and values are: * - entity_type: The type of the entity the field is attached to. * - bundle: The bundle this field belongs to. * - field_name: The name of an existing field. @@ -542,103 +271,20 @@ function field_create_instance(&$instance) { * @see field_create_instance() */ function field_update_instance($instance) { - // Check that the specified field exists. - $field = field_read_field($instance['field_name']); - if (empty($field)) { - throw new FieldException(t('Attempt to update an instance of a nonexistent field @field.', array('@field' => $instance['field_name']))); - } - - // Check that the field instance exists (even if it is inactive, since we - // want to be able to replace inactive widgets with new ones). - $prior_instance = field_read_instance($instance['entity_type'], $instance['field_name'], $instance['bundle'], array('include_inactive' => TRUE)); - if (empty($prior_instance)) { - throw new FieldException(t("Attempt to update an instance of field @field on bundle @bundle that doesn't exist.", array('@field' => $instance['field_name'], '@bundle' => $instance['bundle']))); - } - - $instance['id'] = $prior_instance['id']; - $instance['field_id'] = $prior_instance['field_id']; - - _field_write_instance($instance, TRUE); - - // Clear caches. - field_cache_clear(); - - module_invoke_all('field_update_instance', $instance, $prior_instance); -} - -/** - * Stores an instance record in the field configuration database. - * - * @param $instance - * An instance structure. - * @param $update - * Whether this is a new or existing instance. - */ -function _field_write_instance(&$instance, $update = FALSE) { - $field = field_read_field($instance['field_name']); - $field_type = field_info_field_types($field['type']); - - // Temporary workaround to allow incoming $instance as arrays or classed - // objects. - // @todo remove once the external APIs have been converted to use - // FieldInstance objects. - if (is_object($instance) && get_class($instance) == 'Drupal\field\FieldInstance') { - $instance = $instance->getArray(); + // Module developers can still pass in an array of properties. + if (is_array($instance)) { + $instance_loaded = entity_load('field_instance', $instance['entity_type'] . '.' . $instance['bundle'] . '.' . $instance['field_name']); + if (empty($instance_loaded)) { + throw new FieldException('Attempt to update a non-existent instance.'); + } + // Merge incoming values. + foreach ($instance as $key => $value) { + $instance_loaded[$key] = $value; + } + $instance = $instance_loaded; } - // Set defaults. - $instance += array( - 'settings' => array(), - 'widget' => array(), - 'required' => FALSE, - 'label' => $instance['field_name'], - 'description' => '', - 'deleted' => 0, - ); - - // Set default instance settings. - $instance['settings'] += field_info_instance_settings($field['type']); - - // Set default widget and settings. - $instance['widget'] += array( - // TODO: what if no 'default_widget' specified ? - 'type' => $field_type['default_widget'], - 'settings' => array(), - ); - // If no weight specified, make sure the field sinks at the bottom. - if (!isset($instance['widget']['weight'])) { - $max_weight = field_info_max_weight($instance['entity_type'], $instance['bundle'], 'form'); - $instance['widget']['weight'] = isset($max_weight) ? $max_weight + 1 : 0; - } - // Check widget module. - $widget_type = field_info_widget_types($instance['widget']['type']); - $instance['widget']['module'] = $widget_type['module']; - $instance['widget']['settings'] += field_info_widget_settings($instance['widget']['type']); - - // The serialized 'data' column contains everything from $instance that does - // not have its own column and is not automatically populated when the - // instance is read. - $data = $instance; - unset($data['id'], $data['field_id'], $data['field_name'], $data['entity_type'], $data['bundle'], $data['deleted']); - - $record = array( - 'field_id' => $instance['field_id'], - 'field_name' => $instance['field_name'], - 'entity_type' => $instance['entity_type'], - 'bundle' => $instance['bundle'], - 'data' => $data, - 'deleted' => $instance['deleted'], - ); - // We need to tell drupal_update_record() the primary keys to trigger an - // update. - if ($update) { - $record['id'] = $instance['id']; - drupal_write_record('field_config_instance', $record, array('id')); - } - else { - drupal_write_record('field_config_instance', $record); - $instance['id'] = $record['id']; - } + $instance->save(); } /** @@ -672,9 +318,9 @@ function field_read_instance($entity_type, $field_name, $bundle, $include_additi * Reads in field instances that match an array of conditions. * * @param $param - * An array of properties to use in selecting a field instance. Valid keys - * include any column of the field_config_instance table. If NULL, all - * instances will be returned. + * An array of properties to use in selecting a field instance. Keys are names + * of properties found in field instance configuration files, and values are + * conditions to match. * @param $include_additional * The default behavior of this function is to not return field instances that * have been marked deleted, or whose field is inactive. Setting @@ -684,84 +330,105 @@ function field_read_instance($entity_type, $field_name, $bundle, $include_additi * @return * An array of instances matching the arguments. */ -function field_read_instances($params = array(), $include_additional = array()) { +function field_read_instances($conditions = array(), $include_additional = array()) { + // Include instances of inactive fields if specified in the + // $include_additional parameter. $include_inactive = isset($include_additional['include_inactive']) && $include_additional['include_inactive']; - $include_deleted = isset($include_additional['include_deleted']) && $include_additional['include_deleted']; + // Include deleted instances if specified either in the $include_additional + // or the $conditions parameters. + $include_deleted = (isset($include_additional['include_deleted']) && $include_additional['include_deleted']) || (isset($conditions['deleted']) && $conditions['deleted']); - $query = db_select('field_config_instance', 'fci', array('fetch' => PDO::FETCH_ASSOC)); - $query->join('field_config', 'fc', 'fc.id = fci.field_id'); - $query->fields('fci'); + // Get instances stored in configuration. + 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. + $instances = entity_load_multiple('field_instance', array($conditions['entity_type'] . '.' . $conditions['bundle'] . '.' . $conditions['field_name'])); + } + else { + // No specific ID, we need to examine all existing instances. + $instances = entity_load_multiple('field_instance'); + } - // Turn the conditions into a query. - foreach ($params as $key => $value) { - $query->condition('fci.' . $key, $value); + // Merge deleted instances (stored in state) if needed. + if ($include_deleted) { + $state = Drupal::state(); + $deleted_fields = $state->get('field.field.deleted'); + $deleted_instances = $state->get('field.instance.deleted') ?: array(); + foreach ($deleted_instances as $id => $config) { + $instances[$id] = entity_create('field_instance', $config); + } } + + // Translate "do not include inactive fields" into actual conditions. if (!$include_inactive) { - $query - ->condition('fc.active', 1) - ->condition('fc.storage_active', 1); - } - if (!$include_deleted) { - $query->condition('fc.deleted', 0); - $query->condition('fci.deleted', 0); + $conditions['field.active'] = 1; + $conditions['field.storage.active'] = 1; } - $instances = array(); - $results = $query->execute(); - - foreach ($results as $record) { - // Filter out instances on unknown entity types (for instance because the - // module exposing them was disabled). - $entity_info = entity_get_info($record['entity_type']); - if ($include_inactive || $entity_info) { - $instance = unserialize($record['data']); - $instance['id'] = $record['id']; - $instance['field_id'] = $record['field_id']; - $instance['field_name'] = $record['field_name']; - $instance['entity_type'] = $record['entity_type']; - $instance['bundle'] = $record['bundle']; - $instance['deleted'] = $record['deleted']; - - module_invoke_all('field_read_instance', $instance); - $instances[] = $instance; + // Collect matching instances. + $matching_instances = array(); + foreach ($instances as $instance) { + // Only include instances on unknown entity types if 'include_inactive'. + if (!$include_inactive && !entity_get_info($instance->entity_type)) { + continue; + } + + // Get data from the field. If the field is marked as deleted, we need to + // get it from the state storage. + $field = entity_load('field_entity', $instance->field_name); + if (empty($field) && $include_deleted && isset($deleted_fields[$instance->field_uuid])) { + $field = new Field($deleted_fields[$instance->field_uuid]); + } + if (empty($field)) { + continue; + } + + // Only keep the instance if it matches all conditions. + foreach ($conditions as $key => $value) { + // Extract the actual value against which the condition is checked. + switch ($key) { + case 'field.active': + $checked_value = $field->active; + break; + + case 'field.storage.active': + $checked_value = $field->storage['active']; + break; + + case 'field_id': + $checked_value = $instance->field_uuid; + break; + + default: + $checked_value = $instance->$key; + break; + } + + // Skip to the next instance as soon as one condition does not match. + if ($checked_value != $value) { + continue 2; + } } + + module_invoke_all('field_read_instance', $instance); + + $matching_instances[] = $instance; } - return $instances; + + return $matching_instances; } /** * Marks a field instance and its data for deletion. * - * @param $instance - * An instance structure. + * @param \Drupal\field\Plugin\Core\Entity\FieldInstance $instance + * The field instance. * @param $field_cleanup * If TRUE, the field will be deleted as well if its last instance is being * deleted. If FALSE, it is the caller's responsibility to handle the case of * fields left without instances. Defaults to TRUE. */ -function field_delete_instance($instance, $field_cleanup = TRUE) { - // Mark the field instance for deletion. - db_update('field_config_instance') - ->fields(array('deleted' => 1)) - ->condition('field_name', $instance['field_name']) - ->condition('entity_type', $instance['entity_type']) - ->condition('bundle', $instance['bundle']) - ->execute(); - - // Clear the cache. - field_cache_clear(); - - // Mark instance data for deletion. - $field = field_info_field($instance['field_name']); - module_invoke($field['storage']['module'], 'field_storage_delete_instance', $instance); - - // Let modules react to the deletion of the instance. - module_invoke_all('field_delete_instance', $instance); - - // Delete the field itself if we just deleted its last instance. - if ($field_cleanup && count($field['bundles']) == 0) { - field_delete_field($field['field_name']); - } +function field_delete_instance(FieldInstance $instance, $field_cleanup = TRUE) { + $instance->delete($field_cleanup); } /** @@ -858,6 +525,13 @@ function field_purge_batch($batch_size) { $info = entity_get_info(); foreach ($instances as $instance) { $entity_type = $instance['entity_type']; + + // EntityFieldQuery currently fails on conditions on comment bundle. + // Remove when http://drupal.org/node/731724 is fixed. + if ($entity_type == 'comment') { + continue; + } + $ids = (object) array( 'entity_type' => $entity_type, 'bundle' => $instance['bundle'], @@ -866,7 +540,7 @@ function field_purge_batch($batch_size) { $field = field_info_field_by_id($instance['field_id']); // Retrieve some entities. $query = $factory->get($entity_type) - ->condition('id:' . $field['id'] . '.deleted', 1) + ->condition('id:' . $field['uuid'] . '.deleted', 1) ->range(0, $batch_size); // If there's no bundle key, all results will have the same bundle. if (!empty($info[$entity_type]['entity_keys']['bundle'])) { @@ -884,7 +558,7 @@ function field_purge_batch($batch_size) { $ids->entity_id = $entity_id; $entities[$entity_id] = _field_create_entity_from_ids($ids); } - field_attach_load($entity_type, $entities, FIELD_LOAD_CURRENT, array('field_id' => $field['id'], 'deleted' => 1)); + field_attach_load($entity_type, $entities, FIELD_LOAD_CURRENT, array('field_id' => $field['uuid'], 'deleted' => 1)); foreach ($entities as $entity) { // Purge the data for the entity. field_purge_data($entity, $field, $instance); @@ -897,9 +571,10 @@ function field_purge_batch($batch_size) { } // Retrieve all deleted fields. Any that have no instances can be purged. - $fields = field_read_fields(array('deleted' => 1), array('include_deleted' => 1)); - foreach ($fields as $field) { - $instances = field_read_instances(array('field_id' => $field['id']), array('include_deleted' => 1)); + $deleted_fields = Drupal::state()->get('field.field.deleted') ?: array(); + foreach ($deleted_fields as $field) { + $field = new Field($field); + $instances = field_read_instances(array('field_id' => $field['uuid']), array('include_deleted' => 1)); if (empty($instances)) { field_purge_field($field); } @@ -946,14 +621,15 @@ function field_purge_data(EntityInterface $entity, $field, $instance) { * The instance record to purge. */ function field_purge_instance($instance) { - db_delete('field_config_instance') - ->condition('id', $instance['id']) - ->execute(); - // Notify the storage engine. $field = field_info_field_by_id($instance['field_id']); module_invoke($field['storage']['module'], 'field_storage_purge_instance', $instance); + $state = Drupal::state(); + $deleted_instances = $state->get('field.instance.deleted'); + unset($deleted_instances[$instance['uuid']]); + $state->set('field.instance.deleted', $deleted_instances); + // Clear the cache. field_info_cache_clear(); @@ -971,14 +647,15 @@ function field_purge_instance($instance) { * The field record to purge. */ function field_purge_field($field) { - $instances = field_read_instances(array('field_id' => $field['id']), array('include_deleted' => 1)); + $instances = field_read_instances(array('field_id' => $field['uuid']), array('include_deleted' => 1)); if (count($instances) > 0) { throw new FieldException(t('Attempt to purge a field @field_name that still has instances.', array('@field_name' => $field['field_name']))); } - db_delete('field_config') - ->condition('id', $field['id']) - ->execute(); + $state = Drupal::state(); + $deleted_fields = $state->get('field.field.deleted'); + unset($deleted_fields[$field['uuid']]); + $state->set('field.field.deleted', $deleted_fields); // Notify the storage engine. module_invoke($field['storage']['module'], 'field_storage_purge_field', $field); diff --git a/core/modules/field/field.info.inc b/core/modules/field/field.info.inc index b422b47..4ec8bfe 100644 --- a/core/modules/field/field.info.inc +++ b/core/modules/field/field.info.inc @@ -5,7 +5,7 @@ * Field Info API, providing information about available fields and field types. */ -use Drupal\field\FieldInstance; +use Drupal\field\Plugin\Core\Entity\FieldInstance; use Drupal\field\FieldInfo; /** diff --git a/core/modules/field/field.info.yml b/core/modules/field/field.info.yml index 8cd31f1..f5c4a7d 100644 --- a/core/modules/field/field.info.yml +++ b/core/modules/field/field.info.yml @@ -3,6 +3,4 @@ description: 'Field API to add fields to entities like nodes and users.' package: Core version: VERSION core: 8.x -dependencies: - - field_sql_storage required: true diff --git a/core/modules/field/field.install b/core/modules/field/field.install index 695fd89..c1a1378 100644 --- a/core/modules/field/field.install +++ b/core/modules/field/field.install @@ -5,162 +5,13 @@ * Install, update, and uninstall functions for the Field module. */ +use Drupal\Component\Uuid\Uuid; +use Drupal\field\Plugin\Core\Entity\Field; + /** * Implements hook_schema(). */ function field_schema() { - // Static (meta) tables. - $schema['field_config'] = array( - 'fields' => array( - 'id' => array( - 'type' => 'serial', - 'not null' => TRUE, - 'description' => 'The primary identifier for a field', - ), - 'field_name' => array( - 'type' => 'varchar', - 'length' => 32, - 'not null' => TRUE, - 'description' => 'The name of this field. Non-deleted field names are unique, but multiple deleted fields can have the same name.', - ), - 'type' => array( - 'type' => 'varchar', - 'length' => 128, - 'not null' => TRUE, - 'description' => 'The type of this field.', - ), - 'module' => array( - 'type' => 'varchar', - 'length' => 128, - 'not null' => TRUE, - 'default' => '', - 'description' => 'The module that implements the field type.', - ), - 'active' => array( - 'type' => 'int', - 'size' => 'tiny', - 'not null' => TRUE, - 'default' => 0, - 'description' => 'Boolean indicating whether the module that implements the field type is enabled.', - ), - 'storage_type' => array( - 'type' => 'varchar', - 'length' => 128, - 'not null' => TRUE, - 'description' => 'The storage backend for the field.', - ), - 'storage_module' => array( - 'type' => 'varchar', - 'length' => 128, - 'not null' => TRUE, - 'default' => '', - 'description' => 'The module that implements the storage backend.', - ), - 'storage_active' => array( - 'type' => 'int', - 'size' => 'tiny', - 'not null' => TRUE, - 'default' => 0, - 'description' => 'Boolean indicating whether the module that implements the storage backend is enabled.', - ), - 'locked' => array( - 'type' => 'int', - 'size' => 'tiny', - 'not null' => TRUE, - 'default' => 0, - 'description' => '@TODO', - ), - 'data' => array( - 'type' => 'blob', - 'size' => 'big', - 'not null' => TRUE, - 'serialize' => TRUE, - 'description' => 'Serialized data containing the field properties that do not warrant a dedicated column.', - ), - 'cardinality' => array( - 'type' => 'int', - 'size' => 'tiny', - 'not null' => TRUE, - 'default' => 0, - ), - 'translatable' => array( - 'type' => 'int', - 'size' => 'tiny', - 'not null' => TRUE, - 'default' => 0, - ), - 'deleted' => array( - 'type' => 'int', - 'size' => 'tiny', - 'not null' => TRUE, - 'default' => 0, - ), - ), - 'primary key' => array('id'), - 'indexes' => array( - 'field_name' => array('field_name'), - // Used by field_read_fields(). - 'active' => array('active'), - 'storage_active' => array('storage_active'), - 'deleted' => array('deleted'), - // Used by field_sync_field_status(). - 'module' => array('module'), - 'storage_module' => array('storage_module'), - 'type' => array('type'), - 'storage_type' => array('storage_type'), - ), - ); - $schema['field_config_instance'] = array( - 'fields' => array( - 'id' => array( - 'type' => 'serial', - 'not null' => TRUE, - 'description' => 'The primary identifier for a field instance', - ), - 'field_id' => array( - 'type' => 'int', - 'not null' => TRUE, - 'description' => 'The identifier of the field attached by this instance', - ), - 'field_name' => array( - 'type' => 'varchar', - 'length' => 32, - 'not null' => TRUE, - 'default' => '' - ), - 'entity_type' => array( - 'type' => 'varchar', - 'length' => 32, - 'not null' => TRUE, - 'default' => '' - ), - 'bundle' => array( - 'type' => 'varchar', - 'length' => 128, - 'not null' => TRUE, - 'default' => '' - ), - 'data' => array( - 'type' => 'blob', - 'size' => 'big', - 'not null' => TRUE, - 'serialize' => TRUE, - ), - 'deleted' => array( - 'type' => 'int', - 'size' => 'tiny', - 'not null' => TRUE, - 'default' => 0, - ), - ), - 'primary key' => array('id'), - 'indexes' => array( - // Used by field_delete_instance(). - 'field_name_bundle' => array('field_name', 'entity_type', 'bundle'), - // Used by field_read_instances(). - 'deleted' => array('deleted'), - ), - ); $schema['cache_field'] = drupal_get_schema_unprocessed('system', 'cache'); $schema['cache_field']['description'] = 'Cache table for the Field module to store already built field informations.'; @@ -229,12 +80,16 @@ function _update_7000_field_create_field(&$field) { 'deleted' => (int) $field['deleted'], ); // We don't use drupal_write_record() here because it depends on the schema. - $field['id'] = db_insert('field_config') + $field_id = db_insert('field_config') ->fields($record) ->execute(); - // Create storage for the field. - field_sql_storage_field_storage_create_field($field); + // Create storage for the field. This requires a field entity, but cannot use + // the regular entity_create() function here. + $field_entity = new Field($field); + field_sql_storage_field_storage_create_field($field_entity); + + $field['id'] = $field_id; } /** @@ -271,7 +126,6 @@ function _update_7000_field_delete_field($field_name) { ->execute(); } - /** * Deletes an instance and all its data of a field stored in SQL Storage. * @@ -355,6 +209,8 @@ function _update_7000_field_create_instance($field, &$instance) { 'field_id' => $field['id'], 'field_name' => $field['field_name'], 'deleted' => 0, + 'description' => '', + 'required' => FALSE, ); // The serialized 'data' column contains everything from $instance that does @@ -382,15 +238,25 @@ function _update_7000_field_create_instance($field, &$instance) { */ /** - * Reassign all list.module fields to be controlled by options.module. + * Implements hook_update_dependencies(). + */ +function field_update_dependencies() { + // Convert Field API to ConfigEntities after: + $dependencies['field'][8003] = array( + // - Custom block bodies have been turned to fields. + 'block' => 8008, + // - User pictures have been turned to fields. + 'user' => 8011, + // - The {file_usage}.id column has moved to varchar. + 'file' => 8001, + ); + return $dependencies; +} + +/** + * Empty update - moved into field_update_8003(). */ function field_update_8001() { - db_update('field_config') - ->fields(array( - 'module' => 'options', - )) - ->condition('module', 'list') - ->execute(); } /** @@ -442,7 +308,7 @@ function field_update_8002() { // Migration of 'extra_fields' display settings. Avoid calling // entity_get_info() by fetching the relevant variables directly in the - // cariables table. + // variable table. $variables = array_map('unserialize', db_query("SELECT name, value FROM {variable} WHERE name LIKE '%field_bundle_settings_%'")->fetchAllKeyed()); foreach ($variables as $variable_name => $variable_value) { if (preg_match('/field_bundle_settings_(.*)__(.*)/', $variable_name, $matches)) { @@ -484,6 +350,137 @@ function field_update_8002() { } /** + * Convert fields and instances to config. + */ +function field_update_8003() { + $uuid = new Uuid(); + $manifest_ids = array('fields' => array(), 'instances' => array()); + + $state = Drupal::state(); + $deleted_fields = $state->get('field.field.deleted') ?: array(); + $deleted_instances = $state->get('field.instance.deleted') ?: array(); + + $field_uuids = array(); + + // Migrate field definitions. + $records = db_query("SELECT * FROM {field_config}")->fetchAll(PDO::FETCH_ASSOC); + foreach ($records as $record) { + $record['data'] = unserialize($record['data']); + + $config = array( + 'id' => $record['field_name'], + 'uuid' => $uuid->generate(), + 'type' => $record['type'], + 'module' => $record['module'], + 'active' => $record['active'], + 'settings' => $record['data']['settings'], + 'storage' => array( + 'type' => $record['storage_type'], + 'module' => $record['storage_module'], + 'active' => $record['storage_active'], + 'settings' => $record['data']['storage']['settings'], + ), + 'locked' => $record['locked'], + 'cardinality' => $record['cardinality'], + 'translatable' => $record['translatable'], + 'entity_types' => $record['data']['entity_types'], + 'indexes' => $record['data']['indexes'] ?: array(), + 'status' => 1, + 'langcode' => 'und', + ); + + // Reassign all list.module fields to be controlled by options.module. + if ($config['module'] == 'list') { + $config['module'] = 'options'; + } + + // Save in either config or state. + if (!$record['deleted']) { + Drupal::config('field.field.' . $config['id']) + ->setData($config) + ->save(); + $manifest_ids['fields'][] = $config['id']; + } + else { + $config['deleted'] = TRUE; + $deleted_fields[$config['uuid']] = $config; + // Additionally, rename the data tables for deleted fields. Technically + // this would belong in an update in field_sql_storage.module, but it is + // easier to do it now, when the old numeric ID is available. + if ($config['storage']['type'] == 'field_sql_storage') { + $field = new Field($config); + $tables = array( + "field_deleted_data_{$record['id']}" => _field_sql_storage_tablename($field), + "field_deleted_revision_{$record['id']}" => _field_sql_storage_revision_tablename($field), + ); + foreach ($tables as $table_old => $table_new) { + if (db_table_exists($table_old)) { + db_rename_table($table_old, $table_new); + } + } + } + } + + // Store the UUID with the old field_id so that we can map the instances. + $field_uuids[$record['id']] = $config['uuid']; + } + + // Migrate instance definitions. + $records = db_query("SELECT * FROM {field_config_instance}")->fetchAll(PDO::FETCH_ASSOC); + foreach ($records as $record) { + $record['data'] = unserialize($record['data']); + + $config = array( + 'id' => $record['entity_type'] . '.' . $record['bundle'] . '.' . $record['field_name'], + 'uuid' => $uuid->generate(), + 'field_uuid' => $field_uuids[$record['field_id']], + 'entity_type' => $record['entity_type'], + 'bundle' => $record['bundle'], + 'label' => $record['data']['label'], + 'description' => $record['data']['description'], + 'required' => $record['data']['required'], + 'default_value' => isset($record['data']['default_value']) ? $record['data']['default_value'] : array(), + 'default_value_function' => isset($record['data']['default_value_function']) ? $record['data']['default_value_function'] : '', + 'settings' => $record['data']['settings'], + 'widget' => $record['data']['widget'], + 'status' => 1, + 'langcode' => 'und', + ); + + // Save in either config or state. + if (!$record['deleted']) { + Drupal::config('field.instance.' . $config['id']) + ->setData($config) + ->save(); + $manifest_ids['instances'][] = $config['id']; + } + else { + $config['deleted'] = TRUE; + $deleted_instances[$config['uuid']] = $config; + } + + // Update {file_usage} table in case this instance has a default image. + if (!empty($config['settings']['default_image'])) { + db_update('file_usage') + ->fields(array('id' => $config['field_uuid'])) + ->condition('type', 'default_image') + ->condition('module', 'image') + ->condition('id', $record['field_id']) + ->condition('fid', $config['settings']['default_image']) + ->execute(); + } + } + + // Create the manifest files. + update_config_manifest_add('field.field', $manifest_ids['fields']); + update_config_manifest_add('field.instance', $manifest_ids['instances']); + + // Save the deleted fields and instances in state. + $state->set('field.field.deleted', $deleted_fields); + $state->set('field.instance.deleted', $deleted_instances); +} + +/** * @} End of "addtogroup updates-7.x-to-8.x". * The next series of updates should start at 9000. */ diff --git a/core/modules/field/field.module b/core/modules/field/field.module index 095fe45..a5b02bc 100644 --- a/core/modules/field/field.module +++ b/core/modules/field/field.module @@ -49,144 +49,6 @@ * 'subtitle' and 'photo' fields because they are both attached to the 'node' * bundle 'article'. * - * Field definitions are represented as an array of key/value pairs. - * - * array $field: - * - id: (integer, read-only) The primary identifier of the field. It is - * assigned automatically by field_create_field(). - * - field_name: (string) The name of the field. Each field name is unique - * within Field API. When a field is attached to an entity, the field's data - * is stored in $entity->$field_name. Maximum length is 32 characters. - * - type: (string) The type of the field, such as 'text' or 'image'. Field - * types are defined by modules that implement hook_field_info(). - * - entity_types: (array) The array of entity types that can hold instances of - * this field. If empty or not specified, the field can have instances in any - * entity type. - * - cardinality: (integer) The number of values the field can hold. Legal - * values are any positive integer or FIELD_CARDINALITY_UNLIMITED. - * - translatable: (integer) Whether the field is translatable. - * - locked: (integer) Whether or not the field is available for editing. If - * TRUE, users can't change field settings or create new instances of the - * field in the UI. Defaults to FALSE. - * - module: (string, read-only) The name of the module that implements the - * field type. - * - active: (integer, read-only) TRUE if the module that implements the field - * type is currently enabled, FALSE otherwise. - * - deleted: (integer, read-only) TRUE if this field has been deleted, FALSE - * otherwise. Deleted fields are ignored by the Field Attach API. This - * property exists because fields can be marked for deletion but only actually - * destroyed by a separate garbage-collection process. - * - columns: (array, read-only) An array of the Field API columns used to store - * each value of this field. The column list may depend on field settings; it - * is not constant per field type. Field API column specifications are exactly - * like Schema API column specifications but, depending onthe field storage - * module in use, the name of the column may not represent an actual column in - * an SQL database. - * - indexes: (array) An array of indexes on data columns, using the same - * definition format as Schema API index specifications. Only columns that - * appear in the 'columns' setting are allowed. Note that field types can - * specify default indexes, which can be modified or added to when creating a - * field. - * - foreign keys: (optional) An associative array of relations, using the same - * structure as the 'foreign keys' definition of hook_schema(). Note, - * however, that the field data is not necessarily stored in SQL. Also, the - * possible usage is limited, as you cannot specify another field as - * related, only existing SQL tables, such as filter formats. - * - settings: (array) A sub-array of key/value pairs of field-type-specific - * settings. Each field type module defines and documents its own field - * settings. - * - storage: (array) A sub-array of key/value pairs identifying the storage - * backend to use for the for the field. - * - type: (string) The storage backend used by the field. Storage backends - * are defined by modules that implement hook_field_storage_info(). - * - module: (string, read-only) The name of the module that implements the - * storage backend. - * - active: (integer, read-only) TRUE if the module that implements the - * storage backend is currently enabled, FALSE otherwise. - * - settings: (array) A sub-array of key/value pairs of settings. Each - * storage backend defines and documents its own settings. - * - * Field instance definitions are represented as an array of key/value pairs. - * - * array $instance: - * - id: (integer, read-only) The primary identifier of this field instance. It - * is assigned automatically by field_create_instance(). - * - field_id: (integer, read-only) The foreign key of the field attached to the - * bundle by this instance. It is populated automatically by - * field_create_instance(). - * - field_name: (string) The name of the field attached to the bundle by this - * instance. - * - entity_type: (string) The name of the entity type the instance is attached - * to. - * - bundle: (string) The name of the bundle that the field is attached to. - * - label: (string) A human-readable label for the field when used with this - * bundle. For example, the label will be the title of Form API elements for - * this instance. - * - description: (string)A human-readable description for the field when used - * with this bundle. For example, the description will be the help text of - * Form API elements for this instance. - * - required: (integer) TRUE if a value for this field is required when used - * with this bundle, FALSE otherwise. Currently, required-ness is only - * enforced during Form API operations, not by field_attach_load(), - * field_attach_insert(), or field_attach_update(). - * - default_value_function: (string) The name of the function, if any, that - * will provide a default value. - * - default_value: (array) If default_value_function is not set, then fixed - * values can be provided. - * - deleted: (integer, read-only) TRUE if this instance has been deleted, FALSE - * otherwise. Deleted instances are ignored by the Field Attach API. This - * property exists because instances can be marked for deletion but only - * actually destroyed by a separate garbage-collection process. - * - settings: (array) A sub-array of key/value pairs of field-type-specific - * instance settings. Each field type module defines and documents its own - * instance settings. - * - widget: (array) A sub-array of key/value pairs identifying the Form API - * input widget for the field when used by this bundle. - * - type: (string) The type of the widget, such as text_textfield. Widget - * types are defined by modules that implement hook_field_widget_info(). - * - settings: (array) A sub-array of key/value pairs of widget-type-specific - * settings. Each field widget type module defines and documents its own - * widget settings. - * - weight: (float) The weight of the widget relative to the other elements - * in entity edit forms. - * - module: (string, read-only) The name of the module that implements the - * widget type. - * - display: (array) A sub-array of key/value pairs identifying the way field - * values should be displayed in each of the entity type's view modes, plus - * the 'default' mode. For each view mode, Field UI lets site administrators - * define whether they want to use a dedicated set of display options or the - * 'default' options to reduce the number of displays to maintain as they add - * new fields. For nodes, on a fresh install, only the 'teaser' view mode is - * configured to use custom display options, all other view modes defined use - * the 'default' options by default. When programmatically adding field - * instances on nodes, it is therefore recommended to at least specify display - * options for 'default' and 'teaser'. - * - default: (array) A sub-array of key/value pairs describing the display - * options to be used when the field is being displayed in view modes that - * are not configured to use dedicated display options. - * - label: (string) Position of the label. 'inline', 'above' and 'hidden' - * are the values recognized by the default 'field' theme implementation. - * - type: (string) The type of the display formatter, or 'hidden' for no - * display. - * - settings: (array) A sub-array of key/value pairs of display options - * specific to the formatter. - * - weight: (float) The weight of the field relative to the other entity - * components displayed in this view mode. - * - module: (string, read-only) The name of the module which implements - * the display formatter. - * - some_mode: A sub-array of key/value pairs describing the display options - * to be used when the field is being displayed in the 'some_mode' view - * mode. Those options will only be actually applied at run time if the view - * mode is not configured to use default settings for this bundle. - * - ... - * - other_mode: - * - ... - * - * The (default) render arrays produced for field instances are documented at - * field_attach_view(). - * - * Bundles are represented by two strings, an entity type and a bundle name. - * * - @link field_types Field Types API @endlink: Defines field types, widget * types, and display formatters. Field modules use this API to provide field * types like Text and Node Reference along with the associated form elements @@ -334,7 +196,8 @@ function field_cron() { * required if there are any active fields of that type. */ function field_system_info_alter(&$info, $file, $type) { - if ($type == 'module' && module_hook($file->name, 'field_info')) { + // It is not safe to call field_read_fields() during maintenance mode. + if ($type == 'module' && module_hook($file->name, 'field_info') && !defined('MAINTENANCE_MODE')) { $fields = field_read_fields(array('module' => $file->name), array('include_deleted' => TRUE)); if ($fields) { $info['required'] = TRUE; @@ -522,48 +385,81 @@ function field_modules_disabled($modules) { } /** - * Refreshes the 'active' and 'storage_active' columns for fields. + * Refreshes the 'active' and 'storage[active]' values for fields. */ function field_sync_field_status() { - // Refresh the 'active' and 'storage_active' columns according to the current - // set of enabled modules. - $modules = array_keys(drupal_container()->get('module_handler')->getModuleList()); - foreach ($modules as $module_name) { - field_associate_fields($module_name); + $module_handler = Drupal::moduleHandler(); + $state = Drupal::state(); + + // Get both deleted and non-deleted field definitions. + $fields = array(); + foreach (config_get_storage_names_with_prefix('field.field') as $name) { + $field = Drupal::config($name)->get(); + $fields[$field['uuid']] = $field; } - db_update('field_config') - ->fields(array('active' => 0)) - ->condition('module', $modules, 'NOT IN') - ->execute(); - db_update('field_config') - ->fields(array('storage_active' => 0)) - ->condition('storage_module', $modules, 'NOT IN') - ->execute(); -} + $deleted_fields = $state->get('field.field.deleted') ?: array(); + $fields += $deleted_fields; -/** - * Allows a module to update the database for fields and columns it controls. - * - * @param $module - * The name of the module to update on. - */ -function field_associate_fields($module) { - // Associate field types. - $field_types = (array) module_invoke($module, 'field_info'); - if ($field_types) { - db_update('field_config') - ->fields(array('module' => $module, 'active' => 1)) - ->condition('type', array_keys($field_types)) - ->execute(); + if (empty($fields)) { + return; } - // Associate storage backends. - $storage_types = (array) module_invoke($module, 'field_storage_info'); - if ($storage_types) { - db_update('field_config') - ->fields(array('storage_module' => $module, 'storage_active' => 1)) - ->condition('storage_type', array_keys($storage_types)) - ->execute(); + + // Set the 'module' and 'active' values for the current set of enabled + // modules. + $changed = array(); + $modules = $module_handler->getModuleList(); + foreach ($modules as $module => $module_info) { + // Collect field types and storage backends exposed by the module. + $field_types = (array) $module_handler->invoke($module, 'field_info'); + $storage_types = (array) $module_handler->invoke($module, 'field_storage_info'); + + if ($field_types || $storage_types) { + foreach ($fields as $uuid => &$field) { + // Associate field types. + if (isset($field_types[$field['type']]) && ($field['module'] !== $module || !$field['active'])) { + $field['module'] = $module; + $field['active'] = TRUE; + $changed[$uuid] = $field; + } + // Associate storage backends. + if (isset($storage_types[$field['storage']['type']]) && ($field['storage']['module'] !== $module || !$field['storage']['active'])) { + $field['storage']['module'] = $module; + $field['storage']['active'] = TRUE; + $changed[$uuid] = $field; + } + } + } } + + // Set fields with missing field type or storage modules to inactive. + foreach ($fields as $uuid => &$field) { + if (!isset($modules[$field['module']]) && $field['active']) { + $field['active'] = FALSE; + $changed[$uuid] = $field; + } + if (!isset($modules[$field['storage']['module']]) && $field['storage']['active']) { + $field['storage']['active'] = FALSE; + $changed[$uuid] = $field; + } + } + + // Store the updated field definitions. + foreach ($changed as $uuid => $field) { + if (!empty($field['deleted'])) { + $deleted_fields[$uuid] = $field; + } + else { + Drupal::config('field.field.' . $field['id']) + ->set('module', $field['module']) + ->set('active', $field['active']) + ->set('storage.module', $field['storage']['module']) + ->set('storage.active', $field['storage']['active']) + ->save(); + } + } + $state->set('field.field.deleted', $deleted_fields); + + field_cache_clear(); } /** @@ -1003,7 +899,6 @@ function field_get_items(EntityInterface $entity, $field_name, $langcode = NULL) * TRUE if the field has data for any entity; FALSE otherwise. */ function field_has_data($field) { - $field = field_info_field_by_id($field['id']); $columns = array_keys($field['columns']); $factory = drupal_container()->get('entity.query'); foreach ($field['bundles'] as $entity_type => $bundle) { diff --git a/core/modules/field/field.views.inc b/core/modules/field/field.views.inc index d045889..b01e882 100644 --- a/core/modules/field/field.views.inc +++ b/core/modules/field/field.views.inc @@ -72,7 +72,7 @@ function field_views_field_label($field_name) { foreach ($instances as $entity_name => $entity_type) { foreach ($entity_type as $bundle) { if (isset($bundle[$field_name])) { - $label_counter[$bundle[$field_name]['label']] = isset($label_counter[$bundle[$field_name]['label']]) ? ++$label_counter[$bundle[$field_name]['label']] : 1; + $label_counter[$bundle[$field_name]['label']] = isset($label_counter[$bundle[$field_name]['label']]) ? ++$label_counter[$bundle[$field_name]->label] : 1; $all_labels[$entity_name][$bundle[$field_name]['label']] = TRUE; } } @@ -296,10 +296,10 @@ function field_views_field_default_views_data($field) { else { $group = t('@group (historical data)', array('@group' => $group_name)); } - $column_real_name = $field['storage']['details']['sql'][$type][$table][$column]; + $column_real_name = $field['storage_details']['sql'][$type][$table][$column]; // Load all the fields from the table by default. - $additional_fields = array_values($field['storage']['details']['sql'][$type][$table]); + $additional_fields = array_values($field['storage_details']['sql'][$type][$table]); $data[$table][$column_real_name] = array( 'group' => $group, diff --git a/core/modules/field/lib/Drupal/field/FieldInfo.php b/core/modules/field/lib/Drupal/field/FieldInfo.php index 7ff6850..9b79960 100644 --- a/core/modules/field/lib/Drupal/field/FieldInfo.php +++ b/core/modules/field/lib/Drupal/field/FieldInfo.php @@ -142,17 +142,24 @@ public function getFieldMap() { $map = array(); - $query = db_select('field_config_instance', 'fci'); - $query->join('field_config', 'fc', 'fc.id = fci.field_id'); - $query->fields('fc', array('type')); - $query->fields('fci', array('field_name', 'entity_type', 'bundle')) - ->condition('fc.active', 1) - ->condition('fc.storage_active', 1) - ->condition('fc.deleted', 0) - ->condition('fci.deleted', 0); - foreach ($query->execute() as $row) { - $map[$row->field_name]['bundles'][$row->entity_type][] = $row->bundle; - $map[$row->field_name]['type'] = $row->type; + // Get active fields. + foreach (config_get_storage_names_with_prefix('field.field') as $config_id) { + $field_config = \Drupal::config($config_id)->get(); + if ($field_config['active'] && $field_config['storage']['active']) { + $fields[$field_config['uuid']] = $field_config; + } + } + // Get field instances. + foreach (config_get_storage_names_with_prefix('field.instance') as $config_id) { + $instance_config = \Drupal::config($config_id)->get(); + $field_uuid = $instance_config['field_uuid']; + // Filter out instances of inactive fields, and instances on unknown + // entity types. + if (isset($fields[$field_uuid])) { + $field = $fields[$field_uuid]; + $map[$field['id']]['bundles'][$instance_config['entity_type']][] = $instance_config['bundle']; + $map[$field['id']]['type'] = $field['type']; + } } // Save in "static" and persistent caches. @@ -181,7 +188,7 @@ public function getFields() { else { // Collect and prepare fields. foreach (field_read_fields(array(), array('include_deleted' => TRUE)) as $field) { - $this->fieldsById[$field['id']] = $this->prepareField($field); + $this->fieldsById[$field['uuid']] = $this->prepareField($field); } // Store in persistent cache. @@ -191,7 +198,7 @@ public function getFields() { // Fill the name/ID map. foreach ($this->fieldsById as $field) { if (!$field['deleted']) { - $this->fieldIdsByName[$field['field_name']] = $field['id']; + $this->fieldIdsByName[$field['id']] = $field['uuid']; } } @@ -229,7 +236,7 @@ public function getInstances($entity_type = NULL) { foreach (field_read_instances() as $instance) { $field = $this->getField($instance['field_name']); $instance = $this->prepareInstance($instance, $field['type']); - $this->bundleInstances[$instance['entity_type']][$instance['bundle']][$instance['field_name']] = new FieldInstance($instance); + $this->bundleInstances[$instance['entity_type']][$instance['bundle']][$instance['field_name']] = $instance; } // Store in persistent cache. @@ -275,8 +282,8 @@ public function getField($field_name) { $field = $this->prepareField($field); // Save in the "static" cache. - $this->fieldsById[$field['id']] = $field; - $this->fieldIdsByName[$field['field_name']] = $field['id']; + $this->fieldsById[$field['uuid']] = $field; + $this->fieldIdsByName[$field['field_name']] = $field['uuid']; return $field; } @@ -309,14 +316,14 @@ public function getFieldById($field_id) { // bundle. // Cache miss: read from definition. - if ($fields = field_read_fields(array('id' => $field_id), array('include_deleted' => TRUE))) { + if ($fields = field_read_fields(array('uuid' => $field_id), array('include_deleted' => TRUE))) { $field = current($fields); $field = $this->prepareField($field); // Store in the static cache. - $this->fieldsById[$field['id']] = $field; + $this->fieldsById[$field['uuid']] = $field; if (!$field['deleted']) { - $this->fieldIdsByName[$field['field_name']] = $field['id']; + $this->fieldIdsByName[$field['field_name']] = $field['uuid']; } return $field; @@ -355,10 +362,10 @@ public function getBundleInstances($entity_type, $bundle) { // Extract the field definitions and save them in the "static" cache. foreach ($info['fields'] as $field) { - if (!isset($this->fieldsById[$field['id']])) { - $this->fieldsById[$field['id']] = $field; + if (!isset($this->fieldsById[$field['uuid']])) { + $this->fieldsById[$field['uuid']] = $field; if (!$field['deleted']) { - $this->fieldIdsByName[$field['field_name']] = $field['id']; + $this->fieldIdsByName[$field['field_name']] = $field['uuid']; } } } @@ -377,27 +384,40 @@ public function getBundleInstances($entity_type, $bundle) { } // Cache miss: collect from the definitions. - $instances = array(); - // Collect the fields in the bundle. - $params = array('entity_type' => $entity_type, 'bundle' => $bundle); - $fields = field_read_fields($params); + // Do not return anything for unknown entity types. + if (entity_get_info($entity_type)) { - // This iterates on non-deleted instances, so deleted fields are kept out of - // the persistent caches. - foreach (field_read_instances($params) as $instance) { - $field = $fields[$instance['field_name']]; + // Collect names of fields and instances involved in the bundle, using the + // field map. The field map is already filtered to active, non-deleted + // fields and instances, so those are kept out of the persistent caches. + $config_ids = array(); + foreach ($this->getFieldMap() as $field_name => $field_data) { + if (isset($field_data['bundles'][$entity_type]) && in_array($bundle, $field_data['bundles'][$entity_type])) { + $config_ids[$field_name] = "$entity_type.$bundle.$field_name"; + } + } - $instance = $this->prepareInstance($instance, $field['type']); - $instances[$field['field_name']] = new FieldInstance($instance); + // Load and prepare the corresponding fields and instances entities. + if ($config_ids) { + $loaded_fields = entity_load_multiple('field_entity', array_keys($config_ids)); + $loaded_instances = entity_load_multiple('field_instance', array_values($config_ids)); - // If the field is not in our global "static" list yet, add it. - if (!isset($this->fieldsById[$field['id']])) { - $field = $this->prepareField($field); + foreach ($loaded_instances as $instance) { + $field = $loaded_fields[$instance['field_name']]; + + $instance = $this->prepareInstance($instance, $field['type']); + $instances[$field['field_name']] = $instance; - $this->fieldsById[$field['id']] = $field; - $this->fieldIdsByName[$field['field_name']] = $field['id']; + // If the field is not in our global "static" list yet, add it. + if (!isset($this->fieldsById[$field['uuid']])) { + $field = $this->prepareField($field); + + $this->fieldsById[$field['uuid']] = $field; + $this->fieldIdsByName[$field['field_name']] = $field['uuid']; + } + } } } @@ -480,20 +500,6 @@ public function prepareField($field) { $field['settings'] += field_info_field_settings($field['type']); $field['storage']['settings'] += field_info_storage_settings($field['storage']['type']); - // Add storage details. - $details = (array) module_invoke($field['storage']['module'], 'field_storage_details', $field); - drupal_alter('field_storage_details', $details, $field); - $field['storage']['details'] = $details; - - // Populate the list of bundles using the field. - $field['bundles'] = array(); - if (!$field['deleted']) { - $map = $this->getFieldMap(); - if (isset($map[$field['field_name']])) { - $field['bundles'] = $map[$field['field_name']]['bundles']; - } - } - return $field; } @@ -553,4 +559,5 @@ public function prepareExtraFields($extra_fields, $entity_type, $bundle) { return $result; } + } diff --git a/core/modules/field/lib/Drupal/field/FieldInstance.php b/core/modules/field/lib/Drupal/field/FieldInstance.php deleted file mode 100644 index ba6ea44..0000000 --- a/core/modules/field/lib/Drupal/field/FieldInstance.php +++ /dev/null @@ -1,135 +0,0 @@ -definition = $definition; - } - - /** - * Returns the Widget plugin for the instance. - * - * @return Drupal\field\Plugin\Type\Widget\WidgetInterface - * The Widget plugin to be used for the instance. - */ - public function getWidget() { - if (empty($this->widget)) { - $widget_properties = $this->definition['widget']; - - // Let modules alter the widget properties. - $context = array( - 'entity_type' => $this->definition['entity_type'], - 'bundle' => $this->definition['bundle'], - 'field' => field_info_field($this->definition['field_name']), - 'instance' => $this, - ); - drupal_alter(array('field_widget_properties', 'field_widget_properties_' . $this->definition['entity_type']), $widget_properties, $context); - - $options = array( - 'instance' => $this, - 'type' => $widget_properties['type'], - 'settings' => $widget_properties['settings'], - 'weight' => $widget_properties['weight'], - ); - $this->widget = drupal_container()->get('plugin.manager.field.widget')->getInstance($options); - } - - return $this->widget; - } - - /** - * Implements ArrayAccess::offsetExists(). - */ - public function offsetExists($offset) { - return isset($this->definition[$offset]) || array_key_exists($offset, $this->definition); - } - - /** - * Implements ArrayAccess::offsetGet(). - */ - public function &offsetGet($offset) { - return $this->definition[$offset]; - } - - /** - * Implements ArrayAccess::offsetSet(). - */ - public function offsetSet($offset, $value) { - if (!isset($offset)) { - // Do nothing; $array[] syntax is not supported by this temporary wrapper. - return; - } - $this->definition[$offset] = $value; - - // If the widget or formatter properties changed, the corrsponding plugins - // need to be re-instanciated. - if ($offset == 'widget') { - unset($this->widget); - } - } - - /** - * Implements ArrayAccess::offsetUnset(). - */ - public function offsetUnset($offset) { - unset($this->definition[$offset]); - - // If the widget or formatter properties changed, the corrsponding plugins - // need to be re-instanciated. - if ($offset == 'widget') { - unset($this->widget); - } - } - - /** - * Returns the instance definition as a regular array. - * - * This is used as a temporary BC layer. - * @todo Remove once the external APIs have been converted to use - * FieldInstance objects. - * - * @return array - * The instance definition as a regular array. - */ - public function getArray() { - return $this->definition; - } - - /** - * Handles serialization of Drupal\field\FieldInstance objects. - */ - public function __sleep() { - return array('definition'); - } - -} diff --git a/core/modules/field/lib/Drupal/field/FieldInstanceStorageController.php b/core/modules/field/lib/Drupal/field/FieldInstanceStorageController.php new file mode 100644 index 0000000..84ed9e4 --- /dev/null +++ b/core/modules/field/lib/Drupal/field/FieldInstanceStorageController.php @@ -0,0 +1,36 @@ +get()) { + return TRUE; + } + return parent::importDelete($name, $new_config, $old_config); + } + +} diff --git a/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/Field.php b/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/Field.php new file mode 100644 index 0000000..9877c2d --- /dev/null +++ b/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/Field.php @@ -0,0 +1,597 @@ +$field_id}. The maximum length is + * Field:ID_MAX_LENGTH. + * + * Example: body, field_main_image. + * + * @var string + */ + public $id; + + /** + * The field UUID. + * + * This is assigned automatically when the field is created. + * + * @var string + */ + public $uuid; + + /** + * The field type. + * + * Field types are defined by modules that implement hook_field_info(). + * + * Example: text, number_integer. + * + * @var string + */ + public $type; + + /** + * The name of the module that provides the field type. + * + * @var string + */ + public $module; + + /** + * Flag indicating whether the field type module is enabled. + * + * @var bool + */ + public $active; + + /** + * Field-type specific settings. + * + * An array of key/value pairs, The keys and default values are defined by the + * field type in the 'settings' entry of hook_field_info(). + * + * @var array + */ + public $settings; + + /** + * The field cardinality. + * + * The maximum number of values the field can hold. Possible values are + * positive integers or FIELD_CARDINALITY_UNLIMITED. Defaults to 1. + * + * @var integer + */ + public $cardinality; + + /** + * Flag indicating whether the field is translatable. + * + * Defaults to FALSE. + * + * @var bool + */ + public $translatable; + + /** + * The entity types on which the field is allowed to have instances. + * + * If empty or not specified, the field is allowed to have instances in any + * entity type. + * + * @var array + */ + public $entity_types; + + /** + * 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 instances cannot be created + * - existing instances cannot be deleted. + * Defaults to FALSE. + * + * @var bool + */ + public $locked; + + /** + * The field storage definition. + * + * An array of key/value pairs identifying the storage backend to use for the + * field: + * - type: (string) The storage backend used by the field. Storage backends + * are defined by modules that implement hook_field_storage_info(). + * - settings: (array) A sub-array of key/value pairs of settings. The keys + * and default values are defined by the storage backend in the 'settings' + * entry of hook_field_storage_info(). + * - module: (string, read-only) The name of the module that implements the + * storage backend. + * - active: (integer, read-only) TRUE if the module that implements the + * storage backend is currently enabled, FALSE otherwise. + * + * @var array + */ + public $storage; + + /** + * 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 + */ + public $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 + */ + public $deleted; + + /** + * The field schema. + * + * @var array + */ + protected $schema; + + /** + * The storage information for the field. + * + * @var array + */ + protected $storageDetails; + + /** + * Overrides \Drupal\Core\Config\Entity\ConfigEntityBase::__construct(). + */ + public function __construct(array $values, $entity_type = 'field_entity') { + // Check required properties. + if (empty($values['type'])) { + throw new FieldException('Attempt to create a field with no type.'); + } + // Temporary BC layer: accept both 'id' and 'field_name'. + if (empty($values['field_name']) && empty($values['id'])) { + throw new FieldException('Attempt to create an unnamed field.'); + } + if (empty($values['id'])) { + $values['id'] = $values['field_name']; + unset($values['field_name']); + } + if (!preg_match('/^[_a-z]+[_a-z0-9]*$/', $values['id'])) { + throw new FieldException('Attempt to create a field with invalid characters. Only lowercase alphanumeric characters and underscores are allowed, and only lowercase letters and underscore are allowed as the first character'); + } + + // Provide defaults. + $values += array( + 'settings' => array(), + 'cardinality' => 1, + 'translatable' => FALSE, + 'entity_types' => array(), + 'locked' => FALSE, + 'deleted' => 0, + 'storage' => array(), + 'indexes' => array(), + ); + parent::__construct($values, $entity_type); + } + + /** + * Overrides \Drupal\Core\Config\Entity\ConfigEntityBase::getExportProperties(). + */ + public function getExportProperties() { + $names = array( + 'id', + 'uuid', + 'status', + 'langcode', + 'type', + 'settings', + 'module', + 'active', + 'entity_types', + 'storage', + 'locked', + 'cardinality', + 'translatable', + 'indexes', + ); + $properties = array(); + foreach ($names as $name) { + $properties[$name] = $this->get($name); + } + return $properties; + } + + /** + * Overrides \Drupal\Core\Entity\Entity::save(). + */ + public function save() { + $module_handler = \Drupal::moduleHandler(); + $storage_controller = \Drupal::service('plugin.manager.entity')->getStorageController($this->entityType); + + // Clear the derived data about the field. + unset($this->schema, $this->storageDetails); + + if ($this->isNew()) { + // Field name cannot be longer than Field::ID_MAX_LENGTH characters. We + // use drupal_strlen() because the DB layer assumes that column widths + // are given in characters rather than bytes. + if (drupal_strlen($this->id) > static::ID_MAX_LENGTH) { + throw new FieldException(format_string( + 'Attempt to create a field with an ID longer than @max characters: %id', array( + '@max' => static::ID_MAX_LENGTH, + '%id' => $this->id, + ) + )); + } + + // Ensure the field name is unique (we do not care about deleted fields). + if ($prior_field = current($storage_controller->load(array($this->id)))) { + $message = $prior_field->active ? + 'Attempt to create field name %id which already exists and is active.' : + 'Attempt to create field name %id which already exists, although it is inactive.'; + throw new FieldException(format_string($message, array('%id' => $this->id))); + } + + // Disallow reserved field names. This can't prevent all field name + // collisions with existing entity properties, but some is better than + // none. + foreach (\Drupal::service('plugin.manager.entity')->getDefinitions() as $type => $info) { + if (in_array($this->id, $info['entity_keys'])) { + throw new FieldException(format_string('Attempt to create field %id which is reserved by entity type %type.', array('%id' => $this->id, '%type' => $type))); + } + } + + // Check that the field type is known. + $field_type = field_info_field_types($this->type); + if (!$field_type) { + throw new FieldException(format_string('Attempt to create a field of unknown type %type.', array('%type' => $this->type))); + } + $this->module = $field_type['module']; + $this->active = 1; + + // Create all per-field-type properties (needed here as long as we have + // settings that impact column definitions). + $this->settings += $field_type['settings']; + + // Provide default storage. + $this->storage += array( + 'type' => variable_get('field_storage_default', 'field_sql_storage'), + 'settings' => array(), + ); + // Check that the storage type is known. + $storage_type = field_info_storage_types($this->storage['type']); + if (!$storage_type) { + throw new FieldException(format_string('Attempt to create a field with unknown storage type %type.', array('%type' => $this->storage['type']))); + } + $this->storage['module'] = $storage_type['module']; + $this->storage['active'] = 1; + // Provide default storage settings. + $this->storage['settings'] += $storage_type['settings']; + + // Invoke the storage backend's hook_field_storage_create_field(). + $module_handler->invoke($this->storage['module'], 'field_storage_create_field', array($this)); + + $hook = 'field_create_field'; + $hook_args = array($this); + } + // Otherwise, the field is being updated. + else { + $original = $storage_controller->loadUnchanged($this->id()); + + // Some updates are always disallowed. + if ($this->type != $original->type) { + throw new FieldException("Cannot change an existing field's type."); + } + if ($this->entity_types != $original->entity_types) { + throw new FieldException("Cannot change an existing field's entity_types property."); + } + if ($this->storage['type'] != $original->storage['type']) { + throw new FieldException("Cannot change an existing field's storage type."); + } + + // Make sure all settings are present, so that a complete field + // definition is saved. This allows calling code to perform partial + // updates on a field object. + $this->settings += $original->settings; + + $has_data = field_has_data($this); + + // See if any module forbids the update by throwing an exception. This + // invokes hook_field_update_forbid(). + $module_handler->invokeAll('field_update_forbid', array($this, $original, $has_data)); + + // Tell the storage engine to update the field by invoking the + // hook_field_storage_update_field(). Do this before saving the + // new definition since it still might fail. + $module_handler->invoke($this->storage['module'], 'field_storage_update_field', array($this, $original, $has_data)); + + $hook = 'field_update_field'; + $hook_args = array($this, $original, $has_data); + } + + // Save the configuration. + $result = parent::save(); + field_cache_clear(); + + // Invoke external hooks after the cache is cleared for API consistency. + // This invokes either hook_field_create_field() or + // hook_field_update_field() depending on whether the field is new. + $module_handler->invokeAll($hook, $hook_args); + + return $result; + } + + /** + * Overrides \Drupal\Core\Entity\Entity::delete(). + */ + public function delete() { + if (!$this->deleted) { + $module_handler = \Drupal::moduleHandler(); + $instance_controller = \Drupal::service('plugin.manager.entity')->getStorageController('field_instance'); + $state = \Drupal::state(); + + // Delete all non-deleted instances. + $instance_ids = array(); + foreach ($this->getBundles() as $entity_type => $bundles) { + foreach ($bundles as $bundle) { + $instance_ids[] = "$entity_type.$bundle.$this->id"; + } + } + foreach ($instance_controller->load($instance_ids) as $instance) { + // By default, FieldInstance::delete() will automatically try to delete + // a field definition when it is deleting the last instance of the + // field. Since the whole field is being deleted here, pass FALSE as + // the $field_cleanup parameter to prevent a loop. + $instance->delete(FALSE); + } + + // Mark field data for deletion by invoking + // hook_field_storage_delete_field(). + $module_handler->invoke($this->storage['module'], 'field_storage_delete_field', array($this)); + + // Delete the configuration of this field and save the field configuration + // in the key_value table so we can use it later during + // field_purge_batch(). This makes sure a new field can be created + // immediately with the same name. + $deleted_fields = $state->get('field.field.deleted') ?: array(); + $config = $this->getExportProperties(); + $config['deleted'] = TRUE; + $deleted_fields[$this->uuid] = $config; + $state->set('field.field.deleted', $deleted_fields); + + parent::delete(); + + // Clear the cache. + field_cache_clear(); + + // Invoke hook_field_delete_field(). + $module_handler->invokeAll('field_delete_field', array($this)); + } + } + + /** + * Returns the field schema. + * + * @return array + * The field schema, as an array of key/value pairs in the format returned + * by hook_field_schema(): + * - columns: An array of Schema API column specifications, keyed by column + * name. This specifies what comprises a single value for a given field. + * No assumptions should be made on how storage backends internally use + * the original column name to structure their storage. + * - indexes: An array of Schema API index definitions. Some storage + * backends might not support indexes. + * - foreign keys: An array of Schema API foreign key definitions. Note, + * however, that depending on the storage backend specified for the field, + * the field data is not necessarily stored in SQL. + */ + public function getSchema() { + if (!isset($this->schema)) { + $module_handler = \Drupal::moduleHandler(); + + // Collect the schema from the field type. + // @todo Use $module_handler->loadInclude() once + // http://drupal.org/node/1941000 is fixed. + module_load_install($this->module); + // Invoke hook_field_schema() for the field. + $schema = (array) $module_handler->invoke($this->module, 'field_schema', array($this)); + $schema += array('columns' => array(), 'indexes' => array(), 'foreign keys' => array()); + + // Check that the schema does not include forbidden column names. + if (array_intersect(array_keys($schema['columns']), field_reserved_columns())) { + throw new FieldException('Illegal field type columns.'); + } + + // 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; + } + + /** + * Returns information about how the storage backend stores the field data. + * + * The content of the returned value depends on the storage backend, and some + * storage backends might provide no information. + * + * It is strongly discouraged to use this information to perform direct write + * operations to the field data storage, bypassing the regular field saving + * APIs. + * + * Example return value for the default field_sql_storage backend: + * - 'sql' + * - FIELD_LOAD_CURRENT + * - Table name (string). + * - Table schema (array) + * - FIELD_LOAD_REVISION + * - Table name (string). + * - Table schema (array). + * + * @return array + * The storage details. + * - The first dimension is a store type (sql, solr, etc). + * - The second dimension indicates the age of the values in the store + * FIELD_LOAD_CURRENT or FIELD_LOAD_REVISION. + * - Other dimensions are specific to the field storage backend. + + */ + public function getStorageDetails() { + if (!isset($this->storageDetails)) { + $module_handler = \Drupal::moduleHandler(); + + // Collect the storage details from the storage backend, and let other + // modules alter it. This invokes hook_field_storage_details() and + // hook_field_storage_details_alter(). + $details = (array) $module_handler->invoke($this->storage['module'], 'field_storage_details', array($this)); + $module_handler->alter('field_storage_details', $details, $this); + + $this->storageDetails = $details; + } + + return $this->storageDetails; + } + + /** + * Returns the list of bundles where the field has instances. + * + * @return array + * An array keyed by entity type names, whose values are arrays of bundle + * names. + */ + public function getBundles() { + if (empty($this->deleted)) { + $map = field_info_field_map(); + if (isset($map[$this->id]['bundles'])) { + return $map[$this->id]['bundles']; + } + } + return array(); + } + + /** + * Implements ArrayAccess::offsetExists(). + */ + public function offsetExists($offset) { + return isset($this->{$offset}) || in_array($offset, array('columns', 'foreign keys', 'bundles', 'storage_details')); + } + + /** + * Implements ArrayAccess::offsetGet(). + */ + public function &offsetGet($offset) { + switch ($offset) { + case 'id': + return $this->uuid; + + case 'field_name': + return $this->id; + + case 'columns': + $this->getSchema(); + return $this->schema['columns']; + + case 'foreign keys': + $this->getSchema(); + return $this->schema['foreign keys']; + + case 'bundles': + $bundles = $this->getBundles(); + return $bundles; + + case 'storage_details': + $this->getStorageDetails(); + return $this->storageDetails; + } + + return $this->{$offset}; + } + + /** + * Implements ArrayAccess::offsetSet(). + */ + public function offsetSet($offset, $value) { + if (!in_array($offset, array('columns', 'foreign keys', 'bundles', 'storage_details'))) { + $this->{$offset} = $value; + } + } + + /** + * Implements ArrayAccess::offsetUnset(). + */ + public function offsetUnset($offset) { + if (!in_array($offset, array('columns', 'foreign keys', 'bundles', 'storage_details'))) { + unset($this->{$offset}); + } + } + +} diff --git a/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/FieldInstance.php b/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/FieldInstance.php new file mode 100644 index 0000000..a8035ca --- /dev/null +++ b/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/FieldInstance.php @@ -0,0 +1,509 @@ +save()); the default value is + * added if the $entity object provides no explicit entry (actual values or + * "the field is empty") for the field. + * + * The default value is expressed as a numerically indexed array of items, + * each item being an array of key/value pairs matching the set of 'columns' + * defined by the "field schema" for the field type, as exposed in + * hook_field_schema(). If the number of items exceeds the cardinality of the + * field, extraneous items will be ignored. + * + * This property is overlooked if the $default_value_function is non-empty. + * + * Example for a number_integer field: + * @code + * array( + * array('value' => 1), + * array('value' => 2), + * ) + * @endcode + * + * @var array + */ + public $default_value; + + /** + * The name of a callback function that returns default values. + * + * The function will be called with the following arguments: + * - \Drupal\Core\Entity\EntityInterface $entity + * The entity being created. + * - \Drupal\field\Plugin\Core\Entity\Field $field + * The field object. + * - \Drupal\field\Plugin\Core\Entity\FieldInstance $instance + * The field instance object. + * - string $langcode + * The language of the entity being created. + * It should return an array of default values, in the same format as the + * $default_value property. + * + * This property takes precedence on the list of fixed values specified in the + * $default_value property. + * + * @var string + */ + public $default_value_function; + + /** + * The widget definition. + * + * An array of key/value pairs identifying the Form API input widget for + * the field when used by this bundle. + * - type: (string) The plugin ID of the widget, such as text_textfield. + * - settings: (array) A sub-array of key/value pairs of settings. The keys + * and default values are defined by the widget plugin in the 'settings' + * entry of its "plugin definition" (typycally plugin class annotations). + * - weight: (float) The weight of the widget relative to the other + * elements in entity edit forms. + * - module: (string, read-only) The name of the module that provides the + * widget plugin. + * + * @var array + */ + public $widget; + + /** + * Flag indicating whether the instance is deleted. + * + * The delete() method marks the instance 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 instances stay out of the regular entity lifecycle (notably, their + * values are not populated in loaded entities, and are not saved back). + * + * @var bool + */ + public $deleted; + + /** + * The widget plugin used for this instance. + * + * @var \Drupal\field\Plugin\Type\Widget\WidgetInterface + */ + protected $widgetPlugin; + + /** + * Overrides \Drupal\Core\Config\Entity\ConfigEntityBase::__construct(). + */ + public function __construct(array $values, $entity_type = 'field_instance') { + // Check required properties. + if (empty($values['entity_type'])) { + throw new FieldException(format_string('Attempt to create an instance of field @field_name without an entity type.', array('@field_name' => $values['field_name']))); + } + if (empty($values['bundle'])) { + throw new FieldException(format_string('Attempt to create an instance of field @field_name without a bundle.', array('@field_name' => $values['field_name']))); + } + + // Accept incoming 'field_name' instead of 'field_uuid', for easier DX on + // creation of new instances. + if (isset($values['field_name']) && !isset($values['field_uuid'])) { + $field = field_info_field($values['field_name']); + if ($field) { + $values['field_uuid'] = $field->uuid; + } + else { + throw new FieldException(format_string('Attempt to create an instance of unknown, disabled, or deleted field @name', array('@name' => $values['field_name']))); + } + } + // Fill in the field_name property for data coming out of config. + // @todo Revisit that in favor of a getField() method. + elseif (isset($values['field_uuid']) && !isset($values['field_name'])) { + $field = current(field_read_fields(array('uuid' => $values['field_uuid']), array('include_inactive' => TRUE, 'include_deleted' => TRUE))); + if ($field) { + $values['field_name'] = $field->id; + } + else { + throw new FieldException(format_string('Attempt to create an instance of unknown field @uuid', array('@uuid' => $values['field_uuid']))); + } + } + + if (empty($values['field_uuid'])) { + throw new FieldException('Attempt to create an instance of an unspecified field.'); + } + + // Provide defaults. + $values += array( + 'label' => $values['field_name'], + 'description' => '', + 'required' => FALSE, + 'default_value' => array(), + 'default_value_function' => '', + 'settings' => array(), + 'widget' => array(), + 'deleted' => 0, + ); + parent::__construct($values, $entity_type); + } + + /** + * Overrides \Drupal\Core\Entity\Entity::id(). + */ + public function id() { + return $this->entity_type . '.' . $this->bundle . '.' . $this->field_name; + } + + /** + * Overrides \Drupal\Core\Config\Entity\ConfigEntityBase::getExportProperties(). + */ + public function getExportProperties() { + $names = array( + 'id', + 'uuid', + 'status', + 'langcode', + 'field_uuid', + 'entity_type', + 'bundle', + 'label', + 'description', + 'required', + 'default_value', + 'default_value_function', + 'settings', + 'widget', + ); + $properties = array(); + foreach ($names as $name) { + $properties[$name] = $this->get($name); + } + return $properties; + } + + /** + * Overrides \Drupal\Core\Entity\Entity::save(). + */ + public function save() { + $module_handler = \Drupal::moduleHandler(); + $entity_manager = \Drupal::service('plugin.manager.entity'); + $field_controller = $entity_manager->getStorageController('field_entity'); + $instance_controller = $entity_manager->getStorageController($this->entityType); + + $field = current($field_controller->load(array($this->field_name))); + + if ($this->isNew()) { + if (empty($field)) { + throw new FieldException(format_string("Attempt to save an instance of a field @field_id that doesn't exist or is currently inactive.", array('@field_name' => $this->field_name))); + } + // Check that the field can be attached to this entity type. + if (!empty($field->entity_types) && !in_array($this->entity_type, $field->entity_types)) { + throw new FieldException(format_string('Attempt to create an instance of field @field_name on forbidden entity type @entity_type.', array('@field_name' => $this->field_name, '@entity_type' => $this->entity_type))); + } + + // Assign the ID. + $this->id = $this->id(); + + // Ensure the field instance is unique within the bundle. + if ($prior_instance = current($instance_controller->load(array($this->id)))) { + throw new FieldException(format_string('Attempt to create an instance of field @field_name on bundle @bundle that already has an instance of that field.', array('@field_name' => $this->field_name, '@bundle' => $this->bundle))); + } + + // Set the field UUID. + $this->field_uuid = $field->uuid; + + $hook = 'field_create_instance'; + $hook_args = array($this); + } + // Otherwise, the field instance is being updated. + else { + $original = \Drupal::service('plugin.manager.entity') + ->getStorageController($this->entityType) + ->loadUnchanged($this->getOriginalID()); + + // Some updates are always disallowed. + if ($this->entity_type != $original->entity_type) { + throw new FieldException("Cannot change an existing instance's entity_type."); + } + // Renaming a bundle on the instance is allowed when an entity's bundle + // is renamed and when field_entity_bundle_rename() does internal + // housekeeping. + if ($this->bundle != $original->bundle && !isset($this->bundle_rename_allowed)) { + throw new FieldException("Cannot change an existing instance's bundle."); + } + if ($this->field_name != $original->field_name || $this->field_uuid != $original->field_uuid) { + throw new FieldException("Cannot change an existing instance's field."); + } + + $hook = 'field_update_instance'; + $hook_args = array($this, $original); + } + + $field_type_info = field_info_field_types($field->type); + + // Set the default instance settings. + $this->settings += $field_type_info['instance_settings']; + + // Set the default widget and settings. + $this->widget += array( + 'type' => $field_type_info['default_widget'], + 'settings' => array(), + ); + // Get the widget module and settings from the widget type. + if ($widget_type_info = \Drupal::service('plugin.manager.field.widget')->getDefinition($this->widget['type'])) { + $this->widget['module'] = $widget_type_info['module']; + $this->widget['settings'] += $widget_type_info['settings']; + } + // If no weight is specified, make sure the field sinks to the bottom. + if (!isset($this->widget['weight'])) { + $max_weight = field_info_max_weight($this->entity_type, $this->bundle, 'form'); + $this->widget['weight'] = isset($max_weight) ? $max_weight + 1 : 0; + } + + // Save the configuration. + $result = parent::save(); + field_cache_clear(); + + // Invoke external hooks after the cache is cleared for API consistency. + // This invokes hook_field_create_instance() or hook_field_update_instance() + // depending on whether the field is new. + $module_handler->invokeAll($hook, $hook_args); + + return $result; + } + + /** + * Overrides \Drupal\Core\Entity\Entity::delete(). + * + * @param bool $field_cleanup + * (optional) If TRUE, the field will be deleted as well if its last + * instance is being deleted. If FALSE, it is the caller's responsibility to + * handle the case of fields left without instances. Defaults to TRUE. + */ + public function delete($field_cleanup = TRUE) { + if (!$this->deleted) { + $module_handler = \Drupal::moduleHandler(); + $state = \Drupal::state(); + + // Delete the configuration of this instance and save the configuration + // in the key_value table so we can use it later during + // field_purge_batch(). + $deleted_instances = $state->get('field.instance.deleted') ?: array(); + $config = $this->getExportProperties(); + $config['deleted'] = TRUE; + $deleted_instances[$this->uuid] = $config; + $state->set('field.instance.deleted', $deleted_instances); + + parent::delete(); + + // Clear the cache. + field_cache_clear(); + + // Mark instance data for deletion by invoking + // hook_field_storage_delete_instance(). + $field = field_info_field($this->field_name); + $module_handler->invoke($field->storage['module'], 'field_storage_delete_instance', array($this)); + + // Let modules react to the deletion of the instance with + // hook_field_delete_instance(). + $module_handler->invokeAll('field_delete_instance', array($this)); + + // Delete the field itself if we just deleted its last instance. + if ($field_cleanup && count($field->getBundles()) == 0) { + $field->delete(); + } + } + } + + /** + * Returns the Widget plugin for the instance. + * + * @return Drupal\field\Plugin\Type\Widget\WidgetInterface + * The Widget plugin to be used for the instance. + */ + public function getWidget() { + if (empty($this->widgetPlugin)) { + $widget_properties = $this->widget; + + // Let modules alter the widget properties. + $context = array( + 'entity_type' => $this->entity_type, + 'bundle' => $this->bundle, + 'field' => field_info_field_by_id($this->field_uuid), + 'instance' => $this, + ); + // Invoke hook_field_widget_properties_alter() and + // hook_field_widget_properties_ENTITY_TYPE_alter(). + drupal_alter(array('field_widget_properties', 'field_widget_properties_' . $this->entity_type), $widget_properties, $context); + + $options = array( + 'instance' => $this, + 'type' => $widget_properties['type'], + 'settings' => $widget_properties['settings'], + 'weight' => $widget_properties['weight'], + ); + $this->widgetPlugin = \Drupal::service('plugin.manager.field.widget')->getInstance($options); + } + + return $this->widgetPlugin; + } + + /** + * Implements ArrayAccess::offsetExists(). + */ + public function offsetExists($offset) { + return (isset($this->{$offset}) || $offset == 'field_id'); + } + + /** + * Implements ArrayAccess::offsetGet(). + */ + public function &offsetGet($offset) { + if ($offset == 'field_id') { + return $this->field_uuid; + } + return $this->{$offset}; + } + + /** + * Implements ArrayAccess::offsetSet(). + */ + public function offsetSet($offset, $value) { + if ($offset == 'field_id') { + $offset = 'field_uuid'; + } + $this->{$offset} = $value; + } + + /** + * Implements ArrayAccess::offsetUnset(). + */ + public function offsetUnset($offset) { + if ($offset == 'field_id') { + $offset = 'field_uuid'; + } + unset($this->{$offset}); + } + +} + diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterBase.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterBase.php index 92d6fba..5b2473a 100644 --- a/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterBase.php +++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterBase.php @@ -9,7 +9,7 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\field\Plugin\PluginSettingsBase; -use Drupal\field\FieldInstance; +use Drupal\field\Plugin\Core\Entity\FieldInstance; /** * Base class for 'Field formatter' plugin implementations. diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterInterface.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterInterface.php index 36693b0..4058810 100644 --- a/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterInterface.php +++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterInterface.php @@ -8,7 +8,7 @@ namespace Drupal\field\Plugin\Type\Formatter; use Drupal\Core\Entity\EntityInterface; -use Drupal\field\FieldInstance; +use Drupal\field\Plugin\Core\Entity\FieldInstance; use Drupal\field\Plugin\PluginSettingsInterface; /** diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterPluginManager.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterPluginManager.php index 424f272..4057c88 100644 --- a/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterPluginManager.php +++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterPluginManager.php @@ -13,7 +13,7 @@ use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery; use Drupal\Core\Plugin\Discovery\AlterDecorator; use Drupal\field\Plugin\Type\Formatter\FormatterLegacyDiscoveryDecorator; -use Drupal\field\FieldInstance; +use Drupal\field\Plugin\Core\Entity\FieldInstance; /** * Plugin type manager for field formatters. diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetBase.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetBase.php index b60552e..39efcc8 100644 --- a/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetBase.php +++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetBase.php @@ -10,7 +10,7 @@ use Drupal\Component\Utility\NestedArray; use Drupal\Core\Entity\EntityInterface; use Drupal\field\Plugin\PluginSettingsBase; -use Drupal\field\FieldInstance; +use Drupal\field\Plugin\Core\Entity\FieldInstance; /** * Base class for 'Field widget' plugin implementations. @@ -52,7 +52,7 @@ * The plugin_id for the widget. * @param array $plugin_definition * The plugin implementation definition. - * @param Drupal\field\FieldInstance $instance + * @param \Drupal\field\Plugin\Core\Entity\FieldInstance $instance * The field instance to which the widget is associated. * @param array $settings * The widget settings. diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetInterface.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetInterface.php index c0ffc95..8711bbd 100644 --- a/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetInterface.php +++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetInterface.php @@ -8,7 +8,7 @@ namespace Drupal\field\Plugin\Type\Widget; use Drupal\Core\Entity\EntityInterface; -use Drupal\field\FieldInstance; +use Drupal\field\Plugin\Core\Entity\FieldInstance; /** * Interface definition for field widget plugins. diff --git a/core/modules/field/lib/Drupal/field/Plugin/views/field/Field.php b/core/modules/field/lib/Drupal/field/Plugin/views/field/Field.php index c73260f..7c94f28 100644 --- a/core/modules/field/lib/Drupal/field/Plugin/views/field/Field.php +++ b/core/modules/field/lib/Drupal/field/Plugin/views/field/Field.php @@ -169,8 +169,8 @@ public function query($use_groupby = FALSE) { // Go through the list and determine the actual column name from field api. foreach ($options as $column) { $name = $column; - if (isset($this->field_info['storage']['details']['sql'][$rkey][$this->table][$column])) { - $name = $this->field_info['storage']['details']['sql'][$rkey][$this->table][$column]; + if (isset($this->field_info['storage_details']['sql'][$rkey][$this->table][$column])) { + $name = $this->field_info['storage_details']['sql'][$rkey][$this->table][$column]; } $fields[$column] = $name; @@ -451,7 +451,6 @@ function fakeFieldInstance($formatter, $formatter_settings) { ), // Set the other fields to their default values. - // @see _field_write_instance(). 'required' => FALSE, 'label' => $field_name, 'description' => '', diff --git a/core/modules/field/lib/Drupal/field/Tests/BulkDeleteTest.php b/core/modules/field/lib/Drupal/field/Tests/BulkDeleteTest.php index 0635f38..e8d11c0 100644 --- a/core/modules/field/lib/Drupal/field/Tests/BulkDeleteTest.php +++ b/core/modules/field/lib/Drupal/field/Tests/BulkDeleteTest.php @@ -7,6 +7,8 @@ namespace Drupal\field\Tests; +use Drupal\field\Plugin\Core\Entity\FieldInstance; + /** * Unit test class for field bulk delete and batch purge functionality. */ @@ -164,7 +166,7 @@ function testDeleteFieldInstance() { field_delete_instance($instance); // The instance still exists, deleted. - $instances = field_read_instances(array('field_id' => $field['id'], 'deleted' => 1), array('include_deleted' => 1, 'include_inactive' => 1)); + $instances = field_read_instances(array('field_id' => $field['uuid'], 'deleted' => 1), array('include_deleted' => 1, 'include_inactive' => 1)); $this->assertEqual(count($instances), 1, 'There is one deleted instance'); $this->assertEqual($instances[0]['bundle'], $bundle, 'The deleted instance is for the correct bundle'); @@ -191,7 +193,7 @@ function testDeleteFieldInstance() { $ids->entity_id = $entity_id; $entities[$entity_id] = _field_create_entity_from_ids($ids); } - field_attach_load($this->entity_type, $entities, FIELD_LOAD_CURRENT, array('field_id' => $field['id'], 'deleted' => 1)); + field_attach_load($this->entity_type, $entities, FIELD_LOAD_CURRENT, array('field_id' => $field['uuid'], 'deleted' => 1)); $this->assertEqual(count($found), 10, 'Correct number of entities found after deleting'); foreach ($entities as $id => $entity) { $this->assertEqual($this->entities[$id]->{$field['field_name']}, $entity->{$field['field_name']}, "Entity $id with deleted data loaded correctly"); @@ -247,19 +249,19 @@ function testPurgeInstance() { $this->checkHooksInvocations($hooks, $actual_hooks); // The instance still exists, deleted. - $instances = field_read_instances(array('field_id' => $field['id'], 'deleted' => 1), array('include_deleted' => 1, 'include_inactive' => 1)); + $instances = field_read_instances(array('field_id' => $field['uuid'], 'deleted' => 1), array('include_deleted' => 1, 'include_inactive' => 1)); $this->assertEqual(count($instances), 1, 'There is one deleted instance'); // Purge the instance. field_purge_batch($batch_size); // The instance is gone. - $instances = field_read_instances(array('field_id' => $field['id'], 'deleted' => 1), array('include_deleted' => 1, 'include_inactive' => 1)); + $instances = field_read_instances(array('field_id' => $field['uuid'], 'deleted' => 1), array('include_deleted' => 1, 'include_inactive' => 1)); $this->assertEqual(count($instances), 0, 'The instance is gone'); // The field still exists, not deleted, because it has a second instance. - $fields = field_read_fields(array('id' => $field['id']), array('include_deleted' => 1, 'include_inactive' => 1)); - $this->assertTrue(isset($fields[$field['id']]), 'The field exists and is not deleted'); + $fields = field_read_fields(array('uuid' => $field['uuid']), array('include_deleted' => 1, 'include_inactive' => 1)); + $this->assertTrue(isset($fields[$field['uuid']]), 'The field exists and is not deleted'); } /** @@ -300,8 +302,8 @@ function testPurgeField() { field_purge_batch(0); // The field still exists, not deleted. - $fields = field_read_fields(array('id' => $field['id']), array('include_deleted' => 1)); - $this->assertTrue(isset($fields[$field['id']]) && !$fields[$field['id']]['deleted'], 'The field exists and is not deleted'); + $fields = field_read_fields(array('uuid' => $field['uuid']), array('include_deleted' => 1)); + $this->assertTrue(isset($fields[$field['uuid']]) && !$fields[$field['uuid']]->deleted, 'The field exists and is not deleted'); // Delete the second instance. $bundle = next($this->bundles); @@ -324,14 +326,14 @@ function testPurgeField() { $this->checkHooksInvocations($hooks, $actual_hooks); // The field still exists, deleted. - $fields = field_read_fields(array('id' => $field['id']), array('include_deleted' => 1)); - $this->assertTrue(isset($fields[$field['id']]) && $fields[$field['id']]['deleted'], 'The field exists and is deleted'); + $fields = field_read_fields(array('uuid' => $field['uuid']), array('include_deleted' => 1)); + $this->assertTrue(isset($fields[$field['uuid']]) && $fields[$field['uuid']]->deleted, 'The field exists and is deleted'); // Purge again to purge the instance and the field. field_purge_batch(0); // The field is gone. - $fields = field_read_fields(array('id' => $field['id']), array('include_deleted' => 1, 'include_inactive' => 1)); + $fields = field_read_fields(array('uuid' => $field['uuid']), array('include_deleted' => 1, 'include_inactive' => 1)); $this->assertEqual(count($fields), 0, 'The field is purged.'); } } diff --git a/core/modules/field/lib/Drupal/field/Tests/CrudTest.php b/core/modules/field/lib/Drupal/field/Tests/CrudTest.php index f7c3a9c..a0afa3f 100644 --- a/core/modules/field/lib/Drupal/field/Tests/CrudTest.php +++ b/core/modules/field/lib/Drupal/field/Tests/CrudTest.php @@ -41,28 +41,29 @@ function testCreateField() { 'type' => 'test_field', ); field_test_memorize(); - $field_definition = field_create_field($field_definition); + $field = field_create_field($field_definition); $mem = field_test_memorize(); - $this->assertIdentical($mem['field_test_field_create_field'][0][0], $field_definition, 'hook_field_create_field() called with correct arguments.'); + $this->assertIdentical($mem['field_test_field_create_field'][0][0]['field_name'], $field_definition['field_name'], 'hook_field_create_field() called with correct arguments.'); + $this->assertIdentical($mem['field_test_field_create_field'][0][0]['type'], $field_definition['type'], 'hook_field_create_field() called with correct arguments.'); - // Read the raw record from the {field_config_instance} table. - $result = db_query('SELECT * FROM {field_config} WHERE field_name = :field_name', array(':field_name' => $field_definition['field_name'])); - $record = $result->fetchAssoc(); - $record['data'] = unserialize($record['data']); + // Read the configuration. Check against raw configuration data rather than + // the loaded ConfigEntity, to be sure we check that the defaults are + // applied on write. + $field_config = \Drupal::config('field.field.' . $field->id())->get(); // Ensure that basic properties are preserved. - $this->assertEqual($record['field_name'], $field_definition['field_name'], 'The field name is properly saved.'); - $this->assertEqual($record['type'], $field_definition['type'], 'The field type is properly saved.'); + $this->assertEqual($field_config['id'], $field_definition['field_name'], 'The field name is properly saved.'); + $this->assertEqual($field_config['type'], $field_definition['type'], 'The field type is properly saved.'); // Ensure that cardinality defaults to 1. - $this->assertEqual($record['cardinality'], 1, 'Cardinality defaults to 1.'); + $this->assertEqual($field_config['cardinality'], 1, 'Cardinality defaults to 1.'); // Ensure that default settings are present. $field_type = field_info_field_types($field_definition['type']); - $this->assertIdentical($record['data']['settings'], $field_type['settings'], 'Default field settings have been written.'); + $this->assertEqual($field_config['settings'], $field_type['settings'], 'Default field settings have been written.'); // Ensure that default storage was set. - $this->assertEqual($record['storage_type'], variable_get('field_storage_default'), 'The field type is properly saved.'); + $this->assertEqual($field_config['storage']['type'], variable_get('field_storage_default'), 'The field type is properly saved.'); // Guarantee that the name is unique. try { @@ -143,7 +144,7 @@ function testCreateField() { 'type' => 'test_field', 'field_name' => 'ftvid', ); - $field = field_create_field($field_definition); + field_create_field($field_definition); $this->fail(t('Cannot create a field bearing the name of an entity key.')); } catch (FieldException $e) { @@ -157,11 +158,10 @@ function testCreateField() { function testCreateFieldFail() { $field_name = 'duplicate'; $field_definition = array('field_name' => $field_name, 'type' => 'test_field', 'storage' => array('type' => 'field_test_storage_failure')); - $query = db_select('field_config')->condition('field_name', $field_name)->countQuery(); + $field = entity_load('field_entity', $field_name); - // The field does not appear in field_config. - $count = $query->execute()->fetchField(); - $this->assertEqual($count, 0, 'A field_config row for the field does not exist.'); + // The field does not exist. + $this->assertFalse($field, 'The field does not exist.'); // Try to create the field. try { @@ -172,9 +172,9 @@ function testCreateFieldFail() { $this->assertTrue(TRUE, 'Field creation (correctly) fails.'); } - // The field does not appear in field_config. - $count = $query->execute()->fetchField(); - $this->assertEqual($count, 0, 'A field_config row for the field does not exist.'); + // The field does not exist. + $field = entity_load('field_entity', $field_name); + $this->assertFalse($field, 'The field does not exist.'); } /** @@ -219,12 +219,6 @@ function testReadFields() { 'bundle' => 'test_bundle', ); field_create_instance($instance_definition); - - // Check that criteria spanning over the field_config_instance table work. - $fields = field_read_fields(array('entity_type' => $instance_definition['entity_type'], 'bundle' => $instance_definition['bundle'])); - $this->assertTrue(count($fields) == 1 && isset($fields[$field_definition['field_name']]), 'The field was properly read.'); - $fields = field_read_fields(array('entity_type' => $instance_definition['entity_type'], 'field_name' => $instance_definition['field_name'])); - $this->assertTrue(count($fields) == 1 && isset($fields[$field_definition['field_name']]), 'The field was properly read.'); } /** @@ -238,8 +232,9 @@ function testFieldIndexes() { ); field_create_field($field_definition); $field = field_read_field($field_definition['field_name']); + $schema = $field->getSchema(); $expected_indexes = array('value' => array('value')); - $this->assertEqual($field['indexes'], $expected_indexes, 'Field type indexes saved by default'); + $this->assertEqual($schema['indexes'], $expected_indexes, 'Field type indexes saved by default'); // Check that indexes specified by the field definition override the field // type indexes. @@ -252,8 +247,9 @@ function testFieldIndexes() { ); field_create_field($field_definition); $field = field_read_field($field_definition['field_name']); + $schema = $field->getSchema(); $expected_indexes = array('value' => array()); - $this->assertEqual($field['indexes'], $expected_indexes, 'Field definition indexes override field type indexes'); + $this->assertEqual($schema['indexes'], $expected_indexes, 'Field definition indexes override field type indexes'); // Check that indexes specified by the field definition add to the field // type indexes. @@ -266,8 +262,9 @@ function testFieldIndexes() { ); field_create_field($field_definition); $field = field_read_field($field_definition['field_name']); + $schema = $field->getSchema(); $expected_indexes = array('value' => array('value'), 'value_2' => array('value')); - $this->assertEqual($field['indexes'], $expected_indexes, 'Field definition indexes are merged with field type indexes'); + $this->assertEqual($schema['indexes'], $expected_indexes, 'Field definition indexes are merged with field type indexes'); } /** @@ -351,17 +348,6 @@ function testDeleteField() { } } - function testUpdateNonExistentField() { - $test_field = array('field_name' => 'does_not_exist', 'type' => 'number_decimal'); - try { - field_update_field($test_field); - $this->fail(t('Cannot update a field that does not exist.')); - } - catch (FieldException $e) { - $this->pass(t('Cannot update a field that does not exist.')); - } - } - function testUpdateFieldType() { $field = array('field_name' => 'field_type', 'type' => 'number_decimal'); $field = field_create_field($field); @@ -384,18 +370,16 @@ function testUpdateField() { // respected. Since cardinality enforcement is consistent across database // systems, it makes a good test case. $cardinality = 4; - $field_definition = array( + $field = field_create_field(array( 'field_name' => 'field_update', 'type' => 'test_field', 'cardinality' => $cardinality, - ); - $field_definition = field_create_field($field_definition); - $instance = array( + )); + $instance = field_create_instance(array( 'field_name' => 'field_update', 'entity_type' => 'test_entity', 'bundle' => 'test_bundle', - ); - $instance = field_create_instance($instance); + )); do { // We need a unique ID for our entity. $cardinality will do. @@ -410,14 +394,14 @@ function testUpdateField() { // Load back and assert there are $cardinality number of values. $entity = field_test_create_entity($id, $id, $instance['bundle']); field_attach_load('test_entity', array($id => $entity)); - $this->assertEqual(count($entity->field_update[LANGUAGE_NOT_SPECIFIED]), $field_definition['cardinality'], 'Cardinality is kept'); + $this->assertEqual(count($entity->field_update[LANGUAGE_NOT_SPECIFIED]), $field['cardinality'], 'Cardinality is kept'); // Now check the values themselves. for ($delta = 0; $delta < $cardinality; $delta++) { $this->assertEqual($entity->field_update[LANGUAGE_NOT_SPECIFIED][$delta]['value'], $delta, 'Value is kept'); } // Increase $cardinality and set the field cardinality to the new value. - $field_definition['cardinality'] = ++$cardinality; - field_update_field($field_definition); + $field['cardinality'] = ++$cardinality; + field_update_field($field); } while ($cardinality < 6); } @@ -444,4 +428,5 @@ function testUpdateFieldForbid() { $this->pass(t("An unchangeable setting cannot be updated.")); } } + } diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldAttachStorageTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldAttachStorageTest.php index 9ec10f3..38499c8 100644 --- a/core/modules/field/lib/Drupal/field/Tests/FieldAttachStorageTest.php +++ b/core/modules/field/lib/Drupal/field/Tests/FieldAttachStorageTest.php @@ -117,7 +117,7 @@ function testFieldAttachLoadMultiple() { $field_names[$i] = 'field_' . $i; $field = array('field_name' => $field_names[$i], 'type' => 'test_field'); $field = field_create_field($field); - $field_ids[$i] = $field['id']; + $field_ids[$i] = $field['uuid']; foreach ($field_bundles_map[$i] as $bundle) { $instance = array( 'field_name' => $field_names[$i], @@ -242,9 +242,9 @@ function testFieldStorageDetailsAlter() { $instance = field_info_instance($instance['entity_type'], $instance['field_name'], $instance['bundle']); // The storage details are indexed by a storage engine type. - $this->assertTrue(array_key_exists('drupal_variables', $field['storage']['details']), 'The storage type is Drupal variables.'); + $this->assertTrue(array_key_exists('drupal_variables', $field['storage_details']), 'The storage type is Drupal variables.'); - $details = $field['storage']['details']['drupal_variables']; + $details = $field['storage_details']['drupal_variables']; // The field_test storage details are indexed by variable name. The details // are altered, so moon and mars are correct for this test. @@ -253,7 +253,7 @@ function testFieldStorageDetailsAlter() { // Test current and revision storage details together because the columns // are the same. - foreach ((array) $field['columns'] as $column_name => $attributes) { + foreach ($field['columns'] as $column_name => $attributes) { $this->assertEqual($details[FIELD_LOAD_CURRENT]['moon'][$column_name], $column_name, format_string('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => 'moon[FIELD_LOAD_CURRENT]'))); $this->assertEqual($details[FIELD_LOAD_REVISION]['mars'][$column_name], $column_name, format_string('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => 'mars[FIELD_LOAD_REVISION]'))); } diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldImportChangeTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldImportChangeTest.php new file mode 100644 index 0000000..aaf648d --- /dev/null +++ b/core/modules/field/lib/Drupal/field/Tests/FieldImportChangeTest.php @@ -0,0 +1,64 @@ + 'Field config change tests', + 'description' => 'Update field and instances during config change method invocation.', + 'group' => 'Field API', + ); + } + + /** + * Tests importing an updated field instance. + */ + function testImportChange() { + $field_id = 'field_test_import'; + $instance_id = "test_entity.test_bundle.$field_id"; + $instance_config_name = "field.instance.$instance_id"; + + // Import default config. + $this->installConfig(array('field_test_config')); + + // Simulate config data to import: + // - the current manifest for field instances, + // - a modified version (modified label) of the instance config. + $manifest_name = 'manifest.field.instance'; + $active = $this->container->get('config.storage'); + $manifest = $active->read($manifest_name); + $instance = $active->read($instance_config_name); + $new_label = 'Test update import field'; + $instance['label'] = $new_label; + + // Save as files in the the staging directory. + $staging = $this->container->get('config.storage.staging'); + $staging->write($manifest_name, $manifest); + $staging->write($instance_config_name, $instance); + + // Import the content of the staging directory. + config_import(); + + // Check that the updated config was correctly imported. + $instance = entity_load('field_instance', $instance_id); + $this->assertEqual($instance['label'], $new_label, 'Instance label updated'); + } + +} diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldImportCreateTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldImportCreateTest.php new file mode 100644 index 0000000..ff21fac --- /dev/null +++ b/core/modules/field/lib/Drupal/field/Tests/FieldImportCreateTest.php @@ -0,0 +1,81 @@ + 'Field config create tests', + 'description' => 'Create field and instances during config create method invocation.', + 'group' => 'Field API', + ); + } + + /** + * Tests creating fields and instances during default config import. + */ + function testImportCreateDefault() { + $field_id = 'field_test_import'; + $instance_id = "test_entity.test_bundle.$field_id"; + + // Check that the field and instance do not exist yet. + $this->assertFalse(entity_load('field_entity', $field_id)); + $this->assertFalse(entity_load('field_instance', $instance_id)); + + // Enable field_test_config module and check that the field and instance + // shipped in the module's default config were created. + module_enable(array('field_test_config')); + $field = entity_load('field_entity', $field_id); + $this->assertTrue($field, 'The field was created.'); + $instance = entity_load('field_instance', $instance_id); + $this->assertTrue($instance, 'The field instance was deleted.'); + } + + /** + * Tests creating fields and instances during config import. + */ + function testImportCreate() { + $field_id = 'field_test_import_staging'; + $instance_id = "test_entity.test_bundle.$field_id"; + $field_config_name = "field.field.$field_id"; + $instance_config_name = "field.instance.$instance_id"; + + // Simulate config data to import: + $src_dir = drupal_get_path('module', 'field_test_config') . '/staging'; + $this->assertTrue(file_unmanaged_copy("$src_dir/$field_config_name.yml", "public://config_staging/$field_config_name.yml")); + $this->assertTrue(file_unmanaged_copy("$src_dir/$instance_config_name.yml", "public://config_staging/$instance_config_name.yml")); + + // Add the coresponding entries to the current manifest data. + $field_manifest_name = 'manifest.field.field'; + $instance_manifest_name = 'manifest.field.instance'; + $active = $this->container->get('config.storage'); + $field_manifest = $active->read($field_manifest_name); + $field_manifest[$field_id] = array('name' => $field_config_name); + $instance_manifest = $active->read($instance_manifest_name); + $instance_manifest[$instance_id] = array('name' => $instance_config_name); + + // Save the manifests as files in the the staging directory. + $staging = $this->container->get('config.storage.staging'); + $staging->write($field_manifest_name, $field_manifest); + $staging->write($instance_manifest_name, $instance_manifest); + + // Import the content of the staging directory. + config_import(); + + // Check that the field and instance were created. + $field = entity_load('field_entity', $field_id); + $this->assertTrue($field, 'Test import field from staging exists'); + $instance = entity_load('field_instance', $instance_id); + $this->assertTrue($instance, 'Test import field instance from staging exists'); + } + +} diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldImportDeleteTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldImportDeleteTest.php new file mode 100644 index 0000000..0db6455 --- /dev/null +++ b/core/modules/field/lib/Drupal/field/Tests/FieldImportDeleteTest.php @@ -0,0 +1,93 @@ + 'Field config delete tests', + 'description' => 'Delete field and instances during config delete method invocation.', + 'group' => 'Field API', + ); + } + + /** + * Tests deleting fields and instances as part of config import. + */ + function testImportDelete() { + $field_id = 'field_test_import'; + $instance_id = "test_entity.test_bundle.$field_id"; + $field_config_name = "field.field.$field_id"; + $instance_config_name = "field.instance.$instance_id"; + + // Import default config. + $this->installConfig(array('field_test_config')); + + // Check that the config was correctly imported. + $field = entity_load('field_entity', $field_id); + $this->assertTrue($field, 'The field was created.'); + $instance = entity_load('field_instance', $instance_id); + $this->assertTrue($instance, 'The field instance was created.'); + + $field_uuid = $field->uuid; + + // Simulate config data to import: + // - the current manifest for fields, without the entry for the field we + // remove, + // - the current manifest for instances, without the entry for the instance + // we remove. + $field_manifest_name = 'manifest.field.field'; + $instance_manifest_name = 'manifest.field.instance'; + $active = $this->container->get('config.storage'); + $field_manifest = $active->read($field_manifest_name); + unset($field_manifest[$field_id]); + $instance_manifest = $active->read($instance_manifest_name); + unset($instance_manifest[$instance_id]); + + // Save as files in the the staging directory. + $staging = $this->container->get('config.storage.staging'); + $staging->write($field_manifest_name, $field_manifest); + $staging->write($instance_manifest_name, $instance_manifest); + + // Import the content of the staging directory. + config_import(); + + // Check that the field and instance are gone. + $field = entity_load('field_entity', $field_id, TRUE); + $this->assertFalse($field, 'The field was deleted.'); + $instance = entity_load('field_instance', $instance_id, TRUE); + $this->assertFalse($instance, 'The field instance was deleted.'); + + // Check that all config files are gone. + $active = $this->container->get('config.storage'); + $this->assertIdentical($active->listAll($field_config_name), array()); + $this->assertIdentical($active->listAll($instance_config_name), array()); + + // Check that the field definition is preserved in state. + $deleted_fields = \Drupal::state()->get('field.field.deleted') ?: array(); + $this->assertTrue(isset($deleted_fields[$field_uuid])); + + // Purge field data, and check that the field definition has been completely + // removed once the data is purged. + field_purge_batch(10); + $deleted_fields = \Drupal::state()->get('field.field.deleted') ?: array(); + $this->assertTrue(empty($deleted_fields), 'Fields are deleted'); + } + +} diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldInfoTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldInfoTest.php index f9a1031..0ae1dc0 100644 --- a/core/modules/field/lib/Drupal/field/Tests/FieldInfoTest.php +++ b/core/modules/field/lib/Drupal/field/Tests/FieldInfoTest.php @@ -131,27 +131,22 @@ function testFieldPrepare() { 'field_name' => 'field', 'type' => 'test_field', ); - field_create_field($field_definition); + $field = field_create_field($field_definition); // Simulate a stored field definition missing a field setting (e.g. a // third-party module adding a new field setting has been enabled, and // existing fields do not know the setting yet). - $data = db_query('SELECT data FROM {field_config} WHERE field_name = :field_name', array(':field_name' => $field_definition['field_name']))->fetchField(); - $data = unserialize($data); - $data['settings'] = array(); - db_update('field_config') - ->fields(array('data' => serialize($data))) - ->condition('field_name', $field_definition['field_name']) - ->execute(); - - field_cache_clear(); + \Drupal::config('field.field.' . $field->id()) + ->set('settings', array()) + ->save(); + field_info_cache_clear(); // Read the field back. $field = field_info_field($field_definition['field_name']); // Check that all expected settings are in place. $field_type = field_info_field_types($field_definition['type']); - $this->assertIdentical($field['settings'], $field_type['settings'], 'All expected default field settings are present.'); + $this->assertEqual($field['settings'], $field_type['settings'], 'All expected default field settings are present.'); } /** @@ -168,30 +163,24 @@ function testInstancePrepare() { 'entity_type' => 'test_entity', 'bundle' => 'test_bundle', ); - field_create_instance($instance_definition); + $instance = field_create_instance($instance_definition); // Simulate a stored instance definition missing various settings (e.g. a // third-party module adding instance or widget settings has been enabled, // but existing instances do not know the new settings). - $data = db_query('SELECT data FROM {field_config_instance} WHERE field_name = :field_name AND bundle = :bundle', array(':field_name' => $instance_definition['field_name'], ':bundle' => $instance_definition['bundle']))->fetchField(); - $data = unserialize($data); - $data['settings'] = array(); - $data['widget']['settings'] = 'unavailable_widget'; - $data['widget']['settings'] = array(); - db_update('field_config_instance') - ->fields(array('data' => serialize($data))) - ->condition('field_name', $instance_definition['field_name']) - ->condition('bundle', $instance_definition['bundle']) - ->execute(); - - field_cache_clear(); + \Drupal::config('field.instance.' . $instance->id()) + ->set('settings', array()) + ->set('widget.type', 'unavailable_widget') + ->set('widget.settings', array()) + ->save(); + field_info_cache_clear(); // Read the instance back. $instance = field_info_instance($instance_definition['entity_type'], $instance_definition['field_name'], $instance_definition['bundle']); // Check that all expected instance settings are in place. $field_type = field_info_field_types($field_definition['type']); - $this->assertIdentical($instance['settings'], $field_type['instance_settings'] , 'All expected instance settings are present.'); + $this->assertEqual($instance['settings'], $field_type['instance_settings'] , 'All expected instance settings are present.'); // Check that the default widget is used and expected settings are in place. $widget = $instance->getWidget(); diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldInstanceCrudTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldInstanceCrudTest.php index ee30cf1..2455a31 100644 --- a/core/modules/field/lib/Drupal/field/Tests/FieldInstanceCrudTest.php +++ b/core/modules/field/lib/Drupal/field/Tests/FieldInstanceCrudTest.php @@ -8,6 +8,7 @@ namespace Drupal\field\Tests; use Drupal\field\FieldException; +use Drupal\field\Plugin\Core\Entity\FieldInstance; class FieldInstanceCrudTest extends FieldUnitTestBase { @@ -46,28 +47,25 @@ function setUp() { * Test the creation of a field instance. */ function testCreateFieldInstance() { - field_create_instance($this->instance_definition); + $instance = field_create_instance($this->instance_definition); - // Read the raw record from the {field_config_instance} table. - $result = db_query('SELECT * FROM {field_config_instance} WHERE field_name = :field_name AND bundle = :bundle', array(':field_name' => $this->instance_definition['field_name'], ':bundle' => $this->instance_definition['bundle'])); - $record = $result->fetchAssoc(); - $record['data'] = unserialize($record['data']); + // Read the configuration. Check against raw configuration data rather than + // the loaded ConfigEntity, to be sure we check that the defaults are + // applied on write. + $config = \Drupal::config('field.instance.' . $instance->id())->get(); $field_type = field_info_field_types($this->field['type']); $widget_type = field_info_widget_types($field_type['default_widget']); - // Check that the ID key is filled in. - $this->assertIdentical($record['id'], $this->instance_definition['id'], 'The instance id is filled in'); - // Check that default values are set. - $this->assertIdentical($record['data']['required'], FALSE, 'Required defaults to false.'); - $this->assertIdentical($record['data']['label'], $this->instance_definition['field_name'], 'Label defaults to field name.'); - $this->assertIdentical($record['data']['description'], '', 'Description defaults to empty string.'); - $this->assertIdentical($record['data']['widget']['type'], $field_type['default_widget'], 'Default widget has been written.'); + $this->assertEqual($config['required'], FALSE, 'Required defaults to false.'); + $this->assertIdentical($config['label'], $this->instance_definition['field_name'], 'Label defaults to field name.'); + $this->assertIdentical($config['description'], '', 'Description defaults to empty string.'); + $this->assertIdentical($config['widget']['type'], $field_type['default_widget'], 'Default widget has been written.'); // Check that default settings are set. - $this->assertIdentical($record['data']['settings'], $field_type['instance_settings'] , 'Default instance settings have been written.'); - $this->assertIdentical($record['data']['widget']['settings'], $widget_type['settings'] , 'Default widget settings have been written.'); + $this->assertEqual($config['settings'], $field_type['instance_settings'] , 'Default instance settings have been written.'); + $this->assertIdentical($config['widget']['settings'], $widget_type['settings'] , 'Default widget settings have been written.'); // Guarantee that the field/bundle combination is unique. try { @@ -132,7 +130,9 @@ function testReadFieldInstance() { // Read the instance back. $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); - $this->assertTrue($this->instance_definition == $instance, 'The field was properly read.'); + $this->assertTrue($this->instance_definition['field_name'] == $instance['field_name'], 'The field was properly read.'); + $this->assertTrue($this->instance_definition['entity_type'] == $instance['entity_type'], 'The field was properly read.'); + $this->assertTrue($this->instance_definition['bundle'] == $instance['bundle'], 'The field was properly read.'); } /** @@ -206,7 +206,9 @@ function testDeleteFieldInstance() { // Make sure the field is deleted when its last instance is deleted. field_delete_instance($another_instance); - $field = field_read_field($another_instance['field_name'], array('include_deleted' => TRUE)); - $this->assertTrue(!empty($field['deleted']), 'A deleted field is marked for deletion after all its instances have been marked for deletion.'); + $deleted_fields = \Drupal::state()->get('field.field.deleted'); + $this->assertTrue(isset($deleted_fields[$another_instance['field_id']]), 'A deleted field is marked for deletion.'); + $field = field_read_field($another_instance['field_name']); + $this->assertFalse($field, 'The field marked to be deleted is not found anymore in the configuration.'); } } diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldUnitTestBase.php b/core/modules/field/lib/Drupal/field/Tests/FieldUnitTestBase.php index 94b068d..3e1bf38 100644 --- a/core/modules/field/lib/Drupal/field/Tests/FieldUnitTestBase.php +++ b/core/modules/field/lib/Drupal/field/Tests/FieldUnitTestBase.php @@ -35,8 +35,7 @@ */ function setUp() { parent::setUp(); - $this->installSchema('system', array('sequences', 'variable')); - $this->installSchema('field', array('field_config', 'field_config_instance')); + $this->installSchema('system', array('sequences', 'variable', 'config_snapshot')); $this->installSchema('entity_test', 'entity_test'); $this->installSchema('field_test', array('test_entity', 'test_entity_revision', 'test_entity_bundle')); @@ -60,7 +59,7 @@ function createFieldWithInstance($suffix = '') { $this->$field_name = drupal_strtolower($this->randomName() . '_field_name' . $suffix); $this->$field = array('field_name' => $this->$field_name, 'type' => 'test_field', 'cardinality' => 4); $this->$field = field_create_field($this->$field); - $this->$field_id = $this->{$field}['id']; + $this->$field_id = $this->{$field}['uuid']; $this->$instance = array( 'field_name' => $this->$field_name, 'entity_type' => 'test_entity', diff --git a/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php b/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php index 609d70f..83be37d 100644 --- a/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php +++ b/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php @@ -43,21 +43,21 @@ function setUp() { $this->entity_type = 'test_entity'; - $field = array( + $this->field_definition = array( 'field_name' => $this->field_name, 'type' => 'test_field', 'cardinality' => 4, 'translatable' => TRUE, ); - field_create_field($field); + field_create_field($this->field_definition); $this->field = field_read_field($this->field_name); - $instance = array( + $this->instance_definition = array( 'field_name' => $this->field_name, 'entity_type' => $this->entity_type, 'bundle' => 'test_bundle', ); - field_create_instance($instance); + field_create_instance($this->instance_definition); $this->instance = field_read_instance('test_entity', $this->field_name, 'test_bundle'); for ($i = 0; $i < 3; ++$i) { @@ -75,7 +75,7 @@ function setUp() { function testFieldAvailableLanguages() { // Test 'translatable' fieldable info. field_test_entity_info_translatable('test_entity', FALSE); - $field = $this->field; + $field = clone($this->field); $field['field_name'] .= '_untranslatable'; // Enable field translations for the entity. @@ -249,14 +249,15 @@ function testTranslatableFieldSaveLoad() { // Test default values. $field_name_default = drupal_strtolower($this->randomName() . '_field_name'); - $field = $this->field; - $field['field_name'] = $field_name_default; - $instance = $this->instance; - $instance['field_name'] = $field_name_default; - $default = rand(1, 127); - $instance['default_value'] = array(array('value' => $default)); - field_create_field($field); - field_create_instance($instance); + $field_definition = $this->field_definition; + $field_definition['field_name'] = $field_name_default; + $field = field_create_field($field_definition); + + $instance_definition = $this->instance_definition; + $instance_definition['field_name'] = $field_name_default; + $instance_definition['default_value'] = array(array('value' => rand(1, 127))); + $instance = field_create_instance($instance_definition); + $translation_langcodes = array_slice($available_langcodes, 0, 2); asort($translation_langcodes); $translation_langcodes = array_values($translation_langcodes); diff --git a/core/modules/field/tests/modules/field_test/field_test.storage.inc b/core/modules/field/tests/modules/field_test/field_test.storage.inc index 3d7a0f1..7705392 100644 --- a/core/modules/field/tests/modules/field_test/field_test.storage.inc +++ b/core/modules/field/tests/modules/field_test/field_test.storage.inc @@ -89,7 +89,7 @@ function field_test_field_storage_load($entity_type, $entities, $age, $fields, $ foreach ($fields as $field_id => $ids) { $field = field_info_field_by_id($field_id); $field_name = $field['field_name']; - $field_data = $data[$field['id']]; + $field_data = $data[$field['uuid']]; $sub_table = $load_current ? 'current' : 'revisions'; $delta_count = array(); foreach ($field_data[$sub_table] as $row) { @@ -206,7 +206,7 @@ function field_test_field_storage_delete(EntityInterface $entity, $fields) { function field_test_field_storage_purge(EntityInterface $entity, $field, $instance) { $data = _field_test_storage_data(); - $field_data = &$data[$field['id']]; + $field_data = &$data[$field['uuid']]; foreach (array('current', 'revisions') as $sub_table) { foreach ($field_data[$sub_table] as $key => $row) { if ($row->type == $entity->entityType() && $row->entity_id == $entity->id()) { @@ -249,7 +249,7 @@ function field_test_field_storage_query($field_id, $conditions, $count, &$cursor $field = field_info_field_by_id($field_id); $field_columns = array_keys($field['columns']); - $field_data = $data[$field['id']]; + $field_data = $data[$field['uuid']]; $sub_table = $load_current ? 'current' : 'revisions'; // We need to sort records by entity type and entity id. usort($field_data[$sub_table], '_field_test_field_storage_query_sort_helper'); @@ -267,7 +267,7 @@ function field_test_field_storage_query($field_id, $conditions, $count, &$cursor break; } - if ($row->field_id == $field['id']) { + if ($row->field_id == $field['uuid']) { $match = TRUE; $condition_deleted = FALSE; // Add conditions. @@ -372,7 +372,7 @@ function field_test_field_storage_create_field($field) { $data = _field_test_storage_data(); - $data[$field['id']] = array( + $data[$field['uuid']] = array( 'current' => array(), 'revisions' => array(), ); @@ -386,7 +386,7 @@ function field_test_field_storage_create_field($field) { function field_test_field_storage_delete_field($field) { $data = _field_test_storage_data(); - $field_data = &$data[$field['id']]; + $field_data = &$data[$field['uuid']]; foreach (array('current', 'revisions') as $sub_table) { foreach ($field_data[$sub_table] as &$row) { $row->deleted = TRUE; @@ -403,7 +403,7 @@ function field_test_field_storage_delete_instance($instance) { $data = _field_test_storage_data(); $field = field_info_field($instance['field_name']); - $field_data = &$data[$field['id']]; + $field_data = &$data[$field['uuid']]; foreach (array('current', 'revisions') as $sub_table) { foreach ($field_data[$sub_table] as &$row) { if ($row->bundle == $instance['bundle']) { @@ -425,8 +425,8 @@ function field_test_entity_bundle_rename($entity_type, $bundle_old, $bundle_new) $instances = field_read_instances(array('bundle' => $bundle_new), array('include_deleted' => TRUE, 'include_inactive' => TRUE)); foreach ($instances as $field_name => $instance) { $field = field_info_field_by_id($instance['field_id']); - if ($field['storage']['type'] == 'field_test_storage') { - $field_data = &$data[$field['id']]; + if ($field && $field['storage']['type'] == 'field_test_storage') { + $field_data = &$data[$field['uuid']]; foreach (array('current', 'revisions') as $sub_table) { foreach ($field_data[$sub_table] as &$row) { if ($row->bundle == $bundle_old) { @@ -448,7 +448,7 @@ function field_test_field_delete_instance($instance) { $field = field_info_field($instance['field_name']); if ($field['storage']['type'] == 'field_test_storage') { - $field_data = &$data[$field['id']]; + $field_data = &$data[$field['uuid']]; foreach (array('current', 'revisions') as $sub_table) { foreach ($field_data[$sub_table] as &$row) { if ($row->bundle == $instance['bundle']) { diff --git a/core/modules/field/tests/modules/field_test_config/config/field.field.field_test_import.yml b/core/modules/field/tests/modules/field_test_config/config/field.field.field_test_import.yml new file mode 100644 index 0000000..2f8963d --- /dev/null +++ b/core/modules/field/tests/modules/field_test_config/config/field.field.field_test_import.yml @@ -0,0 +1,20 @@ +id: field_test_import +uuid: fb38277f-1fd4-49d5-8d09-9d7037fdcce9 +langcode: und +type: text +settings: + max_length: '255' +module: text +active: 1 +entity_types: { } +storage: + type: field_sql_storage + settings: { } + module: field_sql_storage + active: 1 +locked: '0' +cardinality: '1' +translatable: false +indexes: + format: + - format diff --git a/core/modules/field/tests/modules/field_test_config/config/field.instance.test_entity.test_bundle.field_test_import.yml b/core/modules/field/tests/modules/field_test_config/config/field.instance.test_entity.test_bundle.field_test_import.yml new file mode 100644 index 0000000..3db6847 --- /dev/null +++ b/core/modules/field/tests/modules/field_test_config/config/field.instance.test_entity.test_bundle.field_test_import.yml @@ -0,0 +1,20 @@ +id: test_entity.test_bundle.field_test_import +uuid: 392b4e9d-6157-412e-9603-3d622512f498 +langcode: und +field_uuid: fb38277f-1fd4-49d5-8d09-9d7037fdcce9 +entity_type: test_entity +bundle: test_bundle +label: 'Test import field' +description: '' +required: 0 +default_value: { } +default_value_function: '' +settings: + text_processing: '0' + user_register_form: false +widget: + weight: '-2' + type: text_textfield + module: text + settings: + size: '60' diff --git a/core/modules/field/tests/modules/field_test_config/field_test_config.info.yml b/core/modules/field/tests/modules/field_test_config/field_test_config.info.yml new file mode 100644 index 0000000..57e0849 --- /dev/null +++ b/core/modules/field/tests/modules/field_test_config/field_test_config.info.yml @@ -0,0 +1,6 @@ +name: 'Field API configuration tests' +description: 'Support module for the Field API configuration tests.' +core: 8.x +package: Testing +version: VERSION +hidden: TRUE diff --git a/core/modules/field/tests/modules/field_test_config/field_test_config.module b/core/modules/field/tests/modules/field_test_config/field_test_config.module new file mode 100644 index 0000000..3d7730d --- /dev/null +++ b/core/modules/field/tests/modules/field_test_config/field_test_config.module @@ -0,0 +1,6 @@ + TRUE, 'include_inactive' => TRUE)); - drupal_load('module', 'field_sql_storage'); - foreach ($fields as $field) { - if ($field['storage']['type'] == 'field_sql_storage') { - $schema += _field_sql_storage_schema($field); - } + // Loading entities within hook_schema() triggers lots of race conditions. + // Read definitions for raw storage instead (configuration, and state for + // deleted fields). + $fields = array(); + foreach (config_get_storage_names_with_prefix('field.field') as $name) { + $fields[] = config($name)->get(); + } + $deleted_fields = Drupal::state()->get('field.field.deleted') ?: array(); + $fields = array_merge($fields, $deleted_fields); + + foreach ($fields as $field) { + if ($field['storage']['type'] == 'field_sql_storage') { + $field = new Field($field); + $schema += _field_sql_storage_schema($field); } } + return $schema; } @@ -79,31 +88,58 @@ function _update_8000_field_sql_storage_write($entity_type, $bundle, $entity_id, } /** - * Changes field language into langcode. + * Implements hook_update_dependencies(). + */ +function field_sql_storage_update_dependencies() { + // Convert storage tables after field definitions have moved to + // ConfigEntities. + $dependencies['field_sql_storage'][8000] = array( + 'field' => 8003, + ); + return $dependencies; +} + +/** + * Renames the 'language' column to 'langcode' in field data tables. */ function field_sql_storage_update_8000(&$sandbox) { - if (!isset($sandbox['progress'])) { - $sandbox['progress'] = 0; - $sandbox['last'] = 0; - $sandbox['max'] = db_query("SELECT COUNT(id) FROM {field_config} WHERE storage_type = 'field_sql_storage'")->fetchField(); + // Get field definitions from config, and deleted fields from state(). + $config_names = config_get_storage_names_with_prefix('field.field'); + $deleted_fields = Drupal::state()->get('field.field.deleted') ?: array(); + // Ditch UUID keys, we will iterate through deleted fields using a numeric + // index. + $deleted_fields = array_values($deleted_fields); + + if (empty($config_names) && empty($deleted_fields)) { + return; + } + + if (!isset($sandbox['index'])) { + $sandbox['index'] = 0; + $sandbox['max'] = count($config_names) + count($deleted_fields); } - // Retrieve field data. - $field = db_query("SELECT id, field_name, deleted FROM {field_config} WHERE id > :id AND storage_type = 'field_sql_storage'", array(':id' => $sandbox['last']))->fetchAssoc(); - if ($field) { + // Retrieve the next field definition. When the index exceeds the number of + // 'configuration' fields, use it to iterate on deleted fields. + if (isset($config_names[$sandbox['index']])) { + $field_config = config($config_names[$sandbox['index']])->get(); + } + else { + $field_config = $deleted_fields[$sandbox['index'] - count($config_names)]; + } - $sandbox['progress']++; - $sandbox['last'] = $field['id']; + if ($field_config['storage']['type'] == 'field_sql_storage') { + $field = new Field($field_config); // Prepare updated schema data structures. - $primary_key_data = array ( + $primary_key_data = array( 'entity_type', 'entity_id', 'deleted', 'delta', 'langcode', ); - $primary_key_revision = array ( + $primary_key_revision = array( 'entity_type', 'entity_id', 'revision_id', @@ -117,14 +153,14 @@ function field_sql_storage_update_8000(&$sandbox) { $field_langcode = array( 'type' => 'varchar', 'length' => 32, - 'not null' => true, + 'not null' => TRUE, 'default' => '', ); - $data_table = _field_sql_storage_tablename($field); - $revision_table = _field_sql_storage_revision_tablename($field); - $table_info = array($data_table => $primary_key_data, $revision_table => $primary_key_revision); - + $table_info = array( + _field_sql_storage_tablename($field) => $primary_key_data, + _field_sql_storage_revision_tablename($field) => $primary_key_revision, + ); foreach ($table_info as $table => $primary_key) { // Do not update tables which already have the langcode column, // created during the upgrade before this update function. @@ -138,5 +174,6 @@ function field_sql_storage_update_8000(&$sandbox) { } } - $sandbox['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['progress'] / $sandbox['max']); + $sandbox['index']++; + $sandbox['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['index'] / $sandbox['max']); } diff --git a/core/modules/field_sql_storage/field_sql_storage.module b/core/modules/field_sql_storage/field_sql_storage.module index 193df67..e8afd3c 100644 --- a/core/modules/field_sql_storage/field_sql_storage.module +++ b/core/modules/field_sql_storage/field_sql_storage.module @@ -37,6 +37,11 @@ function field_sql_storage_field_storage_info() { /** * Generates a table name for a field data table. * + * When a field is a deleted, the table is renamed to + * {field_deleted_data_FIELD_UUID}. To make sure we don't end up with table + * names longer than 64 characters, we hash the uuid and return the first 10 + * characters so we end up with a short unique ID. + * * @param $field * The field structure. * @@ -45,7 +50,7 @@ function field_sql_storage_field_storage_info() { */ function _field_sql_storage_tablename($field) { if ($field['deleted']) { - return "field_deleted_data_{$field['id']}"; + return "field_deleted_data_" . substr(hash('sha256', $field['uuid']), 0, 10); } else { return "field_data_{$field['field_name']}"; @@ -55,6 +60,11 @@ function _field_sql_storage_tablename($field) { /** * Generates a table name for a field revision archive table. * + * When a field is a deleted, the table is renamed to + * {field_deleted_revision_FIELD_UUID}. To make sure we don't end up with table + * names longer than 64 characters, we hash the uuid and return the first + * 10 characters so we end up with a short unique ID. + * * @param $name * The field structure. * @@ -63,7 +73,7 @@ function _field_sql_storage_tablename($field) { */ function _field_sql_storage_revision_tablename($field) { if ($field['deleted']) { - return "field_deleted_revision_{$field['id']}"; + return "field_deleted_revision_" . substr(hash('sha256', $field['uuid']), 0, 10); } else { return "field_revision_{$field['field_name']}"; @@ -178,15 +188,16 @@ function _field_sql_storage_schema($field) { ), ); - $field += array('columns' => array(), 'indexes' => array(), 'foreign keys' => array()); + $schema = $field->getSchema(); + // Add field columns. - foreach ($field['columns'] as $column_name => $attributes) { + foreach ($schema['columns'] as $column_name => $attributes) { $real_name = _field_sql_storage_columnname($field['field_name'], $column_name); $current['fields'][$real_name] = $attributes; } // Add indexes. - foreach ($field['indexes'] as $index_name => $columns) { + foreach ($schema['indexes'] as $index_name => $columns) { $real_name = _field_sql_storage_indexname($field['field_name'], $index_name); foreach ($columns as $column_name) { // Indexes can be specified as either a column name or an array with @@ -204,7 +215,7 @@ function _field_sql_storage_schema($field) { } // Add foreign keys. - foreach ($field['foreign keys'] as $specifier => $specification) { + foreach ($schema['foreign keys'] as $specifier => $specification) { $real_name = _field_sql_storage_indexname($field['field_name'], $specifier); $current['foreign keys'][$real_name]['table'] = $specification['table']; foreach ($specification['columns'] as $column => $referenced) { @@ -234,7 +245,18 @@ function field_sql_storage_field_storage_create_field($field) { foreach ($schema as $name => $table) { db_create_table($name, $table); } - drupal_get_schema(NULL, TRUE); + // Do not rebuild the schema right now, since the field definition has not + // been saved yet. This will be done in hook_field_create_field(). +} + +/** + * Implements hook_field_create_field(). + */ +function field_sql_storage_field_create_field($field) { + // Rebuild the schema now that the field has been saved. + if ($field['storage']['type'] == 'field_sql_storage') { + drupal_get_schema(NULL, TRUE); + } } /** @@ -294,8 +316,12 @@ function field_sql_storage_field_storage_update_field($field, $prior_field, $has // priors that exist unchanged. $table = _field_sql_storage_tablename($prior_field); $revision_table = _field_sql_storage_revision_tablename($prior_field); - foreach ($prior_field['indexes'] as $name => $columns) { - if (!isset($field['indexes'][$name]) || $columns != $field['indexes'][$name]) { + + $schema = $field->getSchema(); + $prior_schema = $prior_field->getSchema(); + + foreach ($prior_schema['indexes'] as $name => $columns) { + if (!isset($schema['indexes'][$name]) || $columns != $schema['indexes'][$name]) { $real_name = _field_sql_storage_indexname($field['field_name'], $name); db_drop_index($table, $real_name); db_drop_index($revision_table, $real_name); @@ -303,8 +329,8 @@ function field_sql_storage_field_storage_update_field($field, $prior_field, $has } $table = _field_sql_storage_tablename($field); $revision_table = _field_sql_storage_revision_tablename($field); - foreach ($field['indexes'] as $name => $columns) { - if (!isset($prior_field['indexes'][$name]) || $columns != $prior_field['indexes'][$name]) { + foreach ($schema['indexes'] as $name => $columns) { + if (!isset($prior_schema['indexes'][$name]) || $columns != $prior_schema['indexes'][$name]) { $real_name = _field_sql_storage_indexname($field['field_name'], $name); $real_columns = array(); foreach ($columns as $column_name) { diff --git a/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Tests/FieldSqlStorageTest.php b/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Tests/FieldSqlStorageTest.php index 5ea9c88..f31c63e 100644 --- a/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Tests/FieldSqlStorageTest.php +++ b/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Tests/FieldSqlStorageTest.php @@ -406,10 +406,10 @@ function testFieldStorageDetails() { $instance = field_info_instance($this->instance['entity_type'], $this->instance['field_name'], $this->instance['bundle']); // The storage details are indexed by a storage engine type. - $this->assertTrue(array_key_exists('sql', $field['storage']['details']), 'The storage type is SQL.'); + $this->assertTrue(array_key_exists('sql', $field['storage_details']), 'The storage type is SQL.'); // The SQL details are indexed by table name. - $details = $field['storage']['details']['sql']; + $details = $field['storage_details']['sql']; $this->assertTrue(array_key_exists($current, $details[FIELD_LOAD_CURRENT]), 'Table name is available in the instance array.'); $this->assertTrue(array_key_exists($revision, $details[FIELD_LOAD_REVISION]), 'Revision table name is available in the instance array.'); diff --git a/core/modules/field_ui/field_ui.admin.inc b/core/modules/field_ui/field_ui.admin.inc index 566959b..35fc624 100644 --- a/core/modules/field_ui/field_ui.admin.inc +++ b/core/modules/field_ui/field_ui.admin.inc @@ -5,7 +5,7 @@ * Administrative interface for custom field type creation. */ -use Drupal\field\FieldInstance; +use Drupal\field\Plugin\Core\Entity\FieldInstance; use Drupal\field_ui\FieldOverview; use Drupal\field_ui\DisplayOverview; @@ -609,14 +609,15 @@ function field_ui_field_settings_form_submit($form, &$form_state) { // Merge incoming form values into the existing field. $field = field_info_field($field_values['field_name']); + foreach ($field_values as $key => $value) { + $field[$key] = $value; + } $entity_type = $form['#entity_type']; $bundle = $form['#bundle']; $instance = field_info_instance($entity_type, $field['field_name'], $bundle); // Update the field. - $field = array_merge($field, $field_values); - try { field_update_field($field); drupal_set_message(t('Updated field %label field settings.', array('%label' => $instance['label']))); @@ -859,14 +860,6 @@ function field_ui_field_edit_form($form, &$form_state, $instance) { '#type' => 'value', '#value' => $instance['widget']['type'], ); - $form['instance']['widget']['module'] = array( - '#type' => 'value', - '#value' => $widget_type['module'], - ); - $form['instance']['widget']['active'] = array( - '#type' => 'value', - '#value' => !empty($field['instance']['widget']['active']) ? 1 : 0, - ); // Add additional field instance settings from the field module. $additions = module_invoke($field['module'], 'field_instance_settings_form', $field, $instance, $form_state); @@ -936,7 +929,10 @@ function field_ui_default_value_widget($field, $instance, &$form, &$form_state) // Insert the widget. Since we do not use the "official" instance definition, // the whole flow cannot use field_invoke_method(). - $items = (array) $instance['default_value']; + $items = array(); + if (!empty($instance['default_value'])) { + $items = (array) $instance['default_value']; + } $element += $instance->getWidget()->form($entity, LANGUAGE_NOT_SPECIFIED, $items, $element, $form_state); return $element; diff --git a/core/modules/file/file.install b/core/modules/file/file.install index 27a2438..36ce089 100644 --- a/core/modules/file/file.install +++ b/core/modules/file/file.install @@ -125,8 +125,8 @@ function file_schema() { ), 'id' => array( 'description' => 'The primary key of the object using the file.', - 'type' => 'int', - 'unsigned' => TRUE, + 'type' => 'varchar', + 'length' => 64, 'not null' => TRUE, 'default' => 0, ), @@ -245,3 +245,17 @@ function file_update_8000() { 'file_icon_directory'=>'icon.directory', )); } + +/** + * Convert the 'id' column in {file_usage} to accept UUIDs. + */ +function file_update_8001() { + $spec = array( + 'description' => 'The primary key of the object using the file.', + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + 'default' => '', + ); + db_change_field('file_usage', 'id', 'id', $spec); +} diff --git a/core/modules/file/lib/Drupal/file/Tests/FileFieldWidgetTest.php b/core/modules/file/lib/Drupal/file/Tests/FileFieldWidgetTest.php index 05b0cad..352ce4b 100644 --- a/core/modules/file/lib/Drupal/file/Tests/FileFieldWidgetTest.php +++ b/core/modules/file/lib/Drupal/file/Tests/FileFieldWidgetTest.php @@ -88,8 +88,14 @@ function testSingleValuedWidget() { */ function testMultiValuedWidget() { $type_name = 'article'; - $field_name = strtolower($this->randomName()); - $field_name2 = strtolower($this->randomName()); + // Use explicit names instead of random names for those fields, because of a + // bug in drupalPost() with multiple file uploads in one form, where the + // order of uploads depends on the order in which the upload elements are + // added to the $form (which, in the current implementation of + // FileStorage::listAll(), comes down to the alphabetical order on field + // names). + $field_name = 'test_file_field_1'; + $field_name2 = 'test_file_field_2'; $this->createFileField($field_name, $type_name, array('cardinality' => 3)); $this->createFileField($field_name2, $type_name, array('cardinality' => 3)); @@ -261,6 +267,9 @@ function testPrivateFileComment() { $this->drupalPost(NULL, $edit, t('Save field settings')); $this->drupalPost(NULL, array(), t('Save settings')); + // Manually clear cache on the tester side. + field_info_cache_clear(); + // Create node. $text_file = $this->getTestFile('text'); $edit = array( diff --git a/core/modules/file/lib/Drupal/file/Tests/FileItemTest.php b/core/modules/file/lib/Drupal/file/Tests/FileItemTest.php index 1e6eef5..2ec9dfa 100644 --- a/core/modules/file/lib/Drupal/file/Tests/FileItemTest.php +++ b/core/modules/file/lib/Drupal/file/Tests/FileItemTest.php @@ -54,9 +54,6 @@ public function setUp() { 'entity_type' => 'entity_test', 'field_name' => 'file_test', 'bundle' => 'entity_test', - 'widget' => array( - 'type' => 'options_select', - ), ); field_create_instance($instance); file_put_contents('public://example.txt', $this->randomName()); diff --git a/core/modules/forum/forum.install b/core/modules/forum/forum.install index b1e63de..1bc6915 100644 --- a/core/modules/forum/forum.install +++ b/core/modules/forum/forum.install @@ -22,11 +22,6 @@ function forum_install() { * Implements hook_enable(). */ function forum_enable() { - // If we enable forum at the same time as taxonomy we need to call - // field_associate_fields() as otherwise the field won't be enabled until - // hook modules_enabled is called which takes place after hook_enable events. - field_associate_fields('taxonomy'); - // Create the forum vocabulary if it does not exist. // @todo Change Forum module so forum.settings can contain the vocabulary's // machine name. @@ -54,8 +49,10 @@ function forum_enable() { $config->set('vocabulary', $vocabulary->id())->save(); } - // Create the 'taxonomy_forums' field if it doesn't already exist. - if (!field_info_field('taxonomy_forums')) { + // Create the 'taxonomy_forums' field if it doesn't already exist. If forum + // is being enabled at the same time as taxonomy after both modules have been + // enabled, the field might exist but still be marked inactive. + if (!field_read_field('taxonomy_forums', array('include_inactive' => TRUE))) { $field = array( 'field_name' => 'taxonomy_forums', 'type' => 'taxonomy_term_reference', diff --git a/core/modules/hal/lib/Drupal/hal/Tests/NormalizerTestBase.php b/core/modules/hal/lib/Drupal/hal/Tests/NormalizerTestBase.php index cbab209..caecabf 100644 --- a/core/modules/hal/lib/Drupal/hal/Tests/NormalizerTestBase.php +++ b/core/modules/hal/lib/Drupal/hal/Tests/NormalizerTestBase.php @@ -59,7 +59,6 @@ function setUp() { parent::setUp(); $this->installSchema('system', array('variable', 'url_alias')); - $this->installSchema('field', array('field_config', 'field_config_instance')); $this->installSchema('user', array('users')); $this->installSchema('language', array('language')); $this->installSchema('entity_test', array('entity_test')); diff --git a/core/modules/image/image.module b/core/modules/image/image.module index b963a45..017ce42 100644 --- a/core/modules/image/image.module +++ b/core/modules/image/image.module @@ -383,12 +383,12 @@ function image_field_update_field($field, $prior_field, $has_data) { if ($file_new) { $file_new->status = FILE_STATUS_PERMANENT; $file_new->save(); - file_usage()->add($file_new, 'image', 'default_image', $field['id']); + file_usage()->add($file_new, 'image', 'default_image', $field['uuid']); } // Is there an old file? if ($fid_old && ($file_old = file_load($fid_old))) { - file_usage()->delete($file_old, 'image', 'default_image', $field['id']); + file_usage()->delete($file_old, 'image', 'default_image', $field['uuid']); } } @@ -451,11 +451,11 @@ function image_field_update_instance($instance, $prior_instance) { if ($file_new) { $file_new->status = FILE_STATUS_PERMANENT; $file_new->save(); - file_usage()->add($file_new, 'image', 'default_image', $instance['id']); + file_usage()->add($file_new, 'image', 'default_image', $instance['uuid']); } // Delete the old file, if present. if ($fid_old && ($file_old = file_load($fid_old))) { - file_usage()->delete($file_old, 'image', 'default_image', $instance['id']); + file_usage()->delete($file_old, 'image', 'default_image', $instance['uuid']); } } diff --git a/core/modules/node/lib/Drupal/node/Tests/Condition/NodeConditionTest.php b/core/modules/node/lib/Drupal/node/Tests/Condition/NodeConditionTest.php index b186321..c33134b 100644 --- a/core/modules/node/lib/Drupal/node/Tests/Condition/NodeConditionTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/Condition/NodeConditionTest.php @@ -29,8 +29,6 @@ protected function setUp() { $this->installSchema('node', 'node_type'); $this->installSchema('node', 'node'); $this->installSchema('node', 'node_revision'); - $this->installSchema('field', 'field_config'); - $this->installSchema('field', 'field_config_instance'); } /** diff --git a/core/modules/number/lib/Drupal/number/Tests/NumberFieldTest.php b/core/modules/number/lib/Drupal/number/Tests/NumberFieldTest.php index 925828c..aa6c06e 100644 --- a/core/modules/number/lib/Drupal/number/Tests/NumberFieldTest.php +++ b/core/modules/number/lib/Drupal/number/Tests/NumberFieldTest.php @@ -63,11 +63,6 @@ function testNumberDecimalField() { 'placeholder' => '0.00' ), ), - 'display' => array( - 'default' => array( - 'type' => 'number_decimal', - ), - ), ); field_create_instance($this->instance); entity_get_display('test_entity', 'test_bundle', 'default') diff --git a/core/modules/options/lib/Drupal/options/Tests/OptionsFieldTest.php b/core/modules/options/lib/Drupal/options/Tests/OptionsFieldTest.php index ca9d605..11ab663 100644 --- a/core/modules/options/lib/Drupal/options/Tests/OptionsFieldTest.php +++ b/core/modules/options/lib/Drupal/options/Tests/OptionsFieldTest.php @@ -36,7 +36,7 @@ function setUp() { $this->field_name = 'test_options'; - $this->field = array( + $this->field_definition = array( 'field_name' => $this->field_name, 'type' => 'list_integer', 'cardinality' => 1, @@ -44,7 +44,7 @@ function setUp() { 'allowed_values' => array(1 => 'One', 2 => 'Two', 3 => 'Three'), ), ); - $this->field = field_create_field($this->field); + $this->field = field_create_field($this->field_definition); $this->instance = array( 'field_name' => $this->field_name, @@ -109,8 +109,7 @@ function testUpdateAllowedValues() { // Options are reset when a new field with the same name is created. field_delete_field($this->field_name); unset($this->field['id']); - $this->field['settings']['allowed_values'] = array(1 => 'One', 2 => 'Two', 3 => 'Three'); - $this->field = field_create_field($this->field); + field_create_field($this->field_definition); $this->instance = array( 'field_name' => $this->field_name, 'entity_type' => 'entity_test', @@ -119,7 +118,7 @@ function testUpdateAllowedValues() { 'type' => 'options_buttons', ), ); - $this->instance = field_create_instance($this->instance); + field_create_instance($this->instance); $entity = entity_create('entity_test', array()); $form = entity_get_form($entity); $this->assertTrue(!empty($form[$this->field_name][$langcode][1]), 'Option 1 exists'); diff --git a/core/modules/options/options.module b/core/modules/options/options.module index 793b07e..77d75a4 100644 --- a/core/modules/options/options.module +++ b/core/modules/options/options.module @@ -252,7 +252,7 @@ function options_field_update_field($field, $prior_field, $has_data) { function options_allowed_values($field, $instance = NULL, EntityInterface $entity = NULL) { $allowed_values = &drupal_static(__FUNCTION__, array()); - if (!isset($allowed_values[$field['id']])) { + if (!isset($allowed_values[$field['uuid']])) { $function = $field['settings']['allowed_values_function']; // If $cacheable is FALSE, then the allowed values are not statically // cached. See options_test_dynamic_values_callback() for an example of @@ -266,14 +266,14 @@ function options_allowed_values($field, $instance = NULL, EntityInterface $entit } if ($cacheable) { - $allowed_values[$field['id']] = $values; + $allowed_values[$field['uuid']] = $values; } else { return $values; } } - return $allowed_values[$field['id']]; + return $allowed_values[$field['uuid']]; } /** @@ -388,7 +388,7 @@ function options_field_update_forbid($field, $prior_field, $has_data) { */ function _options_values_in_use($field, $values) { if ($values) { - $field = field_info_field_by_id($field['id']); + $field = field_info_field_by_id($field['uuid']); $factory = drupal_container()->get('entity.query'); foreach ($field['bundles'] as $entity_type => $bundle) { $result = $factory->get($entity_type) @@ -477,6 +477,9 @@ function options_field_widget_info() { function options_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { // Abstract over the actual field columns, to allow different field types to // reuse those widgets. + + // Reset internal pointer since we're dealing with objects now. + reset($field['columns']); $value_key = key($field['columns']); $type = str_replace('options_', '', $instance['widget']['type']); diff --git a/core/modules/serialization/lib/Drupal/serialization/Tests/EntitySerializationTest.php b/core/modules/serialization/lib/Drupal/serialization/Tests/EntitySerializationTest.php index fb7019e..33802db 100644 --- a/core/modules/serialization/lib/Drupal/serialization/Tests/EntitySerializationTest.php +++ b/core/modules/serialization/lib/Drupal/serialization/Tests/EntitySerializationTest.php @@ -57,7 +57,6 @@ public static function getInfo() { protected function setUp() { parent::setUp(); - $this->installSchema('field', array('field_config', 'field_config_instance')); $this->installSchema('entity_test', array('entity_test_mulrev', 'entity_test_mulrev_property_revision', 'entity_test_mulrev_property_data')); // Auto-create a field for testing. diff --git a/core/modules/simpletest/lib/Drupal/simpletest/Tests/DrupalUnitTestBaseTest.php b/core/modules/simpletest/lib/Drupal/simpletest/Tests/DrupalUnitTestBaseTest.php index 3aeab60..c4efde8 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/Tests/DrupalUnitTestBaseTest.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/Tests/DrupalUnitTestBaseTest.php @@ -108,10 +108,7 @@ function testEnableModulesInstall() { */ function testEnableModulesInstallContainer() { // Install Node module. - // @todo field_sql_storage and field should technically not be necessary - // for an entity query. $this->enableModules(array('field_sql_storage', 'field', 'node')); - $this->installSchema('field', array('field_config', 'field_config_instance')); $this->installSchema('node', array('node_type', 'node')); // Perform an entity query against node. diff --git a/core/modules/system/lib/Drupal/system/Tests/Database/SelectComplexTest.php b/core/modules/system/lib/Drupal/system/Tests/Database/SelectComplexTest.php index c0f181a..a997a34 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Database/SelectComplexTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Database/SelectComplexTest.php @@ -27,11 +27,6 @@ public static function getInfo() { ); } - function setUp() { - parent::setUp(); - $this->installSchema('field', array('field_config', 'field_config_instance')); - } - /** * Tests simple JOIN statements. */ diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityUnitTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityUnitTestBase.php index b747e79..c3f236b 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityUnitTestBase.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityUnitTestBase.php @@ -25,7 +25,6 @@ public function setUp() { parent::setUp(); $this->installSchema('user', 'users'); $this->installSchema('system', 'sequences'); - $this->installSchema('field', array('field_config', 'field_config_instance')); $this->installSchema('entity_test', 'entity_test'); } diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/FieldAccessTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/FieldAccessTest.php index 09e7a7a..5e08eed 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/FieldAccessTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/FieldAccessTest.php @@ -41,8 +41,6 @@ public static function getInfo() { protected function setUp() { parent::setUp(); - // Install field module schema. - $this->installSchema('field', array('field_config', 'field_config_instance')); // The users table is needed for creating dummy user accounts. $this->installSchema('user', array('users')); // Register entity_test text field. diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/FieldUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/FieldUpgradePathTest.php index c0de21b..38c57d6 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/FieldUpgradePathTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/FieldUpgradePathTest.php @@ -87,4 +87,132 @@ public function testEntityDisplayUpgrade() { $this->assertEqual($displays['teaser']['content']['language'], $expected['teaser']); } + /** + * Tests migration of field and instance definitions to config. + */ + function testFieldUpgradeToConfig() { + $this->assertTrue($this->performUpgrade(), t('The upgrade was completed successfully.')); + + $field_manifest = config('manifest.field.field')->get(); + $instance_manifest = config('manifest.field.instance')->get(); + + // Check that the configuration for the 'body' field is correct. + $config = \Drupal::config('field.field.body')->get(); + // We cannot predict the value of the UUID, we just check it's present. + $this->assertFalse(empty($config['uuid'])); + $field_uuid = $config['uuid']; + unset($config['uuid']); + $this->assertEqual($config, array( + 'id' => 'body', + 'type' => 'text_with_summary', + 'module' => 'text', + 'active' => '1', + 'settings' => array(), + 'storage' => array( + 'type' => 'field_sql_storage', + 'module' => 'field_sql_storage', + 'active' => '1', + 'settings' => array(), + ), + 'locked' => 0, + 'cardinality' => 1, + 'translatable' => 0, + 'entity_types' => array('node'), + 'indexes' => array( + 'format' => array('format') + ), + 'status' => 1, + 'langcode' => 'und', + )); + // Check that an entry is present in the manifest. + $this->assertEqual($field_manifest['body']['name'], 'field.field.body'); + + // Check that the configuration for the instance on article and page nodes + // is correct. + foreach (array('article', 'page') as $node_type) { + $config = config("field.instance.node.$node_type.body")->get(); + // We cannot predict the value of the UUID, we just check it's present. + $this->assertFalse(empty($config['uuid'])); + unset($config['uuid']); + $this->assertEqual($config, array( + 'id' => "node.$node_type.body", + 'field_uuid' => $field_uuid, + 'entity_type' => 'node', + 'bundle' => $node_type, + 'label' => 'Body', + 'description' => '', + 'required' => FALSE, + 'default_value' => array(), + 'default_value_function' => '', + 'settings' => array( + 'display_summary' => TRUE, + 'text_processing' => 1, + 'user_register_form' => FALSE, + ), + 'widget' => array( + 'type' => 'text_textarea_with_summary', + 'module' => 'text', + 'settings' => array( + 'rows' => 20, + 'summary_rows' => 5, + ), + 'weight' => -4, + ), + 'status' => 1, + 'langcode' => 'und', + )); + // Check that an entry is present in the manifest. + $this->assertEqual($instance_manifest["node.$node_type.body"]['name'], "field.instance.node.$node_type.body"); + } + + // Check that field values in a pre-existing node are read correctly. + $body = node_load(1)->get('body'); + $this->assertEqual($body->value, 'Some value'); + $this->assertEqual($body->summary, 'Some summary'); + $this->assertEqual($body->format, 'filtered_html'); + + // Check that the definition of a deleted field is stored in state rather + // than config. + $this->assertFalse(\Drupal::config('field.field.test_deleted_field')->get()); + // The array is keyed by UUID. We cannot predict the UUID of the + // 'test_deleted_field' field, but assume there was only one deleted field + // in the test database. + $deleted_fields = \Drupal::state()->get('field.field.deleted'); + $uuid_key = key($deleted_fields); + $deleted_field = $deleted_fields[$uuid_key]; + $this->assertEqual($deleted_field['uuid'], $uuid_key); + $this->assertEqual($deleted_field['id'], 'test_deleted_field'); + + // Check that the definition of a deleted instance is stored in state rather + // than config. + $this->assertFalse(\Drupal::config('field.instance.node.article.test_deleted_field')->get()); + $deleted_instances = \Drupal::state()->get('field.instance.deleted'); + // Assume there was only one deleted instance in the test database. + $uuid_key = key($deleted_instances); + $deleted_instance = $deleted_instances[$uuid_key]; + $this->assertEqual($deleted_instance['uuid'], $uuid_key); + $this->assertEqual($deleted_instance['id'], 'node.article.test_deleted_field'); + // The deleted field uuid and deleted instance field_uuid must match. + $this->assertEqual($deleted_field['uuid'], $deleted_instance['field_uuid']); + + // Check that pre-existing deleted field values are read correctly. + $entity = _field_create_entity_from_ids((object) array( + 'entity_type' => 'node', + 'bundle' => 'article', + 'entity_id' => 2, + 'revision_id' => 2, + )); + field_attach_load('node', array(2 => $entity), FIELD_LOAD_CURRENT, array('field_id' => $deleted_field['uuid'], 'deleted' => 1)); + $deleted_value = $entity->get('test_deleted_field'); + $this->assertEqual($deleted_value[LANGUAGE_NOT_SPECIFIED][0]['value'], 'Some deleted value'); + + // Check that creation of a new node works as expected. + $value = $this->randomName(); + $edit = array( + 'title' => 'Node after CMI conversion', + 'body[und][0][value]' => $value, + ); + $this->drupalPost('node/add/article', $edit, 'Save and publish'); + $this->assertText($value); + } } diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/UserPictureUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/UserPictureUpgradePathTest.php index 04070c9..23eb3da 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/UserPictureUpgradePathTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/UserPictureUpgradePathTest.php @@ -51,7 +51,7 @@ public function testUserPictureUpgrade() { // Check file usage for the default image. $usage = file_usage()->listUsage($file); $field = field_info_field('user_picture'); - $this->assertEqual(1, $usage['image']['default_image'][$field['id']]); + $this->assertTrue(isset($usage['image']['default_image'][$field['uuid']])); $this->assertEqual($instance['settings']['max_resolution'], '800x800', 'User picture maximum resolution has been migrated.'); $this->assertEqual($instance['settings']['max_filesize'], '700 KB', 'User picture maximum filesize has been migrated.'); diff --git a/core/modules/system/tests/upgrade/drupal-7.field.database.php b/core/modules/system/tests/upgrade/drupal-7.field.database.php index 0862650..9434c7c 100644 --- a/core/modules/system/tests/upgrade/drupal-7.field.database.php +++ b/core/modules/system/tests/upgrade/drupal-7.field.database.php @@ -49,3 +49,338 @@ 'value' => serialize($value), )) ->execute(); + +// Add one node. +db_insert('node') + ->fields(array( + 'nid' => '1', + 'vid' => '1', + 'type' => 'article', + 'language' => 'und', + 'title' => 'node title 1 rev 1', + 'uid' => '1', + 'status' => '1', + 'created' => '1262754000', + 'changed' => '1338795201', + 'comment' => '0', + 'promote' => '1', + 'sticky' => '0', + 'tnid' => '0', + 'translate' => '0', + )) + ->execute(); +db_insert('node_revision') + ->fields(array( + 'nid' => '1', + 'vid' => '1', + 'uid' => '1', + 'title' => 'node title 1 rev 1', + 'log' => 'added 0 node', + 'timestamp' => '1338795201', + 'status' => '1', + 'comment' => '0', + 'promote' => '1', + 'sticky' => '0', + )) + ->execute(); + +$field_data_row = array( + 'entity_type' => 'node', + 'bundle' => 'article', + 'deleted' => '0', + 'entity_id' => '1', + 'revision_id' => '1', + 'language' => 'und', + 'delta' => '0', + 'body_value' => 'Some value', + 'body_summary' => 'Some summary', + 'body_format' => 'filtered_html', +); +db_insert('field_data_body') + ->fields($field_data_row) + ->execute(); +db_insert('field_revision_body') + ->fields($field_data_row) + ->execute(); + +// Add a deleted field and instance. +$field_id = db_insert('field_config') + ->fields(array( + 'field_name' => 'test_deleted_field', + 'type' => 'text', + 'module' => 'text', + 'active' => 1, + 'storage_type' => 'field_sql_storage', + 'storage_module' => 'field_sql_storage', + 'storage_active' => 1, + 'locked' => 0, + 'data' => serialize(array( + 'entity_types' => array(), + 'settings' => array( + 'max_length' => 255, + ), + 'storage' => array( + 'type' => 'field_sql_storage', + 'settings' => array(), + 'module' => 'field_sql_storage', + 'active' => 1, + ), + 'indexes' => array( + 'format' => array(0 => 'format') + ), + 'foreign keys' => array( + 'format' => array( + 'table' => 'filter_format', + 'columns' => array('format' => 'format') + ) + ) + )), + 'cardinality' => 1, + 'translatable' => 0, + 'deleted' => 1, + )) + ->execute(); +db_insert('field_config_instance') + ->fields(array( + 'field_id' => $field_id, + 'field_name' => 'test_deleted_field', + 'entity_type' => 'node', + 'bundle' => 'article', + 'data' => serialize(array( + 'label' => 'Long text', + 'description' => '', + 'required' => FALSE, + 'widget' => array( + 'type' => 'text_textarea', + 'weight' => 4, + 'module' => 'text', + 'active' => 1, + 'settings' => array( + 'rows' => 7 + ), + ), + 'settings' => array( + 'text_processing' => 0, + 'user_register_form' => FALSE, + ), + 'display' => array( + 'default' => array( + 'label' => 'above', + 'type' => 'text_default', + 'settings' => array(), + 'module' => 'text', + 'weight' => 10, + ), + ), + )), + 'deleted' => 1 + )) + ->execute(); + +// Add data tables for the deleted field. +db_create_table("field_deleted_data_{$field_id}", array( + 'fields' => array( + 'entity_type' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + ), + 'bundle' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + ), + 'deleted' => array( + 'type' => 'int', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), + 'entity_id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'revision_id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => FALSE, + ), + 'language' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + ), + 'delta' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'test_deleted_field_value' => array( + 'type' => 'text', + 'size' => 'big', + 'not null' => FALSE, + ), + 'test_deleted_field_format' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + ), + ), + 'primary key' => array( + 'entity_type', + 'entity_id', + 'deleted', + 'delta', + 'language', + ), + 'indexes' => array( + 'entity_type' => array( + 'entity_type', + ), + 'bundle' => array( + 'bundle', + ), + 'deleted' => array( + 'deleted', + ), + 'entity_id' => array( + 'entity_id', + ), + 'revision_id' => array( + 'revision_id', + ), + 'language' => array( + 'language', + ), + 'test_deleted_field_format' => array( + 'test_deleted_field_format', + ), + ), + 'foreign keys' => array( + 'test_deleted_field_format' => array( + 'table' => 'filter_format', + 'columns' => array( + 'test_deleted_field_format' => 'format', + ), + ), + ), + 'module' => 'field_sql_storage', + 'name' => "field_deleted_data_{$field_id}", +)); +db_create_table("field_deleted_revision_{$field_id}", array( + 'fields' => array( + 'entity_type' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + ), + 'bundle' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + ), + 'deleted' => array( + 'type' => 'int', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), + 'entity_id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'revision_id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'language' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + ), + 'delta' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'test_deleted_field_value' => array( + 'type' => 'text', + 'size' => 'big', + 'not null' => FALSE, + ), + 'test_deleted_field_format' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + ), + ), + 'primary key' => array( + 'entity_type', + 'entity_id', + 'revision_id', + 'deleted', + 'delta', + 'language', + ), + 'indexes' => array( + 'entity_type' => array( + 'entity_type', + ), + 'bundle' => array( + 'bundle', + ), + 'deleted' => array( + 'deleted', + ), + 'entity_id' => array( + 'entity_id', + ), + 'revision_id' => array( + 'revision_id', + ), + 'language' => array( + 'language', + ), + 'test_deleted_field_format' => array( + 'test_deleted_field_format', + ), + ), + 'foreign keys' => array( + 'test_deleted_field_format' => array( + 'table' => 'filter_format', + 'columns' => array( + 'test_deleted_field_format' => 'format', + ), + ), + ), + 'module' => 'field_sql_storage', + 'name' => "field_deleted_revision_{$field_id}", +)); + +// Add some deleted field data. +$field_data_row = array( + 'entity_type' => 'node', + 'bundle' => 'article', + 'deleted' => '0', + 'entity_id' => '2', + 'revision_id' => '2', + 'language' => 'und', + 'delta' => '0', + 'test_deleted_field_value' => 'Some deleted value', +); +db_insert("field_deleted_data_{$field_id}") + ->fields($field_data_row) + ->execute(); +db_insert("field_deleted_revision_{$field_id}") + ->fields($field_data_row) + ->execute(); + diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/VocabularyUnitTest.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/VocabularyUnitTest.php index d73302b..b25ba51 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/VocabularyUnitTest.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/VocabularyUnitTest.php @@ -175,15 +175,15 @@ function testUninstallReinstall() { // Fields and field instances attached to taxonomy term bundles should be // removed when the module is uninstalled. $this->field_name = drupal_strtolower($this->randomName() . '_field_name'); - $this->field = array('field_name' => $this->field_name, 'type' => 'text', 'cardinality' => 4); - $this->field = field_create_field($this->field); - $this->instance = array( + $this->field_definition = array('field_name' => $this->field_name, 'type' => 'text', 'cardinality' => 4); + field_create_field($this->field_definition); + $this->instance_definition = array( 'field_name' => $this->field_name, 'entity_type' => 'taxonomy_term', 'bundle' => $this->vocabulary->id(), 'label' => $this->randomName() . '_label', ); - field_create_instance($this->instance); + field_create_instance($this->instance_definition); module_disable(array('taxonomy')); require_once DRUPAL_ROOT . '/core/includes/install.inc'; @@ -196,8 +196,7 @@ function testUninstallReinstall() { // an instance of this field on the same bundle name should be successful. $this->vocabulary->enforceIsNew(); taxonomy_vocabulary_save($this->vocabulary); - unset($this->field['id']); - field_create_field($this->field); - field_create_instance($this->instance); + field_create_field($this->field_definition); + field_create_instance($this->instance_definition); } } diff --git a/core/modules/text/lib/Drupal/text/Tests/Formatter/TextPlainUnitTest.php b/core/modules/text/lib/Drupal/text/Tests/Formatter/TextPlainUnitTest.php index 716ead7..2000ffd 100644 --- a/core/modules/text/lib/Drupal/text/Tests/Formatter/TextPlainUnitTest.php +++ b/core/modules/text/lib/Drupal/text/Tests/Formatter/TextPlainUnitTest.php @@ -45,9 +45,6 @@ public static function getInfo() { function setUp() { parent::setUp(); - $this->installSchema('field', 'field_config'); - $this->installSchema('field', 'field_config_instance'); - // @todo Add helper methods for all of the following. $this->entity_type = 'test_entity'; diff --git a/core/modules/translation_entity/translation_entity.admin.inc b/core/modules/translation_entity/translation_entity.admin.inc index 2537c84..3ed3844 100644 --- a/core/modules/translation_entity/translation_entity.admin.inc +++ b/core/modules/translation_entity/translation_entity.admin.inc @@ -6,20 +6,21 @@ */ use Drupal\Core\Entity\EntityInterface; -use Drupal\field\FieldInstance; +use Drupal\field\Plugin\Core\Entity\Field; +use Drupal\field\Plugin\Core\Entity\FieldInstance; /** * Returns a form element to configure field synchronization. * - * @param array $field + * @param \Drupal\field\Plugin\Core\Entity\Field $field * A field definition array. - * @param \Drupal\Field\FieldInstance $instance + * @param \Drupal\field\Plugin\Core\Entity\FieldInstance $instance * A field instance definition object. * * @return array * A form element to configure field synchronization. */ -function translation_entity_field_sync_widget(array $field, FieldInstance $instance) { +function translation_entity_field_sync_widget(Field $field, FieldInstance $instance) { $element = array(); if (!empty($field['settings']['column_groups']) && count($field['settings']['column_groups']) > 1) { diff --git a/core/modules/user/user.install b/core/modules/user/user.install index c7fd16b..dca417c 100644 --- a/core/modules/user/user.install +++ b/core/modules/user/user.install @@ -315,10 +315,6 @@ function user_install_picture_field() { 'uri_scheme' => 'public', 'default_image' => FALSE, ), - 'storage' => array( - 'type' => 'field_sql_storage', - 'settings' => array(), - ), ); $field = field_create_field($field); diff --git a/core/modules/views/lib/Drupal/views/Tests/Plugin/RelationshipJoinTestBase.php b/core/modules/views/lib/Drupal/views/Tests/Plugin/RelationshipJoinTestBase.php index 5d2b524..172678d 100644 --- a/core/modules/views/lib/Drupal/views/Tests/Plugin/RelationshipJoinTestBase.php +++ b/core/modules/views/lib/Drupal/views/Tests/Plugin/RelationshipJoinTestBase.php @@ -27,7 +27,6 @@ */ protected function setUpFixtures() { $this->installSchema('user', array('users', 'users_roles', 'role_permission')); - $this->installSchema('field', array('field_config', 'field_config_instance')); $this->installConfig(array('user')); parent::setUpFixtures();