diff --git a/core/modules/comment/comment.install b/core/modules/comment/comment.install index f47151f..a4e87ac 100644 --- a/core/modules/comment/comment.install +++ b/core/modules/comment/comment.install @@ -16,7 +16,7 @@ function comment_uninstall() { variable_del('comment_block_count'); $node_types = array_keys(node_type_get_types()); foreach ($node_types as $node_type) { - field_attach_delete_bundle('comment', 'comment_node_' . $node_type); + field_attach_delete_bundle('comment', 'comment_node_' . $node_type, FALSE); variable_del('comment_' . $node_type); variable_del('comment_anonymous_' . $node_type); variable_del('comment_controls_' . $node_type); diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentFieldsTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentFieldsTest.php index ab03d41..6fd47ab 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentFieldsTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentFieldsTest.php @@ -41,7 +41,7 @@ function testCommentDefaultFields() { $this->assertTrue(isset($instances['comment_node_' . $type_name]['comment_body']), format_string('The comment_body field is present for comments on type @type', array('@type' => $type_name))); // Delete the instance along the way. - field_delete_instance($instances['comment_node_' . $type_name]['comment_body']); + field_delete_instance($instances['comment_node_' . $type_name]['comment_body']->getArray()); } // Check that the 'comment_body' field is deleted. diff --git a/core/modules/field/field.attach.inc b/core/modules/field/field.attach.inc index a4e227c..c3fe5a1 100644 --- a/core/modules/field/field.attach.inc +++ b/core/modules/field/field.attach.inc @@ -1585,27 +1585,29 @@ function field_attach_create_bundle($entity_type, $bundle) { * The new name of the bundle. */ function field_attach_rename_bundle($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 $id => $instance) { + if ($instance['entity_type'] == $entity_type && $instance['bundle'] == $bundle_old) { + config('field.instance.' . $instance['entity_type'] . '.' . $bundle_old . '.' . $instance['field_name'])->rename('field.instance.' . $instance['entity_type'] . '.' . $bundle_new . '.' . $instance['field_name']); + config('field.instance.' . $instance['entity_type'] . '.' . $bundle_new . '.' . $instance['field_name'])->set('bundle', $bundle_new)->save(); + } + } // Clear the cache. field_cache_clear(); entity_info_cache_clear(); - // Update bundle settings. - $settings = variable_get('field_bundle_settings_' . $entity_type . '__' . $bundle_old, array()); - variable_set('field_bundle_settings_' . $entity_type . '__' . $bundle_new, $settings); - variable_del('field_bundle_settings_' . $entity_type . '__' . $bundle_old); + // Rename bundle settings. + if (config('field.settings.' . $entity_type . '.' . $bundle_old)->get()) { + config('field.settings.' . $entity_type . '.' . $bundle_old)->rename('field.settings.' . $entity_type . '.' . $bundle_new); + } // Let other modules act on renaming the bundle. module_invoke_all('field_attach_rename_bundle', $entity_type, $bundle_old, $bundle_new); } /** - * Notifies field.module the a bundle was deleted. + * Notifies field.module that a bundle was deleted. * * This deletes the data for the field instances as well as the field instances * themselves. This function actually just marks the data and field instances as @@ -1618,21 +1620,25 @@ function field_attach_rename_bundle($entity_type, $bundle_old, $bundle_new) { * The entity type to which the bundle is bound. * @param $bundle * The bundle to delete. + * @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_attach_delete_bundle($entity_type, $bundle) { +function field_attach_delete_bundle($entity_type, $bundle, $field_cleanup = TRUE) { // First, delete the instances themselves. field_read_instances() must be // used here since field_info_instances() does not return instances for // disabled entity types or bundles. $instances = field_read_instances(array('entity_type' => $entity_type, 'bundle' => $bundle), array('include_inactive' => 1)); foreach ($instances as $instance) { - field_delete_instance($instance); + field_delete_instance($instance, $field_cleanup); } // Clear the cache. field_cache_clear(); // Clear bundle display settings. - variable_del('field_bundle_settings_' . $entity_type . '__' . $bundle); + config('field.settings.' . $entity_type . '.' . $bundle)->delete(); // Let other modules act on deleting the bundle. module_invoke_all('field_attach_delete_bundle', $entity_type, $bundle, $instances); diff --git a/core/modules/field/field.crud.inc b/core/modules/field/field.crud.inc index b8e2015..fdf1faf 100644 --- a/core/modules/field/field.crud.inc +++ b/core/modules/field/field.crud.inc @@ -6,6 +6,7 @@ */ use Drupal\field\FieldException; +use Drupal\Field\FieldInstance; /** * @defgroup field_crud Field CRUD API @@ -148,52 +149,37 @@ function field_create_field($field) { ); $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'], + $field += array( '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']; + // Do not save the 'bundles' property populated by + // field_info_field(). + unset($field['bundles']); + + // Generate id for the field. + $field['id'] = field_generate_id(); + + // Save the configuration. + config('field.field.' . $field['field_name'])->setData($field)->save(); // 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. + // Invoke hook_field_storage_create_field. 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(); + config('field.field.' . $field['field_name'])->delete(); throw $e; } // Clear caches - field_cache_clear(TRUE); + field_cache_clear(); // Invoke external hooks after the cache is cleared for API consistency. module_invoke_all('field_create_field', $field); @@ -272,26 +258,15 @@ function field_update_field($field) { $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; + unset($field['bundles']); - // Store the field and create the id. - $primary_key = array('id'); - drupal_write_record('field_config', $field, $primary_key); + // Store the field. + config('field.field.' . $field['field_name'])->setData($field)->save(); // Clear caches - field_cache_clear(TRUE); + field_cache_clear(); // Invoke external hooks after the cache is cleared for API consistency. module_invoke_all('field_update_field', $field, $prior_field, $has_data); @@ -341,50 +316,42 @@ function field_read_field($field_name, $include_additional = array()) { * field ID, otherwise it is keyed by field name. */ function field_read_fields($params = array(), $include_additional = array()) { - $query = db_select('field_config', 'fc', array('fetch' => PDO::FETCH_ASSOC)); - $query->fields('fc'); - - // Turn the conditions into a query. - foreach ($params as $key => $value) { - // Allow filtering on the 'entity_type' and 'bundle' columns of the - // field_config_instance table. - if ($key == 'entity_type' || $key == 'bundle') { - if (empty($fci_join)) { - $fci_join = $query->join('field_config_instance', 'fci', 'fc.id = fci.field_id'); - } - $key = 'fci.' . $key; - } + $fields = array(); - $query->condition($key, $value); + // Check if we need to retrieve deleted fields. + $include_deleted = (isset($include_additional['include_deleted']) && $include_additional['include_deleted']) || (isset($params['deleted']) && $params['deleted']); + if ($include_deleted) { + $deleted_fields = state()->get('field.deleted') ?: array(); } + // Add active and storage active parameters. if (!isset($include_additional['include_inactive']) || !$include_additional['include_inactive']) { - $query - ->condition('fc.active', 1) - ->condition('fc.storage_active', 1); + $params['active'] = 1; + $params['storage_active'] = 1; } - $include_deleted = (isset($include_additional['include_deleted']) && $include_additional['include_deleted']); - if (!$include_deleted) { - $query->condition('fc.deleted', 0); + + // Get configuration fields. + $config_fields = config_get_storage_names_with_prefix('field.field'); + // Merge deleted fields. + if (!empty($deleted_fields)) { + $config_fields += $deleted_fields; } + foreach ($config_fields as $config) { + if (!is_array($config)) { + $field = config($config)->get(); + } + else { + $field = $config; + } - $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']; + // Conditions. + foreach ($params as $key => $value) { + if ($field[$key] != $value) { + continue 2; + } + } + // Invoke read field. module_invoke_all('field_read_field', $field); // Populate storage information. @@ -411,11 +378,12 @@ function field_read_fields($params = array(), $include_additional = array()) { 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); + field_delete_instance($instance->getArray(), FALSE); } } } @@ -423,14 +391,17 @@ function field_delete_field($field_name) { // 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(); + // 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.deleted') ?: array(); + $field['deleted'] = TRUE; + $deleted_fields[$field['id']] = $field; + state()->set('field.deleted', $deleted_fields); + config('field.field.' . $field['field_name'])->delete(); // Clear the cache. - field_cache_clear(TRUE); + field_cache_clear(); module_invoke_all('field_delete_field', $field); } @@ -490,6 +461,9 @@ function field_create_instance(&$instance) { // Set the field id. $instance['field_id'] = $field['id']; + // Generate id for the instance. + $instance['id'] = field_generate_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. @@ -554,7 +528,7 @@ function field_update_instance($instance) { $instance['id'] = $prior_instance['id']; $instance['field_id'] = $prior_instance['field_id']; - _field_write_instance($instance, TRUE); + _field_write_instance($instance); // Clear caches. field_cache_clear(); @@ -567,10 +541,8 @@ function field_update_instance($instance) { * * @param $instance * An instance structure. - * @param $update - * Whether this is a new or existing instance. */ -function _field_write_instance(&$instance, $update = FALSE) { +function _field_write_instance(&$instance) { $field = field_read_field($instance['field_name']); $field_type = field_info_field_types($field['type']); @@ -595,7 +567,6 @@ function _field_write_instance(&$instance, $update = FALSE) { // 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 ? @@ -636,30 +607,8 @@ function _field_write_instance(&$instance, $update = FALSE) { $instance['display'][$view_mode] = $display; } - // 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']; - } + // Save into config. + config('field.instance.' . $instance['entity_type'] . '.' . $instance['bundle'] . '.' . $instance['field_name'])->setData($instance)->save(); } /** @@ -694,8 +643,8 @@ function field_read_instance($entity_type, $field_name, $bundle, $include_additi * * @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. + * include any property of the instance config object, except for data. + * If NULL, all instances will be returned. * @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 @@ -706,44 +655,55 @@ function field_read_instance($entity_type, $field_name, $bundle, $include_additi * An array of instances matching the arguments. */ function field_read_instances($params = array(), $include_additional = array()) { + $instances = array(); + + $deleted_fields = state()->get('field.deleted'); $include_inactive = isset($include_additional['include_inactive']) && $include_additional['include_inactive']; $include_deleted = isset($include_additional['include_deleted']) && $include_additional['include_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'); + $config_instances = config_get_storage_names_with_prefix('field.instance'); + foreach ($config_instances as $config) { + $instance = config($config)->get(); + $entity_info = entity_get_info($instance['entity_type']); - // Turn the conditions into a query. - foreach ($params as $key => $value) { - $query->condition('fci.' . $key, $value); - } - 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); - } + // Get data from the field. If the field is marked as deleted, we + // need to get it from the state storage. + $field = config('field.field.' . $instance['field_name'])->get(); + if ($include_deleted) { + if (empty($field) && isset($deleted_fields[$instance['field_id']])) { + $field = $deleted_fields[$instance['field_id']]; + } + } + if (empty($field)) { + continue; + } - $instances = array(); - $results = $query->execute(); + $instance['active'] = $field['active']; + $instance['locked'] = $field['locked']; + $instance['type'] = $field['type']; + $instance['module'] = $field['module']; + $instance['storage_type'] = $field['storage_type']; + $instance['storage_active'] = $field['storage_active']; + $instance['storage_module'] = $field['storage_module']; - 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']; + // Conditions. + if (!$include_inactive) { + $params['active'] = 1; + $params['storage_active'] = 1; + } + if (!$include_deleted && !isset($params['deleted'])) { + $params['deleted'] = 0; + } + foreach ($params as $key => $value) { + if ($instance[$key] != $value) { + continue 2; + } + } + // Invoke read instance. module_invoke_all('field_read_instance', $instance); + $instances[] = $instance; } } @@ -761,13 +721,10 @@ function field_read_instances($params = array(), $include_additional = array()) * 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(); + $instance['deleted'] = TRUE; + config('field.instance.' . $instance['entity_type'] . '.' . $instance['bundle'] . '.' . $instance['field_name'])->setData($instance)->save(); // Clear the cache. field_cache_clear(); @@ -915,8 +872,8 @@ 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) { + $deleted_fields = state()->get('field.deleted') ?: array(); + foreach ($deleted_fields as $field) { $instances = field_read_instances(array('field_id' => $field['id']), array('include_deleted' => 1)); if (empty($instances)) { field_purge_field($field); @@ -966,9 +923,7 @@ function field_purge_data($entity_type, $entity, $field, $instance) { * The instance record to purge. */ function field_purge_instance($instance) { - db_delete('field_config_instance') - ->condition('id', $instance['id']) - ->execute(); + config('field.instance.' . $instance['entity_type'] . '.' . $instance['bundle'] . '.' . $instance['field_name'])->delete(); // Notify the storage engine. $field = field_info_field_by_id($instance['field_id']); @@ -996,9 +951,9 @@ function field_purge_field($field) { 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(); + $deleted_fields = state()->get('field.deleted'); + unset($deleted_fields[$field['id']]); + state()->set('field.deleted', $deleted_fields); // Notify the storage engine. module_invoke($field['storage']['module'], 'field_storage_purge_field', $field); diff --git a/core/modules/field/field.install b/core/modules/field/field.install index bc566d6..58162e7 100644 --- a/core/modules/field/field.install +++ b/core/modules/field/field.install @@ -9,158 +9,7 @@ * 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.'; @@ -271,7 +120,6 @@ function _update_7000_field_delete_field($field_name) { ->execute(); } - /** * Deletes an instance and all its data of a field stored in SQL Storage. * @@ -382,15 +230,107 @@ function _update_7000_field_create_instance($field, &$instance) { */ /** - * Reassign all list.module fields to be controlled by options.module. + * Convert Field API to CMI. */ function field_update_8001() { - db_update('field_config') - ->fields(array( - 'module' => 'options', - )) - ->condition('module', 'list') - ->execute(); + + // Changes field language into langcode. This used to be in field_sql_storage + // but needs to be here, because otherwhise _update_7000_field_read_fields() + // would fail. + + // Prepare updated schema data structures. + $primary_key_data = array ( + 'entity_type', + 'entity_id', + 'deleted', + 'delta', + 'langcode', + ); + $primary_key_revision = array ( + 'entity_type', + 'entity_id', + 'revision_id', + 'deleted', + 'delta', + 'langcode', + ); + $langcode_index = array( + 'langcode', + ); + $field_langcode = array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => true, + 'default' => '', + ); + + // Retrieve field data. + $fields = _update_7000_field_read_fields(array('storage_type' => 'field_sql_storage')); + + // Update schema. + foreach ($fields as $field) { + $data_table = _field_sql_storage_tablename($field); + if (db_table_exists($data_table)) { + $revision_table = _field_sql_storage_revision_tablename($field); + $table_info = array($data_table => $primary_key_data, $revision_table => $primary_key_revision); + + foreach ($table_info as $table => $primary_key) { + db_drop_primary_key($table); + db_drop_index($table, 'language'); + db_change_field($table, 'language', 'langcode', $field_langcode); + db_add_primary_key($table, $primary_key); + db_add_index($table, 'langcode', $langcode_index); + } + } + } + + // Convert to CMI. + $deleted_fields = state()->get('field.deleted') ?: array(); + + $field_ids = array(); + $fields = db_query("SELECT * FROM {field_config}"); + foreach ($fields as $field) { + // Generate id for the field. + $old_id = $field->id; + $field->id = field_generate_id(); + + $field->data = unserialize($field->data); + + // Reassign all list.module fields to be controlled by options.module. + if ($field->module == 'list') { + $field->module = 'options'; + } + + if (!$field->deleted) { + config('field.field.' . $field->field_name)->setData((array) $field)->save(); + $field_ids[$old_id] = array( + 'id' => $field->id, + ); + } + else { + $deleted_fields[$field->field_name] = (array) $field; + } + } + + $instances = db_query("SELECT * FROM {field_config_instance}"); + foreach ($instances as $instance) { + $instance->data = unserialize($instance->data); + + // Map old field id to new UUID. + $old_id = $instance->field_id; + $instance->field_id = $field_ids[$old_id]['id']; + + // Generate id for the instance. + $instance->id = field_generate_id(); + config('field.instance.' . $instance->entity_type . '.' . $instance->bundle . '.' . $instance->field_name)->setData((array) $instance)->save(); + } + + // Save the deleted fields. + state()->set('field.deleted', $deleted_fields); + + // Drop the tables. + db_drop_table('field_config'); + db_drop_table('field_config_instance'); } /** diff --git a/core/modules/field/field.module b/core/modules/field/field.module index ac0d1b4..5b19326 100644 --- a/core/modules/field/field.module +++ b/core/modules/field/field.module @@ -6,6 +6,7 @@ use Drupal\Core\Template\Attribute; use Drupal\Core\Entity\Query\QueryFactory; +use Drupal\Component\Uuid\Uuid; /* * Load all public Field API functions. Drupal currently has no @@ -327,6 +328,72 @@ function field_cron() { field_purge_batch($limit); } + /** + * Implements hook_config_import_create(). + */ +function field_config_import_create($name, $new_config, $old_config) { + + list($module, $type) = explode('.', $name); + if ($module != 'field') { + return; + } + + $config = $new_config->get(); + + switch ($type) { + case 'field': + field_create_field($config); + break; + case 'instance': + field_create_instance($config); + break; + } +} + +/** + * Implements hook_config_import_change(). + */ +function field_config_import_change($name, $new_config, $old_config) { + + list($module, $type) = explode('.', $name); + if ($module != 'field') { + return; + } + + $config = $new_config->get(); + + switch ($type) { + case 'field': + field_update_field($config); + break; + case 'instance': + field_update_instance($config); + break; + } +} + +/** + * Implements hook_config_import_delete(). + */ +function field_config_import_delete($name, $new_config, $old_config) { + + list($module, $type) = explode('.', $name); + if ($module != 'field') { + return; + } + + $config = $new_config->get(); + + switch ($type) { + case 'field': + field_delete_field($config['field_name']); + break; + case 'instance': + field_delete_instance($config); + break; + } +} + /** * Implements hook_system_info_alter(). * @@ -499,23 +566,43 @@ 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 + + $deleted_fields = state()->get('field.deleted') ?: array(); + $fields = field_read_fields(array(), array('include_deleted' => 1 ,'include_inactive' => 1)); + // Refresh the 'active' and 'storage_active' values according to the current // set of enabled modules. $modules = module_list(); foreach ($modules as $module_name) { - field_associate_fields($module_name); + $fields = field_associate_fields($module_name, $fields); } - 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(); + + foreach ($fields as $id => $field) { + if (!in_array($field['module'], $modules)) { + $fields[$id]['active'] = 0; + } + if (!in_array($field['storage_module'], $modules)) { + $fields[$id]['storage_active'] = 0; + } + } + + foreach ($fields as $id => $field) { + if (!$field['deleted']) { + // We can not use field_update_field because the prior_field does not + // check whether a field is really there or not. + config('field.field.' . $field['field_name'])->setData($field)->save(); + } + else { + $deleted_fields[$field['id']] = $field; + } + } + + // Save the deleted fields. + state()->set('field.deleted', $deleted_fields); + + field_cache_clear(); } /** @@ -523,24 +610,37 @@ function field_sync_field_status() { * * @param $module * The name of the module to update on. + * @param $fields + * A collection of fields. */ -function field_associate_fields($module) { +function field_associate_fields($module, $fields) { + // 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(); + $field_types = array_keys($field_types); + foreach ($fields as $id => $field) { + if (in_array($field['type'], $field_types)) { + $fields[$id]['module'] = $module; + $fields[$id]['active'] = TRUE; + } + } } + // 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(); + $storage_types = array_keys($storage_types); + foreach ($fields as $id => $field) { + if (in_array($field['storage_type'], $storage_types)) { + $fields[$id]['storage_module'] = $module; + $fields[$id]['storage_active'] = TRUE; + } + } } + + return $fields; } /** @@ -700,12 +800,17 @@ function _field_sort_items_value_helper($a, $b) { * If no $settings are passed, the current settings are returned. */ function field_bundle_settings($entity_type, $bundle, $settings = NULL) { + $identifier = $entity_type . '.' . $bundle; + if (isset($settings)) { - variable_set('field_bundle_settings_' . $entity_type . '__' . $bundle, $settings); + config('field.settings.' . $identifier)->setData($settings)->save(); field_info_cache_clear(); } else { - $settings = variable_get('field_bundle_settings_' . $entity_type . '__' . $bundle, array()); + $settings = config('field.settings.' . $identifier)->get(); + if (empty($settings)) { + $settings = array(); + } $settings += array( 'view_modes' => array(), 'extra_fields' => array(), @@ -829,6 +934,21 @@ function field_cache_clear() { } /** + * Generate an id for fields or instances. + * + * When a field is a deleted, the tables are renamed to {field_data_field_id} + * and {field_revision_field_id}. To make sure alternative uuid implementations + * don't generate longer uuid's and using str_replace() to replace dashes + * to underscores might end up in table names longer than 64 characters, we + * hash the uuid and then take the first 6 characters so we end up with a short + * unique id. + */ +function field_generate_id() { + $uuid = new Uuid(); + return substr(hash('sha256', $uuid->generate()), 0, 6); +} + +/** * Filters an HTML string to prevent cross-site-scripting (XSS) vulnerabilities. * * Like filter_xss_admin(), but with a shorter list of allowed tags. diff --git a/core/modules/field/lib/Drupal/field/FieldInfo.php b/core/modules/field/lib/Drupal/field/FieldInfo.php index b1692f7..ab8fae7 100644 --- a/core/modules/field/lib/Drupal/field/FieldInfo.php +++ b/core/modules/field/lib/Drupal/field/FieldInfo.php @@ -142,17 +142,10 @@ public function getFieldMap() { $map = array(); - $query = db_select('field_config_instance', 'fci'); - $query->join('field_config', 'fc', 'fc.id = fci.field_id'); - $query->fields('fc', array('type')); - $query->fields('fci', array('field_name', 'entity_type', 'bundle')) - ->condition('fc.active', 1) - ->condition('fc.storage_active', 1) - ->condition('fc.deleted', 0) - ->condition('fci.deleted', 0); - foreach ($query->execute() as $row) { - $map[$row->field_name]['bundles'][$row->entity_type][] = $row->bundle; - $map[$row->field_name]['type'] = $row->type; + $instances = field_read_instances(); + foreach ($instances as $key => $instance) { + $map[$instance['field_name']]['bundles'][$instance['entity_type']][] = $instance['bundle']; + $map[$instance['field_name']]['type'] = $instance['type']; } // Save in "static" and persistent caches. @@ -274,7 +267,7 @@ public function getField($field_name) { // Do not check the (large) persistent cache, but read the definition. // Cache miss: read from definition. - if ($field = field_read_field(array('field_name' => $field_name))) { + if ($field = field_read_field($field_name)) { $field = $this->prepareField($field); // Save in the "static" cache. @@ -379,7 +372,7 @@ public function getBundleInstances($entity_type, $bundle) { // Collect the fields in the bundle. $params = array('entity_type' => $entity_type, 'bundle' => $bundle); - $fields = field_read_fields($params); + $fields = field_read_fields(); // This iterates on non-deleted instances, so deleted fields are kept out of // the persistent caches. diff --git a/core/modules/field/lib/Drupal/field/Tests/BulkDeleteTest.php b/core/modules/field/lib/Drupal/field/Tests/BulkDeleteTest.php index cc47e59..c5df419 100644 --- a/core/modules/field/lib/Drupal/field/Tests/BulkDeleteTest.php +++ b/core/modules/field/lib/Drupal/field/Tests/BulkDeleteTest.php @@ -7,6 +7,8 @@ namespace Drupal\field\Tests; +use Drupal\Field\FieldInstance; + /** * Unit test class for field bulk delete and batch purge functionality. */ @@ -167,7 +169,7 @@ function testDeleteFieldInstance() { // Delete the instance. $instance = field_info_instance($this->entity_type, $field['field_name'], $bundle); - field_delete_instance($instance); + field_delete_instance($instance->getArray()); // The instance still exists, deleted. $instances = field_read_instances(array('field_id' => $field['id'], 'deleted' => 1), array('include_deleted' => 1, 'include_inactive' => 1)); @@ -217,7 +219,7 @@ function testPurgeInstance() { // Delete the instance. $instance = field_info_instance($this->entity_type, $field['field_name'], $bundle); - field_delete_instance($instance); + field_delete_instance($instance->getArray()); // No field hooks were called. $mem = field_test_memorize(); @@ -281,7 +283,7 @@ function testPurgeField() { // Delete the first instance. $bundle = reset($this->bundles); $instance = field_info_instance($this->entity_type, $field['field_name'], $bundle); - field_delete_instance($instance); + field_delete_instance($instance->getArray()); // Assert that hook_field_delete() was not called yet. $mem = field_test_memorize(); @@ -312,7 +314,7 @@ function testPurgeField() { // Delete the second instance. $bundle = next($this->bundles); $instance = field_info_instance($this->entity_type, $field['field_name'], $bundle); - field_delete_instance($instance); + field_delete_instance($instance->getArray()); // Assert that hook_field_delete() was not called yet. $mem = field_test_memorize(); diff --git a/core/modules/field/lib/Drupal/field/Tests/CrudTest.php b/core/modules/field/lib/Drupal/field/Tests/CrudTest.php index 4e7557e..43cfb5f 100644 --- a/core/modules/field/lib/Drupal/field/Tests/CrudTest.php +++ b/core/modules/field/lib/Drupal/field/Tests/CrudTest.php @@ -45,10 +45,8 @@ function testCreateField() { $mem = field_test_memorize(); $this->assertIdentical($mem['field_test_field_create_field'][0][0], $field_definition, '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. + $record = config('field.field.' . $field_definition['field_name'])->get(); // Ensure that basic properties are preserved. $this->assertEqual($record['field_name'], $field_definition['field_name'], 'The field name is properly saved.'); @@ -59,7 +57,7 @@ function testCreateField() { // 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($record['data']['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.'); @@ -157,11 +155,10 @@ function testCreateField() { function testCreateFieldFail() { $field_name = 'duplicate'; $field_definition = array('field_name' => $field_name, 'type' => 'test_field', 'storage' => array('type' => 'field_test_storage_failure')); - $query = db_select('field_config')->condition('field_name', $field_name)->countQuery(); + $field = config('field.field.' . $field_name)->get(); - // 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 +169,9 @@ function testCreateFieldFail() { $this->assertTrue(TRUE, 'Field creation (correctly) fails.'); } - // The field does not appear in field_config. - $count = $query->execute()->fetchField(); - $this->assertEqual($count, 0, 'A field_config row for the field does not exist.'); + // The field does not exist. + $field = config('field.field.' . $field_name)->get(); + $this->assertFalse($field, 'The field does not exist.'); } /** @@ -268,8 +265,8 @@ function testDeleteField() { // Make sure that the field is marked as deleted when it is specifically // loaded. - $field = field_read_field($this->field['field_name'], array('include_deleted' => TRUE)); - $this->assertTrue(!empty($field['deleted']), 'A deleted field is marked for deletion.'); + $deleted_fields = state()->get('field.deleted'); + $this->assertTrue(isset($deleted_fields[$field['id']]), 'A deleted field is marked for deletion.'); // Make sure that this field's instance is marked as deleted when it is // specifically loaded. @@ -470,6 +467,6 @@ function _testActiveHelper($field_definition, $modules) { // Check that the field is active again after all modules have been // enabled. $field = field_read_field($field_name); - $this->assertTrue($field_definition <= $field, 'The field was was marked active.'); + $this->assertTrue($field_definition <= $field, 'The field was marked active.'); } } diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldImportChangeTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldImportChangeTest.php new file mode 100644 index 0000000..2e242c7 --- /dev/null +++ b/core/modules/field/lib/Drupal/field/Tests/FieldImportChangeTest.php @@ -0,0 +1,61 @@ + 'Field CMI change tests', + 'description' => 'Update field and instances during CMI change hook invocation.', + 'group' => 'Field API', + ); + } + + function setUp() { + parent::setUp(); + + $this->instance_name = 'field.instance.node.test_import.field_test_import'; + $this->drupalCreateContentType(array('type' => 'test_import', 'name' => 'Test import')); + + $admin_user = $this->drupalCreateUser(array('access administration pages', 'access content overview', 'administer nodes', 'bypass node access')); + $this->drupalLogin($admin_user); + } + + function testImportChange() { + + // Assert default test import. + $this->drupalGet('node/add/test_import'); + $this->assertRaw('Test import field', 'Test import field found'); + + // Export. + config_export(); + + // Change label. + $staging = $this->container->get('config.storage.staging'); + $instance = $staging->read($this->instance_name); + $instance['data']['label'] = 'Test update import field'; + $staging->write($this->instance_name, $instance); + + // Import again. + config_import(); + + // Assert updated label. + $this->drupalGet('node/add/test_import'); + $this->assertText('Test update import field', 'Updated test import field found'); + } +} diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldImportCreateTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldImportCreateTest.php new file mode 100644 index 0000000..79d729d --- /dev/null +++ b/core/modules/field/lib/Drupal/field/Tests/FieldImportCreateTest.php @@ -0,0 +1,43 @@ + 'Field CMI create tests', + 'description' => 'Create field and instances during CMI create hook invocation.', + 'group' => 'Field API', + ); + } + + function setUp() { + parent::setUp(); + + $this->drupalCreateContentType(array('type' => 'test_import', 'name' => 'Test import')); + + $admin_user = $this->drupalCreateUser(array('access administration pages', 'access content overview', 'administer nodes', 'bypass node access')); + $this->drupalLogin($admin_user); + } + + function testImportCreate() { + + // Assert default test import. + $this->drupalGet('node/add/test_import'); + $this->assertNoText('Test import field', 'Test import field not found'); + + // Enable field_test_config module and assert the test import + // field and instance is available on the Test content type. + module_enable(array('field_test_config')); + $this->drupalGet('node/add/test_import'); + $this->assertText('Test import field', 'Test import field found'); + } +} diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldImportDeleteTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldImportDeleteTest.php new file mode 100644 index 0000000..99bbd84 --- /dev/null +++ b/core/modules/field/lib/Drupal/field/Tests/FieldImportDeleteTest.php @@ -0,0 +1,61 @@ + 'Field CMI delete tests', + 'description' => 'Delete field and instances during CMI delete hook invocation.', + 'group' => 'Field API', + ); + } + + function setUp() { + parent::setUp(); + + $this->instance_name = 'field.instance.node.test_import.field_test_import'; + $this->drupalCreateContentType(array('type' => 'test_import', 'name' => 'Test import')); + + $admin_user = $this->drupalCreateUser(array('access administration pages', 'access content overview', 'administer nodes', 'bypass node access')); + $this->drupalLogin($admin_user); + } + + function testImportChange() { + + // Assert default test import. + $this->drupalGet('node/add/test_import'); + $this->assertRaw('Test import field', 'Test import field found'); + + // Export. + config_export(); + + // Change label. + $staging = $this->container->get('config.storage.staging'); + $instance = $staging->read($this->instance_name); + $instance['deleted'] = TRUE; + $staging->write($this->instance_name, $instance); + + // Import again. + config_import(); + + // Assert the instance is gone. + $this->drupalGet('node/add/test_import'); + $this->assertNoText('Test import field', 'Test import field not found'); + } +} diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldInfoTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldInfoTest.php index cfc1718..d7a5019 100644 --- a/core/modules/field/lib/Drupal/field/Tests/FieldInfoTest.php +++ b/core/modules/field/lib/Drupal/field/Tests/FieldInfoTest.php @@ -133,13 +133,9 @@ function testFieldPrepare() { // 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 = config('field.field.' . $field_definition['field_name'])->get(); + $field['data']['settings'] = array(); + field_update_field($field); field_cache_clear(); @@ -148,7 +144,7 @@ function testFieldPrepare() { // 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.'); } /** @@ -170,18 +166,16 @@ function testInstancePrepare() { // Simulate a stored instance definition missing various settings (e.g. a // third-party module adding instance, widget or display 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); + $instance = config('field.instance.' . $instance_definition['entity_type'] . '.' . $instance_definition['bundle'] . '.' . $instance_definition['field_name'])->get(); + $data['settings'] = array(); $data['widget']['settings'] = 'unavailable_widget'; $data['widget']['settings'] = array(); $data['display']['default']['type'] = 'unavailable_formatter'; $data['display']['default']['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(); + + $instance['data'] += $data; + field_update_instance($instance); field_cache_clear(); @@ -190,7 +184,7 @@ function testInstancePrepare() { // Check that all expected instance settings are in place. $field_type = field_info_field_types($field_definition['type']); - $this->assertIdentical($instance['settings'], $field_type['instance_settings'] , 'All expected instance settings are present.'); + $this->assertEqual($instance['settings'], $field_type['instance_settings'] , 'All expected instance settings are present.'); // Check that the default widget is used and expected settings are in place. $widget = $instance->getWidget(); diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldInstanceCrudTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldInstanceCrudTest.php index 84cee72..c240027 100644 --- a/core/modules/field/lib/Drupal/field/Tests/FieldInstanceCrudTest.php +++ b/core/modules/field/lib/Drupal/field/Tests/FieldInstanceCrudTest.php @@ -8,6 +8,7 @@ namespace Drupal\field\Tests; use Drupal\field\FieldException; +use Drupal\field\FieldInstance; class FieldInstanceCrudTest extends FieldTestBase { @@ -55,10 +56,8 @@ function setUp() { function testCreateFieldInstance() { 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. + $record = config('field.instance.' . $this->instance_definition['entity_type'] . '.' . $this->instance_definition['bundle'] . '.' . $this->instance_definition['field_name'])->get(); $field_type = field_info_field_types($this->field['type']); $widget_type = field_info_widget_types($field_type['default_widget']); @@ -68,7 +67,7 @@ function testCreateFieldInstance() { $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->assertEqual($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.'); @@ -76,7 +75,7 @@ function testCreateFieldInstance() { $this->assertIdentical($record['data']['display']['default']['type'], $field_type['default_formatter'], 'Default formatter for "full" view_mode 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->assertEqual($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->assertIdentical($record['data']['display']['default']['settings'], $formatter_type['settings'], 'Default formatter settings for "full" view_mode have been written.'); @@ -143,7 +142,14 @@ function testReadFieldInstance() { // Read the instance back. $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); - $this->assertTrue($this->instance_definition == $instance, 'The field was properly read.'); + $this->assertTrue($this->instance_definition['id'] == $instance['id'], 'The field was properly read.'); + $this->assertTrue($this->instance_definition['label'] == $instance['label'], 'The field was properly read.'); + $this->assertTrue($this->instance_definition['field_id'] == $instance['field_id'], '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.'); + $this->assertTrue($this->instance_definition['settings'] == $instance['settings'], 'The field was properly read.'); + $this->assertTrue($this->instance_definition['display'] == $instance['display'], 'The field was properly read.'); + $this->assertTrue($this->instance_definition['widget'] == $instance['widget'], 'The field was properly read.'); } /** @@ -239,7 +245,9 @@ function testDeleteFieldInstance() { // Make sure the field is deleted when its last instance is deleted. field_delete_instance($another_instance); - $field = field_read_field($another_instance['field_name'], array('include_deleted' => TRUE)); - $this->assertTrue(!empty($field['deleted']), 'A deleted field is marked for deletion after all its instances have been marked for deletion.'); + $deleted_fields = state()->get('field.deleted'); + $this->assertTrue(isset($deleted_fields[$another_instance['field_id']]), 'A deleted field is marked for deletion.'); + $field = field_read_field(array('field_name' => $another_instance['field_name'])); + $this->assertFalse($field, 'The field marked to be deleted is not found anymore in the configuration.'); } } diff --git a/core/modules/field/modules/field_sql_storage/field_sql_storage.install b/core/modules/field/modules/field_sql_storage/field_sql_storage.install index 4d4fe57..2bb42b9 100644 --- a/core/modules/field/modules/field_sql_storage/field_sql_storage.install +++ b/core/modules/field/modules/field_sql_storage/field_sql_storage.install @@ -12,15 +12,15 @@ 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); - } + module_load_include('module', 'field'); + $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); } } + return $schema; } @@ -78,51 +78,3 @@ function _update_8000_field_sql_storage_write($entity_type, $bundle, $entity_id, } } -/** - * Changes field language into langcode. - */ -function field_sql_storage_update_8000(&$sandbox) { - // Prepare updated schema data structures. - $primary_key_data = array ( - 'entity_type', - 'entity_id', - 'deleted', - 'delta', - 'langcode', - ); - $primary_key_revision = array ( - 'entity_type', - 'entity_id', - 'revision_id', - 'deleted', - 'delta', - 'langcode', - ); - $langcode_index = array( - 'langcode', - ); - $field_langcode = array( - 'type' => 'varchar', - 'length' => 32, - 'not null' => true, - 'default' => '', - ); - - // Retrieve field data. - $fields = _update_7000_field_read_fields(array('storage_type' => 'field_sql_storage')); - - // Update schema. - foreach ($fields as $field) { - $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); - - foreach ($table_info as $table => $primary_key) { - db_drop_primary_key($table); - db_drop_index($table, 'language'); - db_change_field($table, 'language', 'langcode', $field_langcode); - db_add_primary_key($table, $primary_key); - db_add_index($table, 'langcode', $langcode_index); - } - } -} diff --git a/core/modules/field/tests/modules/field_test_config/config/field.field.field_test_import.yml b/core/modules/field/tests/modules/field_test_config/config/field.field.field_test_import.yml new file mode 100644 index 0000000..9f4e60c --- /dev/null +++ b/core/modules/field/tests/modules/field_test_config/config/field.field.field_test_import.yml @@ -0,0 +1,82 @@ +active: 1 +bundles: + node: + - test_import +cardinality: '1' +columns: + format: + length: 255 + 'not null': false + type: varchar + value: + length: '255' + 'not null': false + type: varchar +data: + entity_types: { } + 'foreign keys': + format: + columns: + format: format + table: filter_format + id: fd6ccf + indexes: + format: + - format + settings: + max_length: '255' + storage: + active: 1 + details: + sql: + FIELD_LOAD_CURRENT: + field_data_field_test_import: + format: field_test_import_format + value: field_test_import_value + FIELD_LOAD_REVISION: + field_revision_field_test_import: + format: field_test_import_format + value: field_test_import_value + module: field_sql_storage + settings: { } + type: field_sql_storage + storage_active: 1 + storage_module: field_sql_storage + storage_type: field_sql_storage + translatable: false +deleted: 0 +entity_types: { } +field_name: field_test_import +'foreign keys': + format: + columns: + format: format + table: filter_format +id: fd6ccf +indexes: + format: + - format +locked: false +module: text +settings: + max_length: '255' +storage: + active: 1 + details: + sql: + FIELD_LOAD_CURRENT: + field_data_field_test_import: + format: field_test_import_format + value: field_test_import_value + FIELD_LOAD_REVISION: + field_revision_field_test_import: + format: field_test_import_format + value: field_test_import_value + module: field_sql_storage + settings: { } + type: field_sql_storage +storage_active: 1 +storage_module: field_sql_storage +storage_type: field_sql_storage +translatable: false +type: text diff --git a/core/modules/field/tests/modules/field_test_config/config/field.instance.node.test_import.field_test_import.yml b/core/modules/field/tests/modules/field_test_config/config/field.instance.node.test_import.field_test_import.yml new file mode 100644 index 0000000..b67d7ab --- /dev/null +++ b/core/modules/field/tests/modules/field_test_config/config/field.instance.node.test_import.field_test_import.yml @@ -0,0 +1,40 @@ +active: 1 +bundle: test_import +data: + active: 1 + default_value: null + description: '' + display: + default: + label: above + module: text + settings: { } + type: text_default + weight: 1 + label: 'Test import field' + locked: false + module: text + required: 0 + settings: + text_processing: '0' + user_register_form: false + storage_active: 1 + storage_type: field_sql_storage + type: text + widget: + active: 1 + module: text + settings: + size: '60' + type: text_textfield + weight: '-3' +deleted: 0 +entity_type: node +field_id: fd6ccf +field_name: field_test_import +id: 0f12b4 +locked: false +module: text +storage_active: 1 +storage_type: field_sql_storage +type: text diff --git a/core/modules/field/tests/modules/field_test_config/field_test_config.info b/core/modules/field/tests/modules/field_test_config/field_test_config.info new file mode 100644 index 0000000..49b0509 --- /dev/null +++ b/core/modules/field/tests/modules/field_test_config/field_test_config.info @@ -0,0 +1,6 @@ +name = "Field API configuration tests" +description = "Support module for the Field API configuration tests." +core = 8.x +package = Testing +version = VERSION +hidden = TRUE diff --git a/core/modules/field/tests/modules/field_test_config/field_test_config.module b/core/modules/field/tests/modules/field_test_config/field_test_config.module new file mode 100644 index 0000000..e208464 --- /dev/null +++ b/core/modules/field/tests/modules/field_test_config/field_test_config.module @@ -0,0 +1,6 @@ +getArray()); drupal_set_message(t('The field %field has been deleted from the %type content type.', array('%field' => $instance['label'], '%type' => $bundle_label))); } else { diff --git a/core/modules/file/file.install b/core/modules/file/file.install index ca6e1fd..a8c7b0c 100644 --- a/core/modules/file/file.install +++ b/core/modules/file/file.install @@ -125,8 +125,8 @@ function file_schema() { ), 'id' => array( 'description' => 'The primary key of the object using the file.', - 'type' => 'int', - 'unsigned' => TRUE, + 'type' => 'varchar', + 'length' => 64, 'not null' => TRUE, 'default' => 0, ), @@ -232,3 +232,29 @@ function file_requirements($phase) { return $requirements; } + +/** + * @defgroup updates-7.x-to-8.x Updates from 7.x to 8.x + * @{ + * Update functions from 7.x to 8.x. + */ + +/** + * Convert the id column in file_usage table for Field API. + */ +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); +} + +/** + * @} End of "defgroup updates-7.x-to-8.x". + * The next series of updates should start at 9000. + */ + diff --git a/core/modules/forum/forum.install b/core/modules/forum/forum.install index 56f8402..9e56db6 100644 --- a/core/modules/forum/forum.install +++ b/core/modules/forum/forum.install @@ -22,10 +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 @@ -55,7 +51,7 @@ function forum_enable() { } // Create the 'taxonomy_forums' field if it doesn't already exist. - if (!field_info_field('taxonomy_forums')) { + if (!field_read_field('taxonomy_forums', array('include_inactive' => TRUE))) { $field = array( 'field_name' => 'taxonomy_forums', 'type' => 'taxonomy_term_reference', diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/FieldUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/FieldUpgradePathTest.php new file mode 100644 index 0000000..9df2b03 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/FieldUpgradePathTest.php @@ -0,0 +1,46 @@ + 'Field API upgrade test', + 'description' => 'Upgrade tests for Field API.', + 'group' => 'Upgrade path', + ); + } + + public function setUp() { + $this->databaseDumpFiles = array( + drupal_get_path('module', 'system') . '/tests/upgrade/drupal-7.filled.standard_all.database.php.gz', + ); + parent::setUp(); + } + + /** + * Test the upgrade path for fields and instances to the new + * configuration system. + */ + function testFieldConfigUpgrade() { + $this->assertTrue($this->performUpgrade(), t('The upgrade was completed successfully.')); + + // Assert the field_config and field_config_instance tables are gone. + $this->assertTrue(!db_table_exists('field_config'), 'The field_config table has been removed.'); + $this->assertTrue(!db_table_exists('field_config_instance'), 'The field_config_instance table has been removed.'); + + // Assert the body field and instance on the article are converted to CMI. + $body_field = field_info_field('body'); + $this->assertNotNull($body_field, 'The body field has been found.'); + $body_field_instance = field_info_instance('node', 'body', 'article'); + $this->assertNotNull($body_field, 'The body field instance on the article content type has been found.'); + } +}