summaryrefslogtreecommitdiff
path: root/modules/field
diff options
context:
space:
mode:
Diffstat (limited to 'modules/field')
-rw-r--r--modules/field/field.api.php156
-rw-r--r--modules/field/field.attach.inc17
-rw-r--r--modules/field/field.crud.inc11
-rw-r--r--modules/field/field.form.inc4
-rw-r--r--modules/field/field.info.inc14
-rw-r--r--modules/field/field.install27
-rw-r--r--modules/field/field.module330
-rw-r--r--modules/field/modules/list/list.module31
-rw-r--r--modules/field/modules/list/tests/list.test83
-rw-r--r--modules/field/modules/list/tests/list_test.module9
-rw-r--r--modules/field/modules/number/number.module4
-rw-r--r--modules/field/modules/number/number.test22
-rw-r--r--modules/field/modules/options/options.api.php7
-rw-r--r--modules/field/modules/options/options.module21
-rw-r--r--modules/field/modules/options/options.test35
-rw-r--r--modules/field/tests/field.test73
-rw-r--r--modules/field/theme/field.tpl.php2
17 files changed, 576 insertions, 270 deletions
diff --git a/modules/field/field.api.php b/modules/field/field.api.php
index 329cf16d1..134af6615 100644
--- a/modules/field/field.api.php
+++ b/modules/field/field.api.php
@@ -1,7 +1,7 @@
<?php
/**
- * @ingroup hooks
+ * @addtogroup hooks
* @{
*/
@@ -83,35 +83,19 @@ function hook_field_extra_fields_alter(&$info) {
/**
* @defgroup field_types Field Types API
* @{
- * Define field types, widget types, display formatter types, storage types.
+ * Define field types.
*
- * The bulk of the Field Types API are related to field types. A field type
- * represents a particular type of data (integer, string, date, etc.) that
- * can be attached to a fieldable entity. hook_field_info() defines the basic
- * properties of a field type, and a variety of other field hooks are called by
- * the Field Attach API to perform field-type-specific actions.
- *
- * @see hook_field_info()
- * @see hook_field_info_alter()
- * @see hook_field_schema()
- * @see hook_field_load()
- * @see hook_field_validate()
- * @see hook_field_presave()
- * @see hook_field_insert()
- * @see hook_field_update()
- * @see hook_field_delete()
- * @see hook_field_delete_revision()
- * @see hook_field_prepare_view()
- * @see hook_field_is_empty()
+ * In the Field API, each field has a type, which determines what kind of data
+ * (integer, string, date, etc.) the field can hold, which settings it provides,
+ * and so on. The data type(s) accepted by a field are defined in
+ * hook_field_schema(); other basic properties of a field are defined in
+ * hook_field_info(). The other hooks below are called by the Field Attach API
+ * to perform field-type-specific actions.
*
* The Field Types API also defines two kinds of pluggable handlers: widgets
- * and formatters, which specify how the field appears in edit forms and in
- * displayed entities. Widgets and formatters can be implemented by a field-type
- * module for its own field types, or by a third-party module to extend the
- * behavior of existing field types.
- *
- * @see hook_field_widget_info()
- * @see hook_field_formatter_info()
+ * and formatters. @link field_widget Widgets @endlink specify how the field
+ * appears in edit forms, while @link field_formatter formatters @endlink
+ * specify how the field appears in displayed entities.
*
* A third kind of pluggable handlers, storage backends, is defined by the
* @link field_storage Field Storage API @endlink.
@@ -442,9 +426,14 @@ function hook_field_presave($entity_type, $entity, $field, $instance, $langcode,
}
/**
- * Define custom insert behavior for this module's field types.
+ * Define custom insert behavior for this module's field data.
*
- * Invoked from field_attach_insert().
+ * This hook is invoked from field_attach_insert() on the module that defines a
+ * field, during the process of inserting an entity object (node, taxonomy term,
+ * etc.). It is invoked just before the data for this field on the particular
+ * entity object is inserted into field storage. Only field modules that are
+ * storing or tracking information outside the standard field storage mechanism
+ * need to implement this hook.
*
* @param $entity_type
* The type of $entity.
@@ -458,6 +447,9 @@ function hook_field_presave($entity_type, $entity, $field, $instance, $langcode,
* The language associated with $items.
* @param $items
* $entity->{$field['field_name']}[$langcode], or an empty array if unset.
+ *
+ * @see hook_field_update()
+ * @see hook_field_delete()
*/
function hook_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) {
if (variable_get('taxonomy_maintain_index_table', TRUE) && $field['storage']['type'] == 'field_sql_storage' && $entity_type == 'node' && $entity->status) {
@@ -475,9 +467,14 @@ function hook_field_insert($entity_type, $entity, $field, $instance, $langcode,
}
/**
- * Define custom update behavior for this module's field types.
+ * Define custom update behavior for this module's field data.
*
- * Invoked from field_attach_update().
+ * This hook is invoked from field_attach_update() on the module that defines a
+ * field, during the process of updating an entity object (node, taxonomy term,
+ * etc.). It is invoked just before the data for this field on the particular
+ * entity object is updated into field storage. Only field modules that are
+ * storing or tracking information outside the standard field storage mechanism
+ * need to implement this hook.
*
* @param $entity_type
* The type of $entity.
@@ -491,6 +488,9 @@ function hook_field_insert($entity_type, $entity, $field, $instance, $langcode,
* The language associated with $items.
* @param $items
* $entity->{$field['field_name']}[$langcode], or an empty array if unset.
+ *
+ * @see hook_field_insert()
+ * @see hook_field_delete()
*/
function hook_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) {
if (variable_get('taxonomy_maintain_index_table', TRUE) && $field['storage']['type'] == 'field_sql_storage' && $entity_type == 'node') {
@@ -556,10 +556,14 @@ function hook_field_storage_update_field($field, $prior_field, $has_data) {
}
/**
- * Define custom delete behavior for this module's field types.
+ * Define custom delete behavior for this module's field data.
*
- * This hook is invoked just before the data is deleted from field storage
- * in field_attach_delete().
+ * This hook is invoked from field_attach_delete() on the module that defines a
+ * field, during the process of deleting an entity object (node, taxonomy term,
+ * etc.). It is invoked just before the data for this field on the particular
+ * entity object is deleted from field storage. Only field modules that are
+ * storing or tracking information outside the standard field storage mechanism
+ * need to implement this hook.
*
* @param $entity_type
* The type of $entity.
@@ -573,6 +577,9 @@ function hook_field_storage_update_field($field, $prior_field, $has_data) {
* The language associated with $items.
* @param $items
* $entity->{$field['field_name']}[$langcode], or an empty array if unset.
+ *
+ * @see hook_field_insert()
+ * @see hook_field_update()
*/
function hook_field_delete($entity_type, $entity, $field, $instance, $langcode, &$items) {
list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
@@ -583,7 +590,7 @@ function hook_field_delete($entity_type, $entity, $field, $instance, $langcode,
// be counted in hook_file_references().
$item['file_field_type'] = $entity_type;
$item['file_field_id'] = $id;
- file_field_delete_file($item, $field);
+ file_field_delete_file($item, $field, $entity_type, $id);
}
}
@@ -608,10 +615,11 @@ function hook_field_delete($entity_type, $entity, $field, $instance, $langcode,
* $entity->{$field['field_name']}[$langcode], or an empty array if unset.
*/
function hook_field_delete_revision($entity_type, $entity, $field, $instance, $langcode, &$items) {
+ list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
foreach ($items as $delta => $item) {
// For hook_file_references, remember that this file is being deleted.
$item['file_field_name'] = $field['field_name'];
- if (file_field_delete_file($item, $field)) {
+ if (file_field_delete_file($item, $field, $entity_type, $id)) {
$items[$delta] = NULL;
}
}
@@ -668,11 +676,33 @@ function hook_field_is_empty($item, $field) {
}
/**
- * Expose Field API widget types.
+ * @} End of "defgroup field_types"
+ */
+
+/**
+ * @defgroup field_widget Field Widget API
+ * @{
+ * Define Field API widget types.
+ *
+ * Field API widgets specify how fields are displayed in edit forms. Fields of a
+ * given @link field_types field type @endlink may be edited using more than one
+ * widget. In this case, the Field UI module allows the site builder to choose
+ * which widget to use. Widget types are defined by implementing
+ * hook_field_widget_info().
+ *
+ * Widgets are
+ * @link http://api.drupal.org/api/drupal/developer--topics--forms_api_reference.html Form API @endlink
+ * elements with additional processing capabilities. Widget hooks are typically
+ * called by the Field Attach API during the creation of the field form
+ * structure with field_attach_form().
*
- * Widgets are Form API elements with additional processing capabilities.
- * Widget hooks are typically called by the Field Attach API during the
- * creation of the field form structure with field_attach_form().
+ * @see field
+ * @see field_types
+ * @see field_formatter
+ */
+
+/**
+ * Expose Field API widget types.
*
* @return
* An array describing the widget types implemented by the module.
@@ -852,17 +882,16 @@ function hook_field_widget_form(&$form, &$form_state, $field, $instance, $langco
* @param $context
* An associative array containing the following key-value pairs, matching the
* arguments received by hook_field_widget_form():
- * - "form": The form structure where widgets are being attached to. This
- * might be a full form structure, or a sub-element of a larger form.
- * - "field": The field structure.
- * - "instance": The field instance structure.
- * - "langcode": The language associated with $items.
- * - "items": Array of default values for this field.
- * - "delta": The order of this item in the array of subelements (0, 1, 2,
- * etc).
+ * - form: The form structure to which widgets are being attached. This may be
+ * a full form structure, or a sub-element of a larger form.
+ * - field: The field structure.
+ * - instance: The field instance structure.
+ * - langcode: The language associated with $items.
+ * - items: Array of default values for this field.
+ * - delta: The order of this item in the array of subelements (0, 1, 2, etc).
*
* @see hook_field_widget_form()
- * @see hook_field_widget_WIDGET_TYPE_form_alter
+ * @see hook_field_widget_WIDGET_TYPE_form_alter()
*/
function hook_field_widget_form_alter(&$element, &$form_state, $context) {
// Add a css class to widget form elements for all fields of type mytype.
@@ -928,6 +957,29 @@ function hook_field_widget_error($element, $error, $form, &$form_state) {
form_error($element['value'], $error['message']);
}
+
+/**
+ * @} End of "defgroup field_widget"
+ */
+
+
+/**
+ * @defgroup field_formatter Field Formatter API
+ * @{
+ * Define Field API formatter types.
+ *
+ * Field API formatters specify how fields are displayed when the entity to
+ * which the field is attached is displayed. Fields of a given
+ * @link field_types field type @endlink may be displayed using more than one
+ * formatter. In this case, the Field UI module allows the site builder to
+ * choose which formatter to use. Field formatters are defined by implementing
+ * hook_field_formatter_info().
+ *
+ * @see field
+ * @see field_types
+ * @see field_widget
+ */
+
/**
* Expose Field API formatter types.
*
@@ -1146,7 +1198,7 @@ function hook_field_formatter_view($entity_type, $entity, $field, $instance, $la
}
/**
- * @} End of "ingroup field_type"
+ * @} End of "defgroup field_formatter"
*/
/**
@@ -1502,7 +1554,7 @@ function hook_field_attach_delete_bundle($entity_type, $bundle, $instances) {
}
/**
- * @} End of "ingroup field_attach"
+ * @} End of "defgroup field_attach"
*/
/**
@@ -2623,5 +2675,5 @@ function hook_field_access($op, $field, $entity_type, $entity, $account) {
}
/**
- * @} End of "ingroup hooks"
+ * @} End of "addtogroup hooks"
*/
diff --git a/modules/field/field.attach.inc b/modules/field/field.attach.inc
index bd2934b48..36117eb7a 100644
--- a/modules/field/field.attach.inc
+++ b/modules/field/field.attach.inc
@@ -73,7 +73,7 @@ define('FIELD_STORAGE_INSERT', 'insert');
* @{
* Operate on Field API data attached to Drupal entities.
*
- * Field Attach API functions load, store, display, generate Form API
+ * Field Attach API functions load, store, display, generate Field API
* structures, and perform a variety of other functions for field data attached
* to individual entities.
*
@@ -1321,12 +1321,9 @@ function field_attach_rename_bundle($entity_type, $bundle_old, $bundle_new) {
field_cache_clear();
// Update bundle settings.
- $settings = variable_get('field_bundle_settings', array());
- if (isset($settings[$entity_type][$bundle_old])) {
- $settings[$entity_type][$bundle_new] = $settings[$entity_type][$bundle_old];
- unset($settings[$entity_type][$bundle_old]);
- variable_set('field_bundle_settings', $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);
// Let other modules act on renaming the bundle.
module_invoke_all('field_attach_rename_bundle', $entity_type, $bundle_old, $bundle_new);
@@ -1360,11 +1357,7 @@ function field_attach_delete_bundle($entity_type, $bundle) {
field_cache_clear();
// Clear bundle display settings.
- $settings = variable_get('field_bundle_settings', array());
- if (isset($settings[$entity_type][$bundle])) {
- unset($settings[$entity_type][$bundle]);
- variable_set('field_bundle_settings', $settings);
- }
+ variable_del('field_bundle_settings_' . $entity_type . '__' . $bundle);
// Let other modules act on deleting the bundle.
module_invoke_all('field_attach_delete_bundle', $entity_type, $bundle, $instances);
diff --git a/modules/field/field.crud.inc b/modules/field/field.crud.inc
index 6df32352b..f9c96c92b 100644
--- a/modules/field/field.crud.inc
+++ b/modules/field/field.crud.inc
@@ -835,11 +835,12 @@ function field_delete_instance($instance, $field_cleanup = TRUE) {
/**
* Purges a batch of deleted Field API data, instances, or fields.
*
- * This function will purge deleted field data on up to a specified maximum
- * number of entities and then return. If a deleted field instance with no
- * remaining data records is found, the instance itself will be purged.
- * If a deleted field with no remaining field instances is found, the field
- * itself will be purged.
+ * This function will purge deleted field data in batches. The batch size
+ * is defined as an argument to the function, and once each batch is finished,
+ * it continues with the next batch until all have completed. If a deleted field
+ * instance with no remaining data records is found, the instance itself will
+ * be purged. If a deleted field with no remaining field instances is found, the
+ * field itself will be purged.
*
* @param $batch_size
* The maximum number of field data records to purge before returning.
diff --git a/modules/field/field.form.inc b/modules/field/field.form.inc
index 4b92501fc..5641375e5 100644
--- a/modules/field/field.form.inc
+++ b/modules/field/field.form.inc
@@ -53,6 +53,8 @@ function field_default_form($entity_type, $entity, $field, $instance, $langcode,
// If field module handles multiple values for this form element, and we
// are displaying an individual element, process the multiple value form.
if (!isset($get_delta) && field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_DEFAULT) {
+ // Store the entity in the form.
+ $form['#entity'] = $entity;
$elements = field_multiple_value_form($field, $instance, $langcode, $items, $form, $form_state);
}
// If the widget is handling multiple values (e.g Options), or if we are
@@ -63,6 +65,7 @@ function field_default_form($entity_type, $entity, $field, $instance, $langcode,
$function = $instance['widget']['module'] . '_field_widget_form';
if (function_exists($function)) {
$element = array(
+ '#entity' => $entity,
'#entity_type' => $instance['entity_type'],
'#bundle' => $instance['bundle'],
'#field_name' => $field_name,
@@ -176,6 +179,7 @@ function field_multiple_value_form($field, $instance, $langcode, $items, &$form,
$multiple = $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED;
$element = array(
'#entity_type' => $instance['entity_type'],
+ '#entity' => $form['#entity'],
'#bundle' => $instance['bundle'],
'#field_name' => $field_name,
'#language' => $langcode,
diff --git a/modules/field/field.info.inc b/modules/field/field.info.inc
index 3b36c0355..e7eaaf0c6 100644
--- a/modules/field/field.info.inc
+++ b/modules/field/field.info.inc
@@ -680,16 +680,16 @@ function field_info_field_by_ids() {
*/
function field_info_instances($entity_type = NULL, $bundle_name = NULL) {
$info = _field_info_collate_fields();
- if (!isset($entity_type)) {
- return $info['instances'];
+
+ if (isset($entity_type) && isset($bundle_name)) {
+ return isset($info['instances'][$entity_type][$bundle_name]) ? $info['instances'][$entity_type][$bundle_name] : array();
}
- if (!isset($bundle_name)) {
- return $info['instances'][$entity_type];
+ elseif (isset($entity_type)) {
+ return isset($info['instances'][$entity_type]) ? $info['instances'][$entity_type] : array();
}
- if (isset($info['instances'][$entity_type][$bundle_name])) {
- return $info['instances'][$entity_type][$bundle_name];
+ else {
+ return $info['instances'];
}
- return array();
}
/**
diff --git a/modules/field/field.install b/modules/field/field.install
index d56eb904c..dff3949fb 100644
--- a/modules/field/field.install
+++ b/modules/field/field.install
@@ -99,14 +99,13 @@ function field_schema() {
'primary key' => array('id'),
'indexes' => array(
'field_name' => array('field_name'),
- // Used by field_read_fields().
+ // Used by field_sync_field_status().
'active' => array('active'),
'storage_active' => array('storage_active'),
'deleted' => array('deleted'),
// Used by field_modules_disabled().
'module' => array('module'),
'storage_module' => array('storage_module'),
- // Used by field_associate_fields().
'type' => array('type'),
'storage_type' => array('storage_type'),
),
@@ -437,3 +436,27 @@ function field_update_7001() {
/**
* @} End of "addtogroup updates-6.x-to-7.x"
*/
+
+/**
+ * @addtogroup updates-7.x-extra
+ * @{
+ */
+
+/**
+ * Split the all-inclusive field_bundle_settings variable per bundle.
+ */
+function field_update_7002() {
+ $settings = variable_get('field_bundle_settings', array());
+ if ($settings) {
+ foreach ($settings as $entity_type => $entity_type_settings) {
+ foreach ($entity_type_settings as $bundle => $bundle_settings) {
+ variable_set('field_bundle_settings_' . $entity_type . '__' . $bundle, $bundle_settings);
+ }
+ }
+ variable_del('field_bundle_settings');
+ }
+}
+
+/**
+ * @} End of "addtogroup updates-7.x-extra"
+ */
diff --git a/modules/field/field.module b/modules/field/field.module
index d110c4878..dedf8470c 100644
--- a/modules/field/field.module
+++ b/modules/field/field.module
@@ -60,166 +60,136 @@ require_once DRUPAL_ROOT . '/modules/field/field.form.inc';
* 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 on
- * the 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.
+ * - 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 on the 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.
+ * 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
- * - ...
+ * - 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().
@@ -316,7 +286,7 @@ function field_help($path, $arg) {
case 'admin/help#field':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
- $output .= '<p>' . t('The Field module allows custom data fields to be defined for <em>entity</em> types (entities include content items, comments, user accounts, and taxonomy terms). The Field module takes care of storing, loading, editing, and rendering field data. Most users will not interact with the Field module directly, but will instead use the <a href="@field-ui-help">Field UI module</a> user interface. Module developers can use the Field API to make new entity types "fieldable" and thus allow fields to be attached to them. For more information, see the online handbook entry for <a href="@field">Field module</a>.', array('@field-ui-help' => url('admin/help/field_ui'), '@field' => 'http://drupal.org/handbook/modules/field')) . '</p>';
+ $output .= '<p>' . t('The Field module allows custom data fields to be defined for <em>entity</em> types (entities include content items, comments, user accounts, and taxonomy terms). The Field module takes care of storing, loading, editing, and rendering field data. Most users will not interact with the Field module directly, but will instead use the <a href="@field-ui-help">Field UI module</a> user interface. Module developers can use the Field API to make new entity types "fieldable" and thus allow fields to be attached to them. For more information, see the online handbook entry for <a href="@field">Field module</a>.', array('@field-ui-help' => url('admin/help/field_ui'), '@field' => 'http://drupal.org/documentation/modules/field')) . '</p>';
$output .= '<h3>' . t('Uses') . '</h3>';
$output .= '<dl>';
$output .= '<dt>' . t('Enabling field types') . '</dt>';
@@ -362,11 +332,12 @@ function field_theme() {
/**
* Implements hook_cron().
- *
- * Purges some deleted Field API data, if any exists.
*/
function field_cron() {
+ // Refresh the 'active' status of fields.
field_sync_field_status();
+
+ // Do a pass of purging on deleted Field API data, if any exists.
$limit = variable_get('field_purge_batch_size', 10);
field_purge_batch($limit);
}
@@ -423,11 +394,30 @@ function field_system_info_alter(&$info, $file, $type) {
* Implements hook_flush_caches().
*/
function field_flush_caches() {
+ // Refresh the 'active' status of fields.
field_sync_field_status();
+
+ // Request a flush of our cache table.
return array('cache_field');
}
/**
+ * Implements hook_modules_enabled().
+ */
+function field_modules_enabled($modules) {
+ // Refresh the 'active' status of fields.
+ field_sync_field_status();
+}
+
+/**
+ * Implements hook_modules_disabled().
+ */
+function field_modules_disabled($modules) {
+ // Refresh the 'active' status of fields.
+ field_sync_field_status();
+}
+
+/**
* Refreshes the 'active' and 'storage_active' columns for fields.
*/
function field_sync_field_status() {
@@ -460,18 +450,18 @@ function field_sync_field_status() {
function field_associate_fields($module) {
// Associate field types.
$field_types = (array) module_invoke($module, 'field_info');
- foreach ($field_types as $name => $field_info) {
+ if ($field_types) {
db_update('field_config')
->fields(array('module' => $module, 'active' => 1))
- ->condition('type', $name)
+ ->condition('type', array_keys($field_types))
->execute();
}
// Associate storage backends.
$storage_types = (array) module_invoke($module, 'field_storage_info');
- foreach ($storage_types as $name => $storage_info) {
+ if ($storage_types) {
db_update('field_config')
->fields(array('storage_module' => $module, 'storage_active' => 1))
- ->condition('storage_type', $name)
+ ->condition('storage_type', array_keys($storage_types))
->execute();
}
}
@@ -613,16 +603,12 @@ 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) {
- $stored_settings = variable_get('field_bundle_settings', array());
-
if (isset($settings)) {
- $stored_settings[$entity_type][$bundle] = $settings;
-
- variable_set('field_bundle_settings', $stored_settings);
+ variable_set('field_bundle_settings_' . $entity_type . '__' . $bundle, $settings);
field_info_cache_clear();
}
else {
- $settings = isset($stored_settings[$entity_type][$bundle]) ? $stored_settings[$entity_type][$bundle] : array();
+ $settings = variable_get('field_bundle_settings_' . $entity_type . '__' . $bundle, array());
$settings += array(
'view_modes' => array(),
'extra_fields' => array(),
@@ -994,16 +980,18 @@ function field_has_data($field) {
*
* @param $op
* The operation to be performed. Possible values:
- * - "edit"
- * - "view"
+ * - 'edit'
+ * - 'view'
* @param $field
- * The field on which the operation is to be performed.
+ * The full field structure array for the field on which the operation is to
+ * be performed. See field_info_field().
* @param $entity_type
* The type of $entity; e.g., 'node' or 'user'.
* @param $entity
* (optional) The entity for the operation.
* @param $account
* (optional) The account to check, if not given use currently logged in user.
+ *
* @return
* TRUE if the operation is allowed;
* FALSE if the operation is denied.
diff --git a/modules/field/modules/list/list.module b/modules/field/modules/list/list.module
index 1d72be44a..634c13488 100644
--- a/modules/field/modules/list/list.module
+++ b/modules/field/modules/list/list.module
@@ -221,24 +221,39 @@ function list_field_update_field($field, $prior_field, $has_data) {
*
* @param $field
* The field definition.
+ * @param $instance
+ * (optional) A field instance array. Defaults to NULL.
+ * @param $entity_type
+ * (optional) The type of entity; e.g. 'node' or 'user'. Defaults to NULL.
+ * @param $entity
+ * (optional) The entity object. Defaults to NULL.
*
* @return
* The array of allowed values. Keys of the array are the raw stored values
* (number or text), values of the array are the display labels.
*/
-function list_allowed_values($field) {
+function list_allowed_values($field, $instance = NULL, $entity_type = NULL, $entity = NULL) {
$allowed_values = &drupal_static(__FUNCTION__, array());
if (!isset($allowed_values[$field['id']])) {
$function = $field['settings']['allowed_values_function'];
+ // If $cacheable is FALSE, then the allowed values are not statically
+ // cached. See list_test_dynamic_values_callback() for an example of
+ // generating dynamic and uncached values.
+ $cacheable = TRUE;
if (!empty($function) && function_exists($function)) {
- $values = $function($field);
+ $values = $function($field, $instance, $entity_type, $entity, $cacheable);
}
else {
$values = $field['settings']['allowed_values'];
}
- $allowed_values[$field['id']] = $values;
+ if ($cacheable) {
+ $allowed_values[$field['id']] = $values;
+ }
+ else {
+ return $values;
+ }
}
return $allowed_values[$field['id']];
@@ -345,7 +360,7 @@ function list_field_update_forbid($field, $prior_field, $has_data) {
// Forbid any update that removes allowed values with actual data.
$lost_keys = array_diff(array_keys($prior_field['settings']['allowed_values']), array_keys($field['settings']['allowed_values']));
if (_list_values_in_use($field, $lost_keys)) {
- throw new FieldUpdateForbiddenException(t('Cannot update a list field to not include keys with existing data.'));
+ throw new FieldUpdateForbiddenException(t('A list field (@field_name) with existing data cannot have its keys changed.', array('@field_name' => $field['field_name'])));
}
}
}
@@ -373,7 +388,7 @@ function _list_values_in_use($field, $values) {
* - 'list_illegal_value': The value is not part of the list of allowed values.
*/
function list_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
- $allowed_values = list_allowed_values($field);
+ $allowed_values = list_allowed_values($field, $instance, $entity_type, $entity);
foreach ($items as $delta => $item) {
if (!empty($item['value'])) {
if (!empty($allowed_values) && !isset($allowed_values[$item['value']])) {
@@ -419,8 +434,8 @@ function list_field_widget_info_alter(&$info) {
/**
* Implements hook_options_list().
*/
-function list_options_list($field) {
- return list_allowed_values($field);
+function list_options_list($field, $instance, $entity_type, $entity) {
+ return list_allowed_values($field, $instance, $entity_type, $entity);
}
/**
@@ -447,7 +462,7 @@ function list_field_formatter_view($entity_type, $entity, $field, $instance, $la
switch ($display['type']) {
case 'list_default':
- $allowed_values = list_allowed_values($field);
+ $allowed_values = list_allowed_values($field, $instance, $entity_type, $entity);
foreach ($items as $delta => $item) {
if (isset($allowed_values[$item['value']])) {
$output = field_filter_xss($allowed_values[$item['value']]);
diff --git a/modules/field/modules/list/tests/list.test b/modules/field/modules/list/tests/list.test
index 765901a84..7a0f46c6d 100644
--- a/modules/field/modules/list/tests/list.test
+++ b/modules/field/modules/list/tests/list.test
@@ -114,6 +114,89 @@ class ListFieldTestCase extends FieldTestCase {
}
/**
+ * Sets up a List field for testing allowed values functions.
+ */
+class ListDynamicValuesTestCase extends FieldTestCase {
+ function setUp() {
+ parent::setUp(array('list', 'field_test', 'list_test'));
+
+ $this->field_name = 'test_list';
+ $this->field = array(
+ 'field_name' => $this->field_name,
+ 'type' => 'list_text',
+ 'cardinality' => 1,
+ 'settings' => array(
+ 'allowed_values_function' => 'list_test_dynamic_values_callback',
+ ),
+ );
+ $this->field = field_create_field($this->field);
+
+ $this->instance = array(
+ 'field_name' => $this->field_name,
+ 'entity_type' => 'test_entity',
+ 'bundle' => 'test_bundle',
+ 'required' => TRUE,
+ 'widget' => array(
+ 'type' => 'options_select',
+ ),
+ );
+ $this->instance = field_create_instance($this->instance);
+ $this->test = array(
+ 'id' => mt_rand(1, 10),
+ // Make sure this does not equal the ID so that
+ // list_test_dynamic_values_callback() always returns 4 values.
+ 'vid' => mt_rand(20, 30),
+ 'bundle' => 'test_bundle',
+ 'label' => $this->randomName(),
+ );
+ $this->entity = call_user_func_array('field_test_create_stub_entity', $this->test);
+ }
+}
+
+/**
+ * Tests the List field allowed values function.
+ */
+class ListDynamicValuesValidationTestCase extends ListDynamicValuesTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'List field dynamic values',
+ 'description' => 'Test the List field allowed values function.',
+ 'group' => 'Field types',
+ );
+ }
+
+ /**
+ * Test that allowed values function gets the entity.
+ */
+ function testDynamicAllowedValues() {
+ // Verify that the test passes against every value we had.
+ foreach ($this->test as $key => $value) {
+ $this->entity->test_list[LANGUAGE_NONE][0]['value'] = $value;
+ try {
+ field_attach_validate('test_entity', $this->entity);
+ $this->pass("$key should pass");
+ }
+ catch (FieldValidationException $e) {
+ // This will display as an exception, no need for a separate error.
+ throw($e);
+ }
+ }
+ // Now verify that the test does not pass against anything else.
+ foreach ($this->test as $key => $value) {
+ $this->entity->test_list[LANGUAGE_NONE][0]['value'] = is_numeric($value) ? (100 - $value) : ('X' . $value);
+ $pass = FALSE;
+ try {
+ field_attach_validate('test_entity', $this->entity);
+ }
+ catch (FieldValidationException $e) {
+ $pass = TRUE;
+ }
+ $this->assertTrue($pass, $key . ' should not pass');
+ }
+ }
+}
+
+/**
* List module UI tests.
*/
class ListFieldUITestCase extends FieldTestCase {
diff --git a/modules/field/modules/list/tests/list_test.module b/modules/field/modules/list/tests/list_test.module
index 8d5340412..aa5333779 100644
--- a/modules/field/modules/list/tests/list_test.module
+++ b/modules/field/modules/list/tests/list_test.module
@@ -21,3 +21,12 @@ function list_test_allowed_values_callback($field) {
return $values;
}
+
+/**
+ * An entity-bound allowed values callback.
+ */
+function list_test_dynamic_values_callback($field, $instance, $entity_type, $entity, &$cacheable) {
+ $cacheable = FALSE;
+ // We need the values of the entity as keys.
+ return drupal_map_assoc(array_merge(array($entity->ftlabel), entity_extract_ids($entity_type, $entity)));
+}
diff --git a/modules/field/modules/number/number.module b/modules/field/modules/number/number.module
index ad5514570..60465442f 100644
--- a/modules/field/modules/number/number.module
+++ b/modules/field/modules/number/number.module
@@ -377,12 +377,12 @@ function number_field_widget_validate($element, &$form_state) {
switch ($type) {
case 'float':
case 'decimal':
- $regexp = '@[^-0-9\\' . $field['settings']['decimal_separator'] . ']@';
+ $regexp = '@([^-0-9\\' . $field['settings']['decimal_separator'] . '])|(.-)@';
$message = t('Only numbers and the decimal separator (@separator) allowed in %field.', array('%field' => $instance['label'], '@separator' => $field['settings']['decimal_separator']));
break;
case 'integer':
- $regexp = '@[^-0-9]@';
+ $regexp = '@([^-0-9])|(.-)@';
$message = t('Only numbers are allowed in %field.', array('%field' => $instance['label']));
break;
}
diff --git a/modules/field/modules/number/number.test b/modules/field/modules/number/number.test
index e96be42a7..e5e7e8c71 100644
--- a/modules/field/modules/number/number.test
+++ b/modules/field/modules/number/number.test
@@ -92,6 +92,28 @@ class NumberFieldTestCase extends DrupalWebTestCase {
t('Correctly failed to save decimal value with more than one decimal point.')
);
}
+
+ // Try to create entries with minus sign not in the first position.
+ $wrong_entries = array(
+ '3-3',
+ '4-',
+ '1.3-',
+ '1.2-4',
+ '-10-10',
+ );
+
+ foreach ($wrong_entries as $wrong_entry) {
+ $this->drupalGet('test-entity/add/test-bundle');
+ $edit = array(
+ "{$this->field['field_name']}[$langcode][0][value]" => $wrong_entry,
+ );
+ $this->drupalPost(NULL, $edit, t('Save'));
+ $this->assertText(
+ t('Only numbers and the decimal separator (@separator) allowed in ',
+ array('@separator' => $this->field['settings']['decimal_separator'])),
+ 'Correctly failed to save decimal value with minus sign in the wrong position.'
+ );
+ }
}
/**
diff --git a/modules/field/modules/options/options.api.php b/modules/field/modules/options/options.api.php
index 68374446e..86f2b129b 100644
--- a/modules/field/modules/options/options.api.php
+++ b/modules/field/modules/options/options.api.php
@@ -20,6 +20,11 @@
* $instance parameter in contexts where no specific instance can be targeted.
* It is recommended to only use instance level properties to filter out
* values from a list defined by field level properties.
+ * @param $entity_type
+ * The entity type the field is attached to.
+ * @param $entity
+ * The entity object the field is attached to, or NULL if no entity
+ * exists (e.g. in field settings page).
*
* @return
* The array of options for the field. Array keys are the values to be
@@ -30,7 +35,7 @@
* widget. The HTML tags defined in _field_filter_xss_allowed_tags() are
* allowed, other tags will be filtered.
*/
-function hook_options_list($field, $instance = NULL) {
+function hook_options_list($field, $instance, $entity_type, $entity) {
// Sample structure.
$options = array(
0 => t('Zero'),
diff --git a/modules/field/modules/options/options.module b/modules/field/modules/options/options.module
index d4d05eca2..3862ba778 100644
--- a/modules/field/modules/options/options.module
+++ b/modules/field/modules/options/options.module
@@ -79,8 +79,11 @@ function options_field_widget_form(&$form, &$form_state, $field, $instance, $lan
$has_value = isset($items[0][$value_key]);
$properties = _options_properties($type, $multiple, $required, $has_value);
+ $entity_type = $element['#entity_type'];
+ $entity = $element['#entity'];
+
// Prepare the list of options.
- $options = _options_get_options($field, $instance, $properties);
+ $options = _options_get_options($field, $instance, $properties, $entity_type, $entity);
// Put current field values in shape.
$default_value = _options_storage_to_form($items, $options, $value_key, $properties);
@@ -102,10 +105,18 @@ function options_field_widget_form(&$form, &$form_state, $field, $instance, $lan
reset($options);
$default_value = array(key($options));
}
+
+ // If this is a single-value field, take the first default value, or
+ // default to NULL so that the form element is properly recognized as
+ // not having a default value.
+ if (!$multiple) {
+ $default_value = $default_value ? reset($default_value) : NULL;
+ }
+
$element += array(
'#type' => $multiple ? 'checkboxes' : 'radios',
// Radio buttons need a scalar value.
- '#default_value' => $multiple ? $default_value : reset($default_value),
+ '#default_value' => $default_value,
'#options' => $options,
);
break;
@@ -200,7 +211,7 @@ function _options_properties($type, $multiple, $required, $has_value) {
if (!$required) {
$properties['empty_option'] = 'option_none';
}
- else if (!$has_value) {
+ elseif (!$has_value) {
$properties['empty_option'] = 'option_select';
}
}
@@ -229,9 +240,9 @@ function _options_properties($type, $multiple, $required, $has_value) {
/**
* Collects the options for a field.
*/
-function _options_get_options($field, $instance, $properties) {
+function _options_get_options($field, $instance, $properties, $entity_type, $entity) {
// Get the list of options.
- $options = (array) module_invoke($field['module'], 'options_list', $field, $instance);
+ $options = (array) module_invoke($field['module'], 'options_list', $field, $instance, $entity_type, $entity);
// Sanitize the options.
_options_prepare_options($options, $properties);
diff --git a/modules/field/modules/options/options.test b/modules/field/modules/options/options.test
index ea58f27ff..69d6116ee 100644
--- a/modules/field/modules/options/options.test
+++ b/modules/field/modules/options/options.test
@@ -516,3 +516,38 @@ class OptionsWidgetsTestCase extends FieldTestCase {
}
}
+/**
+ * Test an options select on a list field with a dynamic allowed values function.
+ */
+class OptionsSelectDynamicValuesTestCase extends ListDynamicValuesTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Options select dynamic values',
+ 'description' => 'Test an options select on a list field with a dynamic allowed values function.',
+ 'group' => 'Field types',
+ );
+ }
+
+ /**
+ * Tests the 'options_select' widget (single select).
+ */
+ function testSelectListDynamic() {
+ // Create an entity.
+ $this->entity->is_new = TRUE;
+ field_test_entity_save($this->entity);
+ // Create a web user.
+ $web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content'));
+ $this->drupalLogin($web_user);
+
+ // Display form.
+ $this->drupalGet('test-entity/manage/' . $this->entity->ftid . '/edit');
+ $options = $this->xpath('//select[@id="edit-test-list-und"]/option');
+ $this->assertEqual(count($options), count($this->test) + 1);
+ foreach ($options as $option) {
+ $value = (string) $option['value'];
+ if ($value != '_none') {
+ $this->assertTrue(array_search($value, $this->test));
+ }
+ }
+ }
+}
diff --git a/modules/field/tests/field.test b/modules/field/tests/field.test
index 657f1f364..f7d9dddb2 100644
--- a/modules/field/tests/field.test
+++ b/modules/field/tests/field.test
@@ -1079,11 +1079,14 @@ class FieldInfoTestCase extends FieldTestCase {
}
// Verify that no unexpected instances exist.
- $core_fields = field_info_fields();
+ $instances = field_info_instances('test_entity');
+ $expected = array('test_bundle' => array());
+ $this->assertIdentical($instances, $expected, "field_info_instances('test_entity') returns " . var_export($expected, TRUE) . '.');
$instances = field_info_instances('test_entity', 'test_bundle');
- $this->assertTrue(empty($instances), t('With no instances, info bundles is empty.'));
+ $this->assertIdentical($instances, array(), "field_info_instances('test_entity', 'test_bundle') returns an empty array.");
// Create a field, verify it shows up.
+ $core_fields = field_info_fields();
$field = array(
'field_name' => drupal_strtolower($this->randomName()),
'type' => 'test_field',
@@ -1119,6 +1122,25 @@ class FieldInfoTestCase extends FieldTestCase {
$instances = field_info_instances('test_entity', $instance['bundle']);
$this->assertEqual(count($instances), 1, t('One instance shows up in info when attached to a bundle.'));
$this->assertTrue($instance < $instances[$instance['field_name']], t('Instance appears in info correctly'));
+
+ // Test a valid entity type but an invalid bundle.
+ $instances = field_info_instances('test_entity', 'invalid_bundle');
+ $this->assertIdentical($instances, array(), "field_info_instances('test_entity', 'invalid_bundle') returns an empty array.");
+
+ // Test invalid entity type and bundle.
+ $instances = field_info_instances('invalid_entity', $instance['bundle']);
+ $this->assertIdentical($instances, array(), "field_info_instances('invalid_entity', 'test_bundle') returns an empty array.");
+
+ // Test invalid entity type, no bundle provided.
+ $instances = field_info_instances('invalid_entity');
+ $this->assertIdentical($instances, array(), "field_info_instances('invalid_entity') returns an empty array.");
+
+ // Test with an entity type that has no bundles.
+ $instances = field_info_instances('user');
+ $expected = array('user' => array());
+ $this->assertIdentical($instances, $expected, "field_info_instances('user') returns " . var_export($expected, TRUE) . '.');
+ $instances = field_info_instances('user', 'user');
+ $this->assertIdentical($instances, array(), "field_info_instances('user', 'user') returns an empty array.");
}
/**
@@ -1460,6 +1482,51 @@ class FieldFormTestCase extends FieldTestCase {
// Test with several multiple fields in a form
}
+ /**
+ * Tests widget handling of multiple required radios.
+ */
+ function testFieldFormMultivalueWithRequiredRadio() {
+ // Create a multivalue test field.
+ $this->field = $this->field_unlimited;
+ $this->field_name = $this->field['field_name'];
+ $this->instance['field_name'] = $this->field_name;
+ field_create_field($this->field);
+ field_create_instance($this->instance);
+ $langcode = LANGUAGE_NONE;
+
+ // Add a required radio field.
+ field_create_field(array(
+ 'field_name' => 'required_radio_test',
+ 'type' => 'list_text',
+ 'settings' => array(
+ 'allowed_values' => array('yes' => 'yes', 'no' => 'no'),
+ ),
+ ));
+ field_create_instance(array(
+ 'field_name' => 'required_radio_test',
+ 'entity_type' => 'test_entity',
+ 'bundle' => 'test_bundle',
+ 'required' => TRUE,
+ 'widget' => array(
+ 'type' => 'options_buttons',
+ ),
+ ));
+
+ // Display creation form.
+ $this->drupalGet('test-entity/add/test-bundle');
+
+ // Press the 'Add more' button.
+ $this->drupalPost(NULL, array(), t('Add another item'));
+
+ // Verify that no error is thrown by the radio element.
+ $this->assertNoFieldByXpath('//div[contains(@class, "error")]', FALSE, 'No error message is displayed.');
+
+ // Verify that the widget is added.
+ $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", '', 'Widget 1 is displayed');
+ $this->assertFieldByName("{$this->field_name}[$langcode][1][value]", '', 'New widget is displayed');
+ $this->assertNoField("{$this->field_name}[$langcode][2][value]", 'No extraneous widget is displayed');
+ }
+
function testFieldFormJSAddMore() {
$this->field = $this->field_unlimited;
$this->field_name = $this->field['field_name'];
@@ -2357,7 +2424,6 @@ class FieldCrudTestCase extends FieldTestCase {
$this->assertTrue($field_definition <= $field, t('The field was properly read.'));
module_disable($modules, FALSE);
- drupal_flush_all_caches();
$fields = field_read_fields(array('field_name' => $field_name), array('include_inactive' => TRUE));
$this->assertTrue(isset($fields[$field_name]) && $field_definition < $field, t('The field is properly read when explicitly fetching inactive fields.'));
@@ -2370,7 +2436,6 @@ class FieldCrudTestCase extends FieldTestCase {
$module = array_shift($modules);
module_enable(array($module), FALSE);
- drupal_flush_all_caches();
}
// Check that the field is active again after all modules have been
diff --git a/modules/field/theme/field.tpl.php b/modules/field/theme/field.tpl.php
index 9e76e3b9c..a6d7a9659 100644
--- a/modules/field/theme/field.tpl.php
+++ b/modules/field/theme/field.tpl.php
@@ -48,7 +48,7 @@ See http://api.drupal.org/api/function/theme_field/7 for details.
After copying this file to your theme's folder and customizing it, remove this
HTML comment.
-->
-<div class="<?php print $classes; ?> clearfix"<?php print $attributes; ?>>
+<div class="<?php print $classes; ?>"<?php print $attributes; ?>>
<?php if (!$label_hidden): ?>
<div class="field-label"<?php print $title_attributes; ?>><?php print $label ?>:&nbsp;</div>
<?php endif; ?>