Issue #1735118 by swentel, yched, xjm, larowlan, alexpott, tim.plunkett: Convert Field API to CMI.

8.0.x
Alex Pott 2013-04-13 18:06:40 +01:00
parent 5a6647be01
commit a820153fa5
78 changed files with 2805 additions and 1382 deletions

View File

@ -12,7 +12,7 @@ use Drupal\field\Plugin\Type\Widget\WidgetBase;
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;

View File

@ -12,7 +12,7 @@ use Drupal\field\Plugin\Type\Widget\WidgetBase;
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;
/**

View File

@ -48,13 +48,12 @@ class DatetimeFieldTest extends WebTestBase {
$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 @@ class DatetimeFieldTest extends WebTestBase {
'settings' => array(
'default_value' => 'blank',
),
);
field_create_instance($this->instance);
));
$this->display_options = array(
'type' => 'datetime_default',
@ -304,6 +302,7 @@ class DatetimeFieldTest extends WebTestBase {
// 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.

View File

@ -9,7 +9,7 @@ namespace Drupal\edit;
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.

View File

@ -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).

View File

@ -9,7 +9,7 @@ namespace Drupal\edit;
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.

View File

@ -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.

View File

@ -9,7 +9,7 @@ namespace Drupal\edit;
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;

View File

@ -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.

View File

@ -9,7 +9,7 @@ namespace Drupal\edit\Plugin\edit\editor;
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.

View File

@ -9,7 +9,7 @@ namespace Drupal\edit\Plugin\edit\editor;
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.

View File

@ -28,7 +28,6 @@ class EditTestBase extends DrupalUnitTestBase {
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 @@ class EditTestBase extends DrupalUnitTestBase {
*/
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(

View File

@ -9,7 +9,7 @@ namespace Drupal\edit_test\Plugin\edit\editor;
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.

View File

@ -11,7 +11,7 @@ use Drupal\Component\Plugin\PluginBase;
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;
/**

View File

@ -24,12 +24,6 @@ class EntityDisplayTest extends DrupalUnitTestBase {
);
}
protected function setUp() {
parent::setUp();
$this->installSchema('field', array('field_config', 'field_config_instance'));
}
/**
* Tests basic CRUD operations on EntityDisplay objects.
*/

View File

@ -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());

View File

@ -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->allowBundleRename();
$instance->save();
}
}
// Clear the cache.
field_cache_clear();

View File

@ -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 @@ use Drupal\field\FieldException;
* 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 @@ use Drupal\field\FieldException;
* - 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.');
// 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;
}
// 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);
}
// 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');
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']);
// 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');
// 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');
}
// 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);
}
}
// Translate "do not include inactive instances" into actual conditions.
if (!$include_inactive) {
$conditions['active'] = 1;
$conditions['storage.active'] = 1;
}
// 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;
}
// Skip to the next field as soon as one condition does not match.
if ($checked_value != $value) {
continue 2;
}
$key = 'fci.' . $key;
}
else {
$key = 'fc.' . $key;
}
$query->condition($key, $value);
}
if (!isset($include_additional['include_inactive']) || !$include_additional['include_inactive']) {
$query
->condition('fc.active', 1)
->condition('fc.storage_active', 1);
}
$include_deleted = (isset($include_additional['include_deleted']) && $include_additional['include_deleted']);
if (!$include_deleted) {
$query->condition('fc.deleted', 0);
}
$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'];
module_invoke_all('field_read_field', $field);
// 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'];
$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'])));
// 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;
}
// 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();
}
// 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');
// Turn the conditions into a query.
foreach ($params as $key => $value) {
$query->condition('fci.' . $key, $value);
// 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']));
}
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);
else {
// No specific ID, we need to examine all existing instances.
$instances = entity_load_multiple('field_instance');
}
$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;
// 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);
}
}
return $instances;
// Translate "do not include inactive fields" into actual conditions.
if (!$include_inactive) {
$conditions['field.active'] = 1;
$conditions['field.storage.active'] = 1;
}
// 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 $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);

View File

@ -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;
/**

View File

@ -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

View File

@ -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)) {
@ -483,6 +349,137 @@ function field_update_8002() {
update_config_manifest_add('entity.display', array_keys($displays));
}
/**
* 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.

View File

@ -49,144 +49,6 @@ require_once DRUPAL_ROOT . '/core/modules/field/field.form.inc';
* '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;
@ -530,48 +393,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);
}
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();
}
$module_handler = Drupal::moduleHandler();
$state = Drupal::state();
/**
* 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();
// 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;
}
// 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();
$deleted_fields = $state->get('field.field.deleted') ?: array();
$fields += $deleted_fields;
if (empty($fields)) {
return;
}
// 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();
}
/**
@ -1011,7 +907,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::service('entity.query');
foreach ($field['bundles'] as $entity_type => $bundle) {

View File

@ -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,

View File

@ -142,17 +142,24 @@ class FieldInfo {
$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 @@ class FieldInfo {
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 @@ class FieldInfo {
// 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 @@ class FieldInfo {
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 @@ class FieldInfo {
$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 @@ class FieldInfo {
// 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 @@ class FieldInfo {
// 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 @@ class FieldInfo {
}
// 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']];
$this->fieldsById[$field['id']] = $field;
$this->fieldIdsByName[$field['field_name']] = $field['id'];
$instance = $this->prepareInstance($instance, $field['type']);
$instances[$field['field_name']] = $instance;
// 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 @@ class FieldInfo {
$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 @@ class FieldInfo {
return $result;
}
}

View File

@ -1,135 +0,0 @@
<?php
/**
* @file
* Definition of Drupal\field\FieldInstance.
*/
namespace Drupal\field;
/**
* Class for field instance objects.
*/
class FieldInstance implements \ArrayAccess {
/**
* The instance definition, as read from configuration storage.
*
* @var array
*/
public $definition;
/**
* The widget object used for this instance.
*
* @var Drupal\field\Plugin\Type\Widget\WidgetInterface
*/
protected $widget;
/**
* Constructs a FieldInstance object.
*
* @param array $definition
* The instance definition array, as read from configuration storage.
*/
public function __construct(array $definition) {
$this->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');
}
}

View File

@ -0,0 +1,36 @@
<?php
/**
* @file
* Contains \Drupal\field\FieldInstanceStorageController.
*/
namespace Drupal\field;
use Drupal\Core\Config\Config;
use Drupal\Core\Config\Entity\ConfigStorageController;
/**
* Controller class for field instances.
*
* Note: the class take no special care about importing instances after their
* field in importCreate(), since this is guaranteed by the alphabetical order
* (field.field.* entries are processed before field.instance.* entries).
* @todo Revisit after http://drupal.org/node/1944368.
*/
class FieldInstanceStorageController extends ConfigStorageController {
/**
* {@inheritdoc}
*/
public function importDelete($name, Config $new_config, Config $old_config) {
// If the field has been deleted in the same import, the instance will be
// deleted by then, and there is nothing left to do. Just return TRUE so
// that the file does not get written to active store.
if (!$old_config->get()) {
return TRUE;
}
return parent::importDelete($name, $new_config, $old_config);
}
}

View File

@ -0,0 +1,600 @@
<?php
/**
* @file
* Contains \Drupal\field\Plugin\Core\Entity\Field.
*/
namespace Drupal\field\Plugin\Core\Entity;
use Drupal\Component\Annotation\Plugin;
use Drupal\Core\Annotation\Translation;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\field\FieldException;
/**
* Defines the Field entity.
*
* @todo use 'field' as the id once hook_field_load() and friends
* are removed.
*
* @Plugin(
* id = "field_entity",
* label = @Translation("Field"),
* module = "field",
* controller_class = "Drupal\Core\Config\Entity\ConfigStorageController",
* config_prefix = "field.field",
* entity_keys = {
* "id" = "id",
* "label" = "id",
* "uuid" = "uuid"
* }
* )
*/
class Field extends ConfigEntityBase implements \ArrayAccess {
/**
* The maximum length of the field ID (machine name), in characters.
*
* For fields created through Field UI, this includes the 'field_' prefix.
*/
const ID_MAX_LENGTH = 32;
/**
* The field ID (machine name).
*
* This is the name of the property under which the field values are placed in
* an entity : $entity-{>$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;
/**
* {@inheritdoc}
*/
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'.
// @todo $field_name and the handling for it will be removed in
// http://drupal.org/node/1953408.
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);
}
/**
* {@inheritdoc}
*/
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;
}
/**
* {@inheritdoc}
*/
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;
// Make sure all settings are present, so that a complete field
// definition is passed to the various hooks and written to config.
$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(). The storage engine can reject the
// definition update as invalid by raising an exception, which stops
// execution before the definition is written to config.
$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;
}
/**
* {@inheritdoc}
*/
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();
}
/**
* {@inheritdoc}
*/
public function offsetExists($offset) {
return isset($this->{$offset}) || in_array($offset, array('columns', 'foreign keys', 'bundles', 'storage_details'));
}
/**
* {@inheritdoc}
*/
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};
}
/**
* {@inheritdoc}
*/
public function offsetSet($offset, $value) {
if (!in_array($offset, array('columns', 'foreign keys', 'bundles', 'storage_details'))) {
$this->{$offset} = $value;
}
}
/**
* {@inheritdoc}
*/
public function offsetUnset($offset) {
if (!in_array($offset, array('columns', 'foreign keys', 'bundles', 'storage_details'))) {
unset($this->{$offset});
}
}
}

View File

@ -0,0 +1,528 @@
<?php
/**
* @file
* Contains \Drupal\field\Plugin\Core\Entity\FieldInstance.
*/
namespace Drupal\field\Plugin\Core\Entity;
use Drupal\Component\Annotation\Plugin;
use Drupal\Core\Annotation\Translation;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\field\FieldException;
/**
* Defines the Field instance entity.
*
* @Plugin(
* id = "field_instance",
* label = @Translation("Field instance"),
* module = "field",
* controller_class = "Drupal\field\FieldInstanceStorageController",
* config_prefix = "field.instance",
* entity_keys = {
* "id" = "id",
* "label" = "label",
* "uuid" = "uuid"
* }
* )
*/
class FieldInstance extends ConfigEntityBase implements \ArrayAccess {
/**
* The instance ID (machine name).
*
* The ID consists of 3 parts: the entity type, bundle and the field name.
*
* Example: node.article.body, user.user.field_main_image.
*
* @var string
*/
public $id;
/**
* The instance UUID.
*
* This is assigned automatically when the instance is created.
*
* @var string
*/
public $uuid;
/**
* The UUID of the field attached to the bundle by this instance.
*
* @var string
*/
public $field_uuid;
/**
* The name of the field attached to the bundle by this instance.
*
* @var string
*
* @todo Revisit that in favor of a getField() method.
* See http://drupal.org/node/1967106.
* @todo This variable is provided for backward compatibility and will be
* removed.
*/
public $field_name;
/**
* The name of the entity type the instance is attached to.
*
* @var string
*/
public $entity_type;
/**
* The name of the bundle the instance is attached to.
*
* @var string
*/
public $bundle;
/**
* The human-readable label for the instance.
*
* This will be used as the title of Form API elements for the field in entity
* edit forms, or as the label for the field values in displayed entities.
*
* @var string
*/
public $label;
/**
* The instance description.
*
* 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 in entity edit forms.
*
* @var string
*/
public $description;
/**
* Field-type specific settings.
*
* An array of key/value pairs. The keys and default values are defined by the
* field type in the 'instance_settings' entry of hook_field_info().
*
* @var array
*/
public $settings;
/**
* Flag indicating whether the field is required.
*
* TRUE if a value for this field is required when used with this bundle,
* FALSE otherwise. Currently, required-ness is only enforced at the Form API
* level in entity edit forms, not during direct API saves.
*
* @var bool
*/
public $required;
/**
* Default field value.
*
* The default value is used when an entity is created, either:
* - through an entity creation form; the form elements for the field are
* prepopulated with the default value.
* - through direct API calls (i.e. $entity->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;
/**
* Flag indicating whether the bundle name can be renamed or not.
*
* @var bool
*/
protected $bundle_rename_allowed = FALSE;
/**
* {@inheritdoc}
*/
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.
// See http://drupal.org/node/1967106.
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);
}
/**
* {@inheritdoc}
*/
public function id() {
return $this->entity_type . '.' . $this->bundle . '.' . $this->field_name;
}
/**
* {@inheritdoc}
*/
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;
}
/**
* {@inheritdoc}
*/
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.");
}
if ($this->bundle != $original->bundle && empty($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;
}
/**
* Allows a bundle to be renamed.
*
* Renaming a bundle on the instance is allowed when an entity's bundle
* is renamed and when field_entity_bundle_rename() does internal
* housekeeping.
*/
public function allowBundleRename() {
$this->bundle_rename_allowed = TRUE;
}
/**
* {@inheritdoc}
*/
public function offsetExists($offset) {
return (isset($this->{$offset}) || $offset == 'field_id');
}
/**
* {@inheritdoc}
*/
public function &offsetGet($offset) {
if ($offset == 'field_id') {
return $this->field_uuid;
}
return $this->{$offset};
}
/**
* {@inheritdoc}
*/
public function offsetSet($offset, $value) {
if ($offset == 'field_id') {
$offset = 'field_uuid';
}
$this->{$offset} = $value;
}
/**
* {@inheritdoc}
*/
public function offsetUnset($offset) {
if ($offset == 'field_id') {
$offset = 'field_uuid';
}
unset($this->{$offset});
}
}

View File

@ -9,7 +9,7 @@ namespace Drupal\field\Plugin\Type\Formatter;
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.

View File

@ -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;
/**

View File

@ -13,7 +13,7 @@ use Drupal\Core\Plugin\Discovery\CacheDecorator;
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.

View File

@ -10,7 +10,7 @@ namespace Drupal\field\Plugin\Type\Widget;
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 @@ abstract class WidgetBase extends PluginSettingsBase implements WidgetInterface
* 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.

View File

@ -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.

View File

@ -166,8 +166,8 @@ class Field extends FieldPluginBase {
// 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;
@ -448,7 +448,6 @@ class Field extends FieldPluginBase {
),
// Set the other fields to their default values.
// @see _field_write_instance().
'required' => FALSE,
'label' => $field_name,
'description' => '',

View File

@ -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 @@ class BulkDeleteTest extends FieldUnitTestBase {
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 @@ class BulkDeleteTest extends FieldUnitTestBase {
$ids->entity_id = $entity_id;
$entities[$entity_id] = _field_create_entity_from_ids($ids);
}
field_attach_load($this->entity_type, $entities, FIELD_LOAD_CURRENT, array('field_id' => $field['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 @@ class BulkDeleteTest extends FieldUnitTestBase {
$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 @@ class BulkDeleteTest extends FieldUnitTestBase {
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 @@ class BulkDeleteTest extends FieldUnitTestBase {
$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.');
}
}

View File

@ -41,28 +41,29 @@ class CrudTest extends FieldUnitTestBase {
'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 @@ class CrudTest extends FieldUnitTestBase {
'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 @@ class CrudTest extends FieldUnitTestBase {
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 @@ class CrudTest extends FieldUnitTestBase {
$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 @@ class CrudTest extends FieldUnitTestBase {
'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 @@ class CrudTest extends FieldUnitTestBase {
);
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 @@ class CrudTest extends FieldUnitTestBase {
);
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 @@ class CrudTest extends FieldUnitTestBase {
);
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 @@ class CrudTest extends FieldUnitTestBase {
}
}
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 @@ class CrudTest extends FieldUnitTestBase {
// 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 @@ class CrudTest extends FieldUnitTestBase {
// 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 @@ class CrudTest extends FieldUnitTestBase {
$this->pass(t("An unchangeable setting cannot be updated."));
}
}
}

View File

@ -117,7 +117,7 @@ class FieldAttachStorageTest extends FieldUnitTestBase {
$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 @@ class FieldAttachStorageTest extends FieldUnitTestBase {
$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 @@ class FieldAttachStorageTest extends FieldUnitTestBase {
// 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]')));
}

View File

@ -0,0 +1,64 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\FieldImportChangeTest.
*/
namespace Drupal\field\Tests;
/**
* Tests updating fields and instances as part of config import.
*/
class FieldImportChangeTest extends FieldUnitTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('field_test_config');
public static function getInfo() {
return array(
'name' => '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');
}
}

View File

@ -0,0 +1,81 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\FieldImportCreateTest.
*/
namespace Drupal\field\Tests;
/**
* Tests creating fields and instances as part of config import.
*/
class FieldImportCreateTest extends FieldUnitTestBase {
public static function getInfo() {
return array(
'name' => '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');
}
}

View File

@ -0,0 +1,93 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\FieldImportDeleteTest.
*/
namespace Drupal\field\Tests;
/**
* Tests deleting fields and instances as part of config import.
*/
class FieldImportDeleteTest extends FieldUnitTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('field_test_config');
public static function getInfo() {
return array(
'name' => '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');
}
}

View File

@ -131,27 +131,22 @@ class FieldInfoTest extends FieldUnitTestBase {
'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 @@ class FieldInfoTest extends FieldUnitTestBase {
'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();

View File

@ -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 @@ class FieldInstanceCrudTest extends FieldUnitTestBase {
* 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 @@ class FieldInstanceCrudTest extends FieldUnitTestBase {
// 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 @@ class FieldInstanceCrudTest extends FieldUnitTestBase {
// 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.');
}
}

View File

@ -35,8 +35,7 @@ abstract class FieldUnitTestBase extends DrupalUnitTestBase {
*/
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 @@ abstract class FieldUnitTestBase extends DrupalUnitTestBase {
$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',

View File

@ -43,21 +43,21 @@ class TranslationTest extends FieldUnitTestBase {
$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 @@ class TranslationTest extends FieldUnitTestBase {
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 @@ class TranslationTest extends FieldUnitTestBase {
// 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);

View File

@ -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']) {

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -0,0 +1,6 @@
<?php
/**
* @file
* Helper module for the Field API configuration tests.
*/

View File

@ -0,0 +1,20 @@
id: field_test_import_staging
uuid: 0bf654cc-f14a-4881-b94c-76959e47466b
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: '0'
indexes:
format:
- format

View File

@ -0,0 +1,20 @@
id: test_entity.test_bundle.field_test_import_staging
uuid: ea711065-6940-47cd-813d-618f64095481
langcode: und
field_uuid: 0bf654cc-f14a-4881-b94c-76959e47466b
entity_type: test_entity
bundle: test_bundle
label: 'Import from staging'
description: ''
required: '0'
default_value: { }
default_value_function: ''
settings:
text_processing: '0'
user_register_form: '0'
widget:
type: text_textfield
weight: '-3'
settings:
size: '60'
module: text

View File

@ -5,22 +5,31 @@
* Install, update, and uninstall functions for the Field SQL Storage module.
*/
use Drupal\field\Plugin\Core\Entity\Field;
/**
* Implements hook_schema().
*/
function field_sql_storage_schema() {
$schema = array();
// Dynamic (data) tables.
if (db_table_exists('field_config')) {
$fields = field_read_fields(array(), array('include_deleted' => 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;
}
// 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) {
if (!isset($sandbox['index'])) {
$sandbox['index'] = 0;
$sandbox['max'] = count($config_names) + count($deleted_fields);
}
$sandbox['progress']++;
$sandbox['last'] = $field['id'];
// 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)];
}
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']);
}

View File

@ -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) {

View File

@ -406,10 +406,10 @@ class FieldSqlStorageTest extends EntityUnitTestBase {
$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.');

View File

@ -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'])));
@ -863,14 +864,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);
@ -953,7 +946,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;

View File

@ -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);
}

View File

@ -88,8 +88,14 @@ class FileFieldWidgetTest extends FileFieldTestBase {
*/
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 @@ class FileFieldWidgetTest extends FileFieldTestBase {
$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(

View File

@ -54,9 +54,6 @@ class FileItemTest extends FieldUnitTestBase {
'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());

View File

@ -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',

View File

@ -59,7 +59,6 @@ abstract class NormalizerTestBase extends DrupalUnitTestBase {
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'));

View File

@ -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']);
}
}

View File

@ -29,8 +29,6 @@ class NodeConditionTest extends DrupalUnitTestBase {
$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');
}
/**

View File

@ -63,11 +63,6 @@ class NumberFieldTest extends WebTestBase {
'placeholder' => '0.00'
),
),
'display' => array(
'default' => array(
'type' => 'number_decimal',
),
),
);
field_create_instance($this->instance);
entity_get_display('test_entity', 'test_bundle', 'default')

View File

@ -36,7 +36,7 @@ class OptionsFieldTest extends FieldUnitTestBase {
$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 @@ class OptionsFieldTest extends FieldUnitTestBase {
'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 @@ class OptionsFieldTest extends FieldUnitTestBase {
// 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 @@ class OptionsFieldTest extends FieldUnitTestBase {
'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');

View File

@ -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::service('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']);

View File

@ -57,7 +57,6 @@ class EntitySerializationTest extends DrupalUnitTestBase {
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.

View File

@ -108,10 +108,7 @@ class DrupalUnitTestBaseTest extends DrupalUnitTestBase {
*/
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.

View File

@ -27,11 +27,6 @@ class SelectComplexTest extends DatabaseTestBase {
);
}
function setUp() {
parent::setUp();
$this->installSchema('field', array('field_config', 'field_config_instance'));
}
/**
* Tests simple JOIN statements.
*/

View File

@ -25,7 +25,6 @@ abstract class EntityUnitTestBase extends DrupalUnitTestBase {
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');
}

View File

@ -41,8 +41,6 @@ class FieldAccessTest extends DrupalUnitTestBase {
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.

View File

@ -87,4 +87,132 @@ class FieldUpgradePathTest extends UpgradePathTestBase {
$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);
}
}

View File

@ -51,7 +51,7 @@ class UserPictureUpgradePathTest extends UpgradePathTestBase {
// 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.');

View File

@ -49,3 +49,338 @@ db_insert('variable')
'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();

View File

@ -175,15 +175,15 @@ class VocabularyUnitTest extends TaxonomyTestBase {
// 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 @@ class VocabularyUnitTest extends TaxonomyTestBase {
// 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);
}
}

View File

@ -45,9 +45,6 @@ class TextPlainUnitTest extends DrupalUnitTestBase {
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';

View File

@ -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) {

View File

@ -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);

View File

@ -27,7 +27,6 @@ abstract class RelationshipJoinTestBase extends PluginUnitTestBase {
*/
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();