summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAngie Byron <webchick@24967.no-reply.drupal.org>2009-02-05 03:42:58 +0000
committerAngie Byron <webchick@24967.no-reply.drupal.org>2009-02-05 03:42:58 +0000
commitf3ed3283db182dfbbf35f5db077ebce2bc368bfe (patch)
treef2581841cbca28f3aebce26ee331ac3c72dd20de
parent8bac2dd319a42cade43b06218cbef31bf8f4867d (diff)
downloadbrdo-f3ed3283db182dfbbf35f5db077ebce2bc368bfe.tar.gz
brdo-f3ed3283db182dfbbf35f5db077ebce2bc368bfe.tar.bz2
#361683 follow-up by sun: Remove more windows line endings.
-rw-r--r--includes/theme.inc8
-rw-r--r--modules/field/api.field.php1530
-rw-r--r--modules/field/field.attach.inc1264
-rw-r--r--modules/field/field.crud.inc1114
-rw-r--r--modules/field/field.form.inc817
-rw-r--r--modules/field/field.test2408
-rw-r--r--modules/field/modules/field_sql_storage/field_sql_storage.module774
-rw-r--r--modules/field/modules/list/list.info1
-rw-r--r--modules/field/modules/list/list.module376
-rw-r--r--modules/field/modules/number/number.info1
-rw-r--r--modules/field/modules/number/number.module848
-rw-r--r--modules/field/modules/options/options.module842
-rw-r--r--modules/field/modules/text/text.info1
-rw-r--r--modules/field/modules/text/text.module798
-rw-r--r--modules/field/sample_code.php206
-rw-r--r--modules/field/theme/field.css4
-rw-r--r--modules/node/node.pages.inc16
-rw-r--r--modules/node/node.tpl.php2
-rw-r--r--modules/simpletest/tests/field_test.install94
-rw-r--r--modules/simpletest/tests/field_test.module676
-rw-r--r--modules/user/user-profile.tpl.php2
21 files changed, 5891 insertions, 5891 deletions
diff --git a/includes/theme.inc b/includes/theme.inc
index 606beebf1..1b5d03afd 100644
--- a/includes/theme.inc
+++ b/includes/theme.inc
@@ -2006,10 +2006,10 @@ function template_preprocess_node(&$variables) {
}
// Clean up name so there are no underscores.
$variables['template_files'][] = 'node-' . str_replace('_', '-', $node->type);
- $variables['template_files'][] = 'node-' . $node->nid;
-
- // Add $FIELD_NAME_rendered variables for fields.
- drupal_function_exists('field_attach_preprocess');
+ $variables['template_files'][] = 'node-' . $node->nid;
+
+ // Add $FIELD_NAME_rendered variables for fields.
+ drupal_function_exists('field_attach_preprocess');
$variables += field_attach_preprocess('node', $node);
}
diff --git a/modules/field/api.field.php b/modules/field/api.field.php
index a681d6778..29381c0b4 100644
--- a/modules/field/api.field.php
+++ b/modules/field/api.field.php
@@ -1,765 +1,765 @@
-<?php
-// $Id$
-
-/**
- * @ingroup field_fieldable_type
- * @{
- */
-
-/**
- * Inform the Field API about one or more fieldable types (object
- * types to which fields can be attached).
- *
- * @return
- * An array whose keys are fieldable object type names and
- * whose values identify properties of those types that the Field
- * system needs to know about:
- *
- * name: The human-readable name of the type.
- * id key: The object property that contains the primary id for the
- * object. Every object passed to the Field API must
- * have this property and its value must be numeric.
- * revision key: The object property that contains the revision id
- * for the object, or NULL if the object type is not
- * versioned. The Field API assumes that all revision ids are
- * unique across all instances of a type; this means, for example,
- * that every object's revision ids cannot be 0, 1, 2, ...
- * bundle key: The object property that contains the bundle name for
- * the object (bundle name is what nodes call "content type").
- * The bundle name defines which fields are connected to the object.
- * cacheable: A boolean indicating whether Field API should cache
- * loaded fields for each object, reducing the cost of
- * field_attach_load().
- * bundles: An array of all existing bundle names for this object
- * type. TODO: Define format. TODO: I'm unclear why we need
- * this.
- */
-function hook_fieldable_info() {
- $return = array(
- 'node' => array(
- 'name' => t('Node'),
- 'id key' => 'nid',
- 'revision key' => 'vid',
- 'bundle key' => 'type',
- // Node.module handles its own caching.
- 'cacheable' => FALSE,
- // Bundles must provide human readable name so
- // we can create help and error messages about them.
- 'bundles' => node_get_types('names'),
- ),
- );
- return $return;
-}
-
-/**
- * @} End of "ingroup field_fieldable_type"
- */
-
-/**
- * @defgroup field_types Field Types API
- * @{
- * Define field types, widget types, and display formatter types.
- *
- * The bulk of the Field Types API are related to field types. A
- * field type represents a particular data storage type (integer,
- * string, date, etc.) that can be attached to a fieldable object.
- * 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.
- *
- * The Field Types API also defines widget types via
- * hook_field_widget_info(). Widgets are Form API elements with
- * additional processing capabilities. A field module can define
- * widgets that work with its own field types or with any other
- * module's field types. Widget hooks are typically called by the
- * Field Attach API when creating the field form elements during
- * field_attach_form().
- *
- * TODO Display formatters.
- */
-
-/**
- * Define Field API field types.
- *
- * @return
- * An array whose keys are field type names and whose values are:
- *
- * label: TODO
- * description: TODO
- * settings: TODO
- * instance_settings: TODO
- * default_widget: TODO
- * default_formatter: TODO
- * behaviors: TODO
- */
-function hook_field_info() {
- return array(
- 'text' => array(
- 'label' => t('Text'),
- 'description' => t('This field stores varchar text in the database.'),
- 'settings' => array('max_length' => 255),
- 'instance_settings' => array('text_processing' => 0),
- 'default_widget' => 'text_textfield',
- 'default_formatter' => 'text_default',
- ),
- 'textarea' => array(
- 'label' => t('Textarea'),
- 'description' => t('This field stores long text in the database.'),
- 'instance_settings' => array('text_processing' => 0),
- 'default_widget' => 'text_textarea',
- 'default_formatter' => 'text_default',
- ),
- );
-}
-
-/**
- * Define the Field API schema for a field structure.
- *
- * @param $field
- * A field structure.
- * @return
- * A Field API schema is an array of Schema API column
- * specifications, keyed by field-independent column name. For
- * example, a field may declare a column named 'value'. The SQL
- * storage engine may create a table with a column named
- * <fieldname>_value_0, but the Field API schema column name is
- * still 'value'.
- */
-function hook_field_columns($field) {
- if ($field['type'] == 'textarea') {
- $columns = array(
- 'value' => array(
- 'type' => 'text',
- 'size' => 'big',
- 'not null' => FALSE,
- ),
- );
- }
- else {
- $columns = array(
- 'value' => array(
- 'type' => 'varchar',
- 'length' => $field['settings']['max_length'],
- 'not null' => FALSE,
- ),
- );
- }
- $columns += array(
- 'format' => array(
- 'type' => 'int',
- 'unsigned' => TRUE,
- 'not null' => FALSE,
- ),
- );
- return $columns;
-}
-
-/**
- * Define Field API widget types.
- *
- * @return
- * An array whose keys are field type names and whose values are:
- *
- * label: TODO
- * description: TODO
- * field types: TODO
- * settings: TODO
- * behaviors: TODO
- */
-function hook_field_widget_info() {
-}
-
-/*
- * Define Field API formatter types.
- *
- * @return
- * An array whose keys are field type names and whose values are:
- *
- * label: TODO
- * description: TODO
- * field types: TODO
- * behaviors: TODO
- */
-function hook_field_formatter_info() {
-}
-
-/**
- * Define custom load behavior for this module's field types.
- *
- * @param $obj_type
- * The type of $object.
- * @param $object
- * The object for the operation.
- * @param $field
- * The field structure for the operation.
- * @param $instance
- * The instance structure for $field on $object's bundle.
- * @param $items
- * $object->{$field['field_name']}, or an empty array if unset.
- */
-function hook_field_load($obj_type, $object, $field, $instance, $items) {
-}
-
-/**
- * Define custom validate behavior for this module's field types.
- *
- * @param $obj_type
- * The type of $object.
- * @param $object
- * The object for the operation.
- * @param $field
- * The field structure for the operation.
- * @param $instance
- * The instance structure for $field on $object's bundle.
- * @param $items
- * $object->{$field['field_name']}, or an empty array if unset.
- * @param $form
- * The form structure being validated. NOTE: This parameter will
- * become obsolete (see field_attach_validate()).
- */
-function hook_field_validate($obj_type, $object, $field, $instance, $items, $form) {
-}
-
-/**
- * Define custom presave behavior for this module's field types.
- * TODO: The behavior of this hook is going to change (see
- * field_attach_presave()).
- *
- * @param $obj_type
- * The type of $object.
- * @param $object
- * The object for the operation.
- * @param $field
- * The field structure for the operation.
- * @param $instance
- * The instance structure for $field on $object's bundle.
- * @param $items
- * $object->{$field['field_name']}, or an empty array if unset.
- */
-function hook_field_presave($obj_type, $object, $field, $instance, $items) {
-}
-
-/**
- * Define custom insert behavior for this module's field types.
- *
- * @param $obj_type
- * The type of $object.
- * @param $object
- * The object for the operation.
- * @param $field
- * The field structure for the operation.
- * @param $instance
- * The instance structure for $field on $object's bundle.
- * @param $items
- * $object->{$field['field_name']}, or an empty array if unset.
- */
-function hook_field_insert($obj_type, $object, $field, $instance, $items) {
-}
-
-/**
- * Define custom update behavior for this module's field types.
- *
- * @param $obj_type
- * The type of $object.
- * @param $object
- * The object for the operation.
- * @param $field
- * The field structure for the operation.
- * @param $instance
- * The instance structure for $field on $object's bundle.
- * @param $items
- * $object->{$field['field_name']}, or an empty array if unset.
- */
-function hook_field_update($obj_type, $object, $field, $instance, $items) {
-}
-
-/**
- * Define custom delete behavior for this module's field types. This
- * hook is invoked just before the data is deleted from field storage.
- *
- * @param $obj_type
- * The type of $object.
- * @param $object
- * The object for the operation.
- * @param $field
- * The field structure for the operation.
- * @param $instance
- * The instance structure for $field on $object's bundle.
- * @param $items
- * $object->{$field['field_name']}, or an empty array if unset.
- */
-function hook_field_delete($obj_type, $object, $field, $instance, $items) {
-}
-
-/**
- * Define custom delete_revision behavior for this module's field
- * types. This hook is invoked just before the data is deleted from
- * field storage, and will only be called for fieldable types that are
- * versioned.
- *
- * @param $obj_type
- * The type of $object.
- * @param $object
- * The object for the operation.
- * @param $field
- * The field structure for the operation.
- * @param $instance
- * The instance structure for $field on $object's bundle.
- * @param $items
- * $object->{$field['field_name']}, or an empty array if unset.
- */
-function hook_field_delete_revision($obj_type, $object, $field, $instance, $items) {
-}
-
-/**
- * Define custom sanitize behavior for this module's field types.
- *
- * @param $obj_type
- * The type of $object.
- * @param $object
- * The object for the operation.
- * @param $field
- * The field structure for the operation.
- * @param $instance
- * The instance structure for $field on $object's bundle.
- * @param $items
- * $object->{$field['field_name']}, or an empty array if unset.
- */
-function hook_field_sanitize($obj_type, $object, $field, $instance, $items) {
-}
-
-/**
- * Define custom prepare_translation behavior for this module's field
- * types. TODO: This hook may or may not survive in Field API.
- *
- * @param $obj_type
- * The type of $object.
- * @param $object
- * The object for the operation.
- * @param $field
- * The field structure for the operation.
- * @param $instance
- * The instance structure for $field on $object's bundle.
- * @param $items
- * $object->{$field['field_name']}, or an empty array if unset.
- */
-function hook_field_prepare_translation($obj_type, $object, $field, $instance, $items) {
-}
-
-/**
- * Return a single form element for a form.
- *
- * It will be built out and validated in the callback(s) listed in
- * hook_elements. We build it out in the callbacks rather than in
- * hook_field_widget so it can be plugged into any module that can
- * provide it with valid $field information.
- *
- * Field API will set the weight, field name and delta values for each
- * form element. If there are multiple values for this field, the
- * Field API will call this function as many times as needed.
- *
- * @param $form
- * The entire form array, $form['#node'] holds node information.
- * TODO: Not #node any more.
- * @param $form_state
- * The form_state, $form_state['values'][$field['field_name']]
- * holds the field's form values.
- * @param $field
- * The field structure.
- * @param $instance
- * The field instance.
- * @param $items
- * Array of default values for this field.
- * @param $delta
- * The order of this item in the array of subelements (0, 1, 2, etc).
- * @return
- * The form item for a single element for this field.
- */
-function hook_field_widget(&$form, &$form_state, $field, $instance, $items, $delta = 0) {
- $element = array(
- '#type' => $instance['widget']['type'],
- '#default_value' => isset($items[$delta]) ? $items[$delta] : '',
- );
- return $element;
-}
-
-/**
- * @} End of "ingroup field_type"
- */
-
-/**
- * @ingroup field_attach
- * @{
- */
-
-/**
- * Act on field_attach_form. This hook is invoked after the field module
- * has performed the operation.
- *
- * See field_attach_form() for details and arguments.
- */
-function hook_field_attach_form($obj_type, $object, &$form, &$form_state) {
-}
-
-/**
- * Act on field_attach_load. This hook is invoked after the field module
- * has performed the operation.
- *
- * See field_attach_load() for details and arguments. TODO:
- * Currently, this hook only accepts a single object a time.
- */
-function hook_field_attach_load($obj_type, $object) {
-}
-
-/**
- * Act on field_attach_validate. This hook is invoked after the field module
- * has performed the operation.
- *
- * See field_attach_validate() for details and arguments.
- */
-function hook_field_attach_validate($obj_type, $object, &$form) {
-}
-
-/**
- * Act on field_attach_submit. This hook is invoked after the field module
- * has performed the operation.
- *
- * See field_attach_submit() for details and arguments.
- */
-function hook_field_attach_submit($obj_type, $object, $form, &$form_state) {
-}
-
-/**
- * Act on field_attach_presave. This hook is invoked after the field module
- * has performed the operation.
- *
- * See field_attach_presave() for details and arguments.
- */
-function hook_field_attach_presave($obj_type, $object) {
-}
-
-/**
- * Act on field_attach_insert. This hook is invoked after the field module
- * has performed the operation.
- *
- * See field_attach_insert() for details and arguments.
- */
-function hook_field_attach_insert($obj_type, $object) {
-}
-
-/**
- * Act on field_attach_update. This hook is invoked after the field module
- * has performed the operation.
- *
- * See field_attach_update() for details and arguments.
- */
-function hook_field_attach_update($obj_type, $object) {
-}
-
-/**
- * Act on field_attach_delete. This hook is invoked after the field module
- * has performed the operation.
- *
- * See field_attach_delete() for details and arguments.
- */
-function hook_field_attach_delete($obj_type, $object) {
-}
-
-/**
- * Act on field_attach_delete_revision. This hook is invoked after
- * the field module has performed the operation.
- *
- * See field_attach_delete_revision() for details and arguments.
- */
-function hook_field_attach_delete_revision($obj_type, $object) {
-}
-
-/**
- * Act on field_attach_view. This hook is invoked after the field module
- * has performed the operation.
- *
- * @param $output
- * The structured content array tree for all of $object's fields.
- * @param $obj_type
- * The type of $object; e.g. 'node' or 'user'.
- * @param $object
- * The object with fields to render.
- * @param $teaser
- * Whether to display the teaser only, as on the main page.
- */
-function hook_field_attach_view($output, $obj_type, $object, $teaser) {
-}
-
-/**
- * Act on field_attach_create_bundle. This hook is invoked after the
- * field module has performed the operation.
- *
- * See field_attach_create_bundle() for details and arguments.
- */
-function hook_field_attach_create_bundle($bundle) {
-}
-
-/**
- * Act on field_attach_rename_bundle. This hook is invoked after the
- * field module has performed the operation.
- *
- * See field_attach_rename_bundle() for details and arguments.
- */
-function hook_field_rename_bundle($bundle_old, $bundle_new) {
-}
-
-/**
- * Act on field_attach_delete_bundle. This hook is invoked after the field module
- * has performed the operation.
- *
- * See field_attach_delete_bundle() for details and arguments.
- */
-function hook_field_attach_delete_bundle($bundle) {
-}
-
-/**
- * @} End of "ingroup field_attach"
- */
-
-/**********************************************************************
- * Field Storage API
- **********************************************************************/
-
-/**
- * @ingroup field_storage
- * @{
- */
-
-/**
- * Load field data for a set of objects.
- *
- * @param $obj_type
- * The entity type of objects being loaded, such as 'node' or
- * 'user'.
- * @param $objects
- * The array of objects for which to load data.
- * @param $age
- * FIELD_LOAD_CURRENT to load the most recent revision for all
- * fields, or FIELD_LOAD_REVISION to load the version indicated by
- * each object.
- * @return
- * An array of field data for the objects, keyed by entity id, field
- * name, and item delta number.
- */
-function hook_field_storage_load($obj_type, $queried_objs, $age) {
-}
-
-/**
- * Write field data for an object.
- *
- * @param $obj_type
- * The entity type of object, such as 'node' or 'user'.
- * @param $object
- * The object on which to operate.
- * @param $update
- * TRUE if this is an update to an existing object, FALSE if it is
- * an insert of a new object.
- */
-function hook_field_storage_write($obj_type, $object, TRUE) {
-}
-
-/**
- * Delete all field data for an object.
- *
- * @param $obj_type
- * The entity type of object, such as 'node' or 'user'.
- * @param $object
- * The object on which to operate.
- */
-function hook_field_storage_delete($obj_type, $object) {
-}
-
-/**
- * Delete a single revision of field data for an object.
- *
- * @param $obj_type
- * The entity type of object, such as 'node' or 'user'.
- * @param $object
- * The object on which to operate. The revision to delete is
- * indicated by the object's revision id property, as identified by
- * hook_fieldable_info() for $obj_type.
- */
-function hook_field_storage_delete_revision($obj_type, $object) {
-}
-
-/**
- * Act on creation of a new bundle.
- *
- * @param $bundle
- * The name of the bundle being created.
- */
-function hook_field_storage_create_bundle($bundle) {
-}
-
-/**
- * Act on a bundle being renamed.
- *
- * @param $bundle_old
- * The old name of the bundle.
- * @param $bundle_new
- * The new name of the bundle.
- */
-function hook_field_storage_rename_bundle($bundle_old, $bundle_new) {
-}
-
-/**
- * Act on creation of a new field.
- *
- * @param $field
- * The field structure being created.
- */
-function hook_field_storage_create_field($field) {
-}
-
-/**
- * Act on deletion of a field.
- *
- * @param $field_name
- * The name of the field being deleted.
- */
-function hook_field_storage_delete_field($field_name) {
-}
-
-/**
- * Act on deletion of a field instance.
- *
- * @param $field_name
- * The name of the field in the new instance.
- * @param $bundle
- * The name of the bundle in the new instance.
- */
-function hook_field_storage_delete_instance($field_name, $bundle) {
-}
-
-/**
- * @} End of "ingroup field_storage"
- */
-
-/**********************************************************************
- * Field CRUD API
- **********************************************************************/
-
-/**
- * @ingroup field_crud
- * @{
- */
-
-/**
- * Act on a field being created. This hook is invoked after the field
- * is created and so it cannot modify the field itself.
- *
- * TODO: Not implemented.
- *
- * @param $field
- * The field just created.
- */
-function hook_field_create_field($field) {
-}
-
-/**
- * Act on a field instance being created. This hook is invoked after
- * the instance record is saved and so it cannot modify the instance
- * itself.
- *
- * @param $instance
- * The instance just created.
- */
-function hook_field_create_instance($instance) {
-}
-
-/**
- * Act on a field being deleted. This hook is invoked just before the
- * field is deleted.
- *
- * TODO: Not implemented.
- *
- * @param $field
- * The field being deleted.
- */
-function hook_field_delete_field($field) {
-}
-
-
-/**
- * Act on a field instance being updated. This hook is invoked after
- * the instance record is saved and so it cannot modify the instance
- * itself.
- *
- * TODO: Not implemented.
- *
- * @param $instance
- * The instance just updated.
- */
-function hook_field_update_instance($instance) {
-}
-
-/**
- * Act on a field instance being deleted. This hook is invoked just
- * before the instance is deleted.
- *
- * TODO: Not implemented.
- *
- * @param $instance
- * The instance just updated.
- */
-function hook_field_delete_instance($instance) {
-}
-
-/**
- * Act on field records being read from the database.
- *
- * @param $field
- * The field record just read from the database.
- */
-function hook_field_read_field($field) {
-}
-
-/**
- * Act on a field record being read from the database.
- *
- * @param $instance
- * The instance record just read from the database.
- */
-function hook_field_read_instance($instance) {
-}
-
-/**
- * @} End of "ingroup field_crud"
- */
-
-/**********************************************************************
- * TODO: I'm not sure where these belong yet.
- **********************************************************************/
-
-/**
- * TODO
- *
- * Note : Right now this belongs to the "Fieldable Type API".
- * Whether 'build modes' is actually a 'fields' concept is to be debated
- * in a separate overhaul patch for core.
- */
-function hook_field_build_modes($obj_type) {
-}
-
-/**
- * Determine whether the user has access to a given field.
- *
- * @param $op
- * The operation to be performed. Possible values:
- * - "edit"
- * - "view"
- * @param $field
- * The field on which the operation is to be performed.
- * @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.
- */
-function hook_field_access($op, $field, $account) {
-}
+<?php
+// $Id$
+
+/**
+ * @ingroup field_fieldable_type
+ * @{
+ */
+
+/**
+ * Inform the Field API about one or more fieldable types (object
+ * types to which fields can be attached).
+ *
+ * @return
+ * An array whose keys are fieldable object type names and
+ * whose values identify properties of those types that the Field
+ * system needs to know about:
+ *
+ * name: The human-readable name of the type.
+ * id key: The object property that contains the primary id for the
+ * object. Every object passed to the Field API must
+ * have this property and its value must be numeric.
+ * revision key: The object property that contains the revision id
+ * for the object, or NULL if the object type is not
+ * versioned. The Field API assumes that all revision ids are
+ * unique across all instances of a type; this means, for example,
+ * that every object's revision ids cannot be 0, 1, 2, ...
+ * bundle key: The object property that contains the bundle name for
+ * the object (bundle name is what nodes call "content type").
+ * The bundle name defines which fields are connected to the object.
+ * cacheable: A boolean indicating whether Field API should cache
+ * loaded fields for each object, reducing the cost of
+ * field_attach_load().
+ * bundles: An array of all existing bundle names for this object
+ * type. TODO: Define format. TODO: I'm unclear why we need
+ * this.
+ */
+function hook_fieldable_info() {
+ $return = array(
+ 'node' => array(
+ 'name' => t('Node'),
+ 'id key' => 'nid',
+ 'revision key' => 'vid',
+ 'bundle key' => 'type',
+ // Node.module handles its own caching.
+ 'cacheable' => FALSE,
+ // Bundles must provide human readable name so
+ // we can create help and error messages about them.
+ 'bundles' => node_get_types('names'),
+ ),
+ );
+ return $return;
+}
+
+/**
+ * @} End of "ingroup field_fieldable_type"
+ */
+
+/**
+ * @defgroup field_types Field Types API
+ * @{
+ * Define field types, widget types, and display formatter types.
+ *
+ * The bulk of the Field Types API are related to field types. A
+ * field type represents a particular data storage type (integer,
+ * string, date, etc.) that can be attached to a fieldable object.
+ * 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.
+ *
+ * The Field Types API also defines widget types via
+ * hook_field_widget_info(). Widgets are Form API elements with
+ * additional processing capabilities. A field module can define
+ * widgets that work with its own field types or with any other
+ * module's field types. Widget hooks are typically called by the
+ * Field Attach API when creating the field form elements during
+ * field_attach_form().
+ *
+ * TODO Display formatters.
+ */
+
+/**
+ * Define Field API field types.
+ *
+ * @return
+ * An array whose keys are field type names and whose values are:
+ *
+ * label: TODO
+ * description: TODO
+ * settings: TODO
+ * instance_settings: TODO
+ * default_widget: TODO
+ * default_formatter: TODO
+ * behaviors: TODO
+ */
+function hook_field_info() {
+ return array(
+ 'text' => array(
+ 'label' => t('Text'),
+ 'description' => t('This field stores varchar text in the database.'),
+ 'settings' => array('max_length' => 255),
+ 'instance_settings' => array('text_processing' => 0),
+ 'default_widget' => 'text_textfield',
+ 'default_formatter' => 'text_default',
+ ),
+ 'textarea' => array(
+ 'label' => t('Textarea'),
+ 'description' => t('This field stores long text in the database.'),
+ 'instance_settings' => array('text_processing' => 0),
+ 'default_widget' => 'text_textarea',
+ 'default_formatter' => 'text_default',
+ ),
+ );
+}
+
+/**
+ * Define the Field API schema for a field structure.
+ *
+ * @param $field
+ * A field structure.
+ * @return
+ * A Field API schema is an array of Schema API column
+ * specifications, keyed by field-independent column name. For
+ * example, a field may declare a column named 'value'. The SQL
+ * storage engine may create a table with a column named
+ * <fieldname>_value_0, but the Field API schema column name is
+ * still 'value'.
+ */
+function hook_field_columns($field) {
+ if ($field['type'] == 'textarea') {
+ $columns = array(
+ 'value' => array(
+ 'type' => 'text',
+ 'size' => 'big',
+ 'not null' => FALSE,
+ ),
+ );
+ }
+ else {
+ $columns = array(
+ 'value' => array(
+ 'type' => 'varchar',
+ 'length' => $field['settings']['max_length'],
+ 'not null' => FALSE,
+ ),
+ );
+ }
+ $columns += array(
+ 'format' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => FALSE,
+ ),
+ );
+ return $columns;
+}
+
+/**
+ * Define Field API widget types.
+ *
+ * @return
+ * An array whose keys are field type names and whose values are:
+ *
+ * label: TODO
+ * description: TODO
+ * field types: TODO
+ * settings: TODO
+ * behaviors: TODO
+ */
+function hook_field_widget_info() {
+}
+
+/*
+ * Define Field API formatter types.
+ *
+ * @return
+ * An array whose keys are field type names and whose values are:
+ *
+ * label: TODO
+ * description: TODO
+ * field types: TODO
+ * behaviors: TODO
+ */
+function hook_field_formatter_info() {
+}
+
+/**
+ * Define custom load behavior for this module's field types.
+ *
+ * @param $obj_type
+ * The type of $object.
+ * @param $object
+ * The object for the operation.
+ * @param $field
+ * The field structure for the operation.
+ * @param $instance
+ * The instance structure for $field on $object's bundle.
+ * @param $items
+ * $object->{$field['field_name']}, or an empty array if unset.
+ */
+function hook_field_load($obj_type, $object, $field, $instance, $items) {
+}
+
+/**
+ * Define custom validate behavior for this module's field types.
+ *
+ * @param $obj_type
+ * The type of $object.
+ * @param $object
+ * The object for the operation.
+ * @param $field
+ * The field structure for the operation.
+ * @param $instance
+ * The instance structure for $field on $object's bundle.
+ * @param $items
+ * $object->{$field['field_name']}, or an empty array if unset.
+ * @param $form
+ * The form structure being validated. NOTE: This parameter will
+ * become obsolete (see field_attach_validate()).
+ */
+function hook_field_validate($obj_type, $object, $field, $instance, $items, $form) {
+}
+
+/**
+ * Define custom presave behavior for this module's field types.
+ * TODO: The behavior of this hook is going to change (see
+ * field_attach_presave()).
+ *
+ * @param $obj_type
+ * The type of $object.
+ * @param $object
+ * The object for the operation.
+ * @param $field
+ * The field structure for the operation.
+ * @param $instance
+ * The instance structure for $field on $object's bundle.
+ * @param $items
+ * $object->{$field['field_name']}, or an empty array if unset.
+ */
+function hook_field_presave($obj_type, $object, $field, $instance, $items) {
+}
+
+/**
+ * Define custom insert behavior for this module's field types.
+ *
+ * @param $obj_type
+ * The type of $object.
+ * @param $object
+ * The object for the operation.
+ * @param $field
+ * The field structure for the operation.
+ * @param $instance
+ * The instance structure for $field on $object's bundle.
+ * @param $items
+ * $object->{$field['field_name']}, or an empty array if unset.
+ */
+function hook_field_insert($obj_type, $object, $field, $instance, $items) {
+}
+
+/**
+ * Define custom update behavior for this module's field types.
+ *
+ * @param $obj_type
+ * The type of $object.
+ * @param $object
+ * The object for the operation.
+ * @param $field
+ * The field structure for the operation.
+ * @param $instance
+ * The instance structure for $field on $object's bundle.
+ * @param $items
+ * $object->{$field['field_name']}, or an empty array if unset.
+ */
+function hook_field_update($obj_type, $object, $field, $instance, $items) {
+}
+
+/**
+ * Define custom delete behavior for this module's field types. This
+ * hook is invoked just before the data is deleted from field storage.
+ *
+ * @param $obj_type
+ * The type of $object.
+ * @param $object
+ * The object for the operation.
+ * @param $field
+ * The field structure for the operation.
+ * @param $instance
+ * The instance structure for $field on $object's bundle.
+ * @param $items
+ * $object->{$field['field_name']}, or an empty array if unset.
+ */
+function hook_field_delete($obj_type, $object, $field, $instance, $items) {
+}
+
+/**
+ * Define custom delete_revision behavior for this module's field
+ * types. This hook is invoked just before the data is deleted from
+ * field storage, and will only be called for fieldable types that are
+ * versioned.
+ *
+ * @param $obj_type
+ * The type of $object.
+ * @param $object
+ * The object for the operation.
+ * @param $field
+ * The field structure for the operation.
+ * @param $instance
+ * The instance structure for $field on $object's bundle.
+ * @param $items
+ * $object->{$field['field_name']}, or an empty array if unset.
+ */
+function hook_field_delete_revision($obj_type, $object, $field, $instance, $items) {
+}
+
+/**
+ * Define custom sanitize behavior for this module's field types.
+ *
+ * @param $obj_type
+ * The type of $object.
+ * @param $object
+ * The object for the operation.
+ * @param $field
+ * The field structure for the operation.
+ * @param $instance
+ * The instance structure for $field on $object's bundle.
+ * @param $items
+ * $object->{$field['field_name']}, or an empty array if unset.
+ */
+function hook_field_sanitize($obj_type, $object, $field, $instance, $items) {
+}
+
+/**
+ * Define custom prepare_translation behavior for this module's field
+ * types. TODO: This hook may or may not survive in Field API.
+ *
+ * @param $obj_type
+ * The type of $object.
+ * @param $object
+ * The object for the operation.
+ * @param $field
+ * The field structure for the operation.
+ * @param $instance
+ * The instance structure for $field on $object's bundle.
+ * @param $items
+ * $object->{$field['field_name']}, or an empty array if unset.
+ */
+function hook_field_prepare_translation($obj_type, $object, $field, $instance, $items) {
+}
+
+/**
+ * Return a single form element for a form.
+ *
+ * It will be built out and validated in the callback(s) listed in
+ * hook_elements. We build it out in the callbacks rather than in
+ * hook_field_widget so it can be plugged into any module that can
+ * provide it with valid $field information.
+ *
+ * Field API will set the weight, field name and delta values for each
+ * form element. If there are multiple values for this field, the
+ * Field API will call this function as many times as needed.
+ *
+ * @param $form
+ * The entire form array, $form['#node'] holds node information.
+ * TODO: Not #node any more.
+ * @param $form_state
+ * The form_state, $form_state['values'][$field['field_name']]
+ * holds the field's form values.
+ * @param $field
+ * The field structure.
+ * @param $instance
+ * The field instance.
+ * @param $items
+ * Array of default values for this field.
+ * @param $delta
+ * The order of this item in the array of subelements (0, 1, 2, etc).
+ * @return
+ * The form item for a single element for this field.
+ */
+function hook_field_widget(&$form, &$form_state, $field, $instance, $items, $delta = 0) {
+ $element = array(
+ '#type' => $instance['widget']['type'],
+ '#default_value' => isset($items[$delta]) ? $items[$delta] : '',
+ );
+ return $element;
+}
+
+/**
+ * @} End of "ingroup field_type"
+ */
+
+/**
+ * @ingroup field_attach
+ * @{
+ */
+
+/**
+ * Act on field_attach_form. This hook is invoked after the field module
+ * has performed the operation.
+ *
+ * See field_attach_form() for details and arguments.
+ */
+function hook_field_attach_form($obj_type, $object, &$form, &$form_state) {
+}
+
+/**
+ * Act on field_attach_load. This hook is invoked after the field module
+ * has performed the operation.
+ *
+ * See field_attach_load() for details and arguments. TODO:
+ * Currently, this hook only accepts a single object a time.
+ */
+function hook_field_attach_load($obj_type, $object) {
+}
+
+/**
+ * Act on field_attach_validate. This hook is invoked after the field module
+ * has performed the operation.
+ *
+ * See field_attach_validate() for details and arguments.
+ */
+function hook_field_attach_validate($obj_type, $object, &$form) {
+}
+
+/**
+ * Act on field_attach_submit. This hook is invoked after the field module
+ * has performed the operation.
+ *
+ * See field_attach_submit() for details and arguments.
+ */
+function hook_field_attach_submit($obj_type, $object, $form, &$form_state) {
+}
+
+/**
+ * Act on field_attach_presave. This hook is invoked after the field module
+ * has performed the operation.
+ *
+ * See field_attach_presave() for details and arguments.
+ */
+function hook_field_attach_presave($obj_type, $object) {
+}
+
+/**
+ * Act on field_attach_insert. This hook is invoked after the field module
+ * has performed the operation.
+ *
+ * See field_attach_insert() for details and arguments.
+ */
+function hook_field_attach_insert($obj_type, $object) {
+}
+
+/**
+ * Act on field_attach_update. This hook is invoked after the field module
+ * has performed the operation.
+ *
+ * See field_attach_update() for details and arguments.
+ */
+function hook_field_attach_update($obj_type, $object) {
+}
+
+/**
+ * Act on field_attach_delete. This hook is invoked after the field module
+ * has performed the operation.
+ *
+ * See field_attach_delete() for details and arguments.
+ */
+function hook_field_attach_delete($obj_type, $object) {
+}
+
+/**
+ * Act on field_attach_delete_revision. This hook is invoked after
+ * the field module has performed the operation.
+ *
+ * See field_attach_delete_revision() for details and arguments.
+ */
+function hook_field_attach_delete_revision($obj_type, $object) {
+}
+
+/**
+ * Act on field_attach_view. This hook is invoked after the field module
+ * has performed the operation.
+ *
+ * @param $output
+ * The structured content array tree for all of $object's fields.
+ * @param $obj_type
+ * The type of $object; e.g. 'node' or 'user'.
+ * @param $object
+ * The object with fields to render.
+ * @param $teaser
+ * Whether to display the teaser only, as on the main page.
+ */
+function hook_field_attach_view($output, $obj_type, $object, $teaser) {
+}
+
+/**
+ * Act on field_attach_create_bundle. This hook is invoked after the
+ * field module has performed the operation.
+ *
+ * See field_attach_create_bundle() for details and arguments.
+ */
+function hook_field_attach_create_bundle($bundle) {
+}
+
+/**
+ * Act on field_attach_rename_bundle. This hook is invoked after the
+ * field module has performed the operation.
+ *
+ * See field_attach_rename_bundle() for details and arguments.
+ */
+function hook_field_rename_bundle($bundle_old, $bundle_new) {
+}
+
+/**
+ * Act on field_attach_delete_bundle. This hook is invoked after the field module
+ * has performed the operation.
+ *
+ * See field_attach_delete_bundle() for details and arguments.
+ */
+function hook_field_attach_delete_bundle($bundle) {
+}
+
+/**
+ * @} End of "ingroup field_attach"
+ */
+
+/**********************************************************************
+ * Field Storage API
+ **********************************************************************/
+
+/**
+ * @ingroup field_storage
+ * @{
+ */
+
+/**
+ * Load field data for a set of objects.
+ *
+ * @param $obj_type
+ * The entity type of objects being loaded, such as 'node' or
+ * 'user'.
+ * @param $objects
+ * The array of objects for which to load data.
+ * @param $age
+ * FIELD_LOAD_CURRENT to load the most recent revision for all
+ * fields, or FIELD_LOAD_REVISION to load the version indicated by
+ * each object.
+ * @return
+ * An array of field data for the objects, keyed by entity id, field
+ * name, and item delta number.
+ */
+function hook_field_storage_load($obj_type, $queried_objs, $age) {
+}
+
+/**
+ * Write field data for an object.
+ *
+ * @param $obj_type
+ * The entity type of object, such as 'node' or 'user'.
+ * @param $object
+ * The object on which to operate.
+ * @param $update
+ * TRUE if this is an update to an existing object, FALSE if it is
+ * an insert of a new object.
+ */
+function hook_field_storage_write($obj_type, $object, $update = TRUE) {
+}
+
+/**
+ * Delete all field data for an object.
+ *
+ * @param $obj_type
+ * The entity type of object, such as 'node' or 'user'.
+ * @param $object
+ * The object on which to operate.
+ */
+function hook_field_storage_delete($obj_type, $object) {
+}
+
+/**
+ * Delete a single revision of field data for an object.
+ *
+ * @param $obj_type
+ * The entity type of object, such as 'node' or 'user'.
+ * @param $object
+ * The object on which to operate. The revision to delete is
+ * indicated by the object's revision id property, as identified by
+ * hook_fieldable_info() for $obj_type.
+ */
+function hook_field_storage_delete_revision($obj_type, $object) {
+}
+
+/**
+ * Act on creation of a new bundle.
+ *
+ * @param $bundle
+ * The name of the bundle being created.
+ */
+function hook_field_storage_create_bundle($bundle) {
+}
+
+/**
+ * Act on a bundle being renamed.
+ *
+ * @param $bundle_old
+ * The old name of the bundle.
+ * @param $bundle_new
+ * The new name of the bundle.
+ */
+function hook_field_storage_rename_bundle($bundle_old, $bundle_new) {
+}
+
+/**
+ * Act on creation of a new field.
+ *
+ * @param $field
+ * The field structure being created.
+ */
+function hook_field_storage_create_field($field) {
+}
+
+/**
+ * Act on deletion of a field.
+ *
+ * @param $field_name
+ * The name of the field being deleted.
+ */
+function hook_field_storage_delete_field($field_name) {
+}
+
+/**
+ * Act on deletion of a field instance.
+ *
+ * @param $field_name
+ * The name of the field in the new instance.
+ * @param $bundle
+ * The name of the bundle in the new instance.
+ */
+function hook_field_storage_delete_instance($field_name, $bundle) {
+}
+
+/**
+ * @} End of "ingroup field_storage"
+ */
+
+/**********************************************************************
+ * Field CRUD API
+ **********************************************************************/
+
+/**
+ * @ingroup field_crud
+ * @{
+ */
+
+/**
+ * Act on a field being created. This hook is invoked after the field
+ * is created and so it cannot modify the field itself.
+ *
+ * TODO: Not implemented.
+ *
+ * @param $field
+ * The field just created.
+ */
+function hook_field_create_field($field) {
+}
+
+/**
+ * Act on a field instance being created. This hook is invoked after
+ * the instance record is saved and so it cannot modify the instance
+ * itself.
+ *
+ * @param $instance
+ * The instance just created.
+ */
+function hook_field_create_instance($instance) {
+}
+
+/**
+ * Act on a field being deleted. This hook is invoked just before the
+ * field is deleted.
+ *
+ * TODO: Not implemented.
+ *
+ * @param $field
+ * The field being deleted.
+ */
+function hook_field_delete_field($field) {
+}
+
+
+/**
+ * Act on a field instance being updated. This hook is invoked after
+ * the instance record is saved and so it cannot modify the instance
+ * itself.
+ *
+ * TODO: Not implemented.
+ *
+ * @param $instance
+ * The instance just updated.
+ */
+function hook_field_update_instance($instance) {
+}
+
+/**
+ * Act on a field instance being deleted. This hook is invoked just
+ * before the instance is deleted.
+ *
+ * TODO: Not implemented.
+ *
+ * @param $instance
+ * The instance just updated.
+ */
+function hook_field_delete_instance($instance) {
+}
+
+/**
+ * Act on field records being read from the database.
+ *
+ * @param $field
+ * The field record just read from the database.
+ */
+function hook_field_read_field($field) {
+}
+
+/**
+ * Act on a field record being read from the database.
+ *
+ * @param $instance
+ * The instance record just read from the database.
+ */
+function hook_field_read_instance($instance) {
+}
+
+/**
+ * @} End of "ingroup field_crud"
+ */
+
+/**********************************************************************
+ * TODO: I'm not sure where these belong yet.
+ **********************************************************************/
+
+/**
+ * TODO
+ *
+ * Note : Right now this belongs to the "Fieldable Type API".
+ * Whether 'build modes' is actually a 'fields' concept is to be debated
+ * in a separate overhaul patch for core.
+ */
+function hook_field_build_modes($obj_type) {
+}
+
+/**
+ * Determine whether the user has access to a given field.
+ *
+ * @param $op
+ * The operation to be performed. Possible values:
+ * - "edit"
+ * - "view"
+ * @param $field
+ * The field on which the operation is to be performed.
+ * @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.
+ */
+function hook_field_access($op, $field, $account) {
+}
diff --git a/modules/field/field.attach.inc b/modules/field/field.attach.inc
index 6f415c166..b16f174e3 100644
--- a/modules/field/field.attach.inc
+++ b/modules/field/field.attach.inc
@@ -1,632 +1,632 @@
-<?php
-// $Id$
-
-// TODO D7 : consistency - do field_attach_functions return a value or alter in place ?
-
-// TOTO D7 : consistency - some of these functions process individual fields
-// and others process the combined value of all fields.
-// Should all iteration through available fields be done here instead of in Field?
-
-/**
- * @defgroup field_storage Field Storage API
- * @{
- * Implement a storage engine for Field API data.
- *
- * The Field Attach API uses the Field Storage API to perform all
- * "database access." Each Field Storage API hook function defines a
- * primitive database operation such as read, write, or delete. The
- * default field storage module, field_sql_storage.module, uses the
- * local SQL database to implement these operations, but alternative
- * field storage engines can choose to represent the data in SQL
- * differently or use a completely different storage mechanism such as
- * a cloud-based database.
- *
- * The Drupal system variable field_storage_module identifies the
- * field storage module to use.
- */
-/**
- * @} End of "defgroup field_storage"
- */
-
-/**
- * @autoload field_attach_.* field_attach FieldException {
- */
-
-/**
- * @defgroup field_attach Field Attach API
- * @{
- * Operate on Field API data attached to Drupal objects.
- *
- * Field Attach API functions load, store, generate Form API
- * structures, display, and perform a vareity of other functions for
- * field data connected to individual objects.
- *
- * Field Attach API functions generally take $obj_type and $object
- * arguments along with additional function-specific arguments.
- * $obj_type is the type of the fieldable entity, such as 'node' or
- * 'user', and $object is the object itself. An individual object's
- * bundle, if any, is read from the object's bundle key property
- * identified by hook_fieldable_info() for $obj_type.
- *
- * Fieldable types call Field Attach API functions during their own
- * API calls; for example, node_load() calls field_attach_load(). A
- * fieldable type may is not required to use all of the Field Attach
- * API functions.
- *
- * Most Field Attach API functions define a corresponding hook
- * function that allows any module to act on Field Attach operations
- * for any object, and access or modify all the field, form, or
- * display data for that object and operation. These all-module hooks
- * are distinct from those of the Field Types API, such as
- * hook_field_load(), that are only invoked for the module that
- * defines a specific field type.
- */
-
-/**
- * Invoke a field hook.
- *
- * @param $op
- * - Possible operations include:
- * - load
- * - form
- * - validate
- * - presave
- * - insert
- * - update
- * - delete
- * - delete revision
- * - sanitize
- * - view
- * - preprocess
- * - prepare translation
- *
- * @param $obj_type
- * - Can be:
- * - node
- * - user
- * - Others not yet implemented.
- *
- * @param $object
- * - The fully formed $obj_type object.
- *
- * @param $a
- * - The $form in the 'form' operation.
- * - The value of $teaser in the 'view' operation.
- * - Otherwise NULL.
- *
- * @param $b
- * - The $form_state in the 'submit' operation.
- * - Otherwise NULL.
- *
- * @param $default
- * - TRUE: render the default field implementation of the field hook.
- * - FALSE: render the field module's implementation of the field hook.
- */
-function _field_invoke($op, $obj_type, &$object, &$a = NULL, &$b = NULL, $default = FALSE) {
- list(, , $bundle) = field_attach_extract_ids($obj_type, $object);
- $instances = field_info_instances($bundle);
-
- $return = array();
- foreach ($instances as $instance) {
- $field_name = $instance['field_name'];
- $field = field_info_field($field_name);
- $items = isset($object->$field_name) ? $object->$field_name : array();
-
- // Make sure AHAH 'add more' button isn't sent to the fields for processing.
- // TODO D7 : needed ?
- unset($items[$field_name . '_add_more']);
-
- $function = $default ? 'field_default_' . $op : $field['module'] . '_field_' . $op;
- if (drupal_function_exists($function)) {
- $result = $function($obj_type, $object, $field, $instance, $items, $a, $b);
- if (is_array($result)) {
- $return = array_merge($return, $result);
- }
- else if (isset($result)) {
- $return[] = $result;
- }
- }
- // Put back the altered items in the object, if the field was present to
- // begin with (avoid replacing missing field with empty array(), those are
- // not semantically equivalent on update).
- if (isset($object->$field_name)) {
- $object->$field_name = $items;
- }
- }
-
- return $return;
-}
-
-/**
- * Invoke field.module's version of a field hook.
- */
-function _field_invoke_default($op, $obj_type, &$object, &$a = NULL, &$b = NULL) {
- return _field_invoke($op, $obj_type, $object, $a, $b, TRUE);
-}
-
-/**
- * @} End of "defgroup field_attach"
- *
- * The rest of the functions in this file are not in a group, but
- * their automatically-generated autoloaders are (see field.autoload.inc).
- */
-
-/**
- * Add form elements for all fields for an object to a form structure.
- *
- * @param $obj_type
- * The type of $object; e.g. 'node' or 'user'.
- * @param $object
- * The object for which to load form elements, used to initialize
- * default form values.
- * @param $form
- * The form structure to fill in.
- * @param $form_state
- * An associative array containing the current state of the form.
- *
- * TODO : document the resulting $form structure, like we do for
- * field_attach_view().
- */
-function _field_attach_form($obj_type, $object, &$form, $form_state) {
- // TODO : something's not right here : do we alter the form or return a value ?
- $form += (array) _field_invoke_default('form', $obj_type, $object, $form, $form_state);
-
- // Let other modules make changes to the form.
- foreach (module_implements('field_attach_form') as $module) {
- $function = $module . '_field_attach_form';
- $function($obj_type, $object, $form, $form_state);
- }
-}
-
-/**
- * Load all fields for the most current version of each of a set of
- * objects of a single object type.
- *
- * @param $obj_type
- * The type of objects for which to load fields; e.g. 'node' or
- * 'user'.
- * @param $objects
- * An array of objects for which to load fields. The keys for
- * primary id and bundle name to load are identified by
- * hook_fieldable_info for $obj_type.
- * @param $age
- * FIELD_LOAD_CURRENT to load the most recent revision for all
- * fields, or FIELD_LOAD_REVISION to load the version indicated by
- * each object. Defaults to FIELD_LOAD_CURRENT; use
- * field_attach_load_revision() instead of passing FIELD_LOAD_REVISION.
- * @returns
- * On return, the objects in $objects are modified by having the
- * appropriate set of fields added.
- */
-function _field_attach_load($obj_type, $objects, $age = FIELD_LOAD_CURRENT) {
- $queried_objects = array();
-
- // Fetch avaliable nodes from cache.
- foreach ($objects as $object) {
- list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object);
- $cid = "field:$obj_type:$id:$vid";
- if ($cacheable && $cached = cache_get($cid, 'cache_field')) {
- foreach ($cached->data as $key => $value) {
- $object->$key = $value;
- }
- }
- else {
- $queried_objects[$id] = $objects[$id];
- }
- }
- // Fetch other nodes from the database.
- if ($queried_objects) {
- // We need the raw additions to be able to cache them, so
- // content_storage_load() and hook_field_load() must not alter
- // nodes directly but return their additions.
- $additions = module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_load', $obj_type, $queried_objects, $age);
- foreach ($additions as $id => $obj_additions) {
- foreach ($obj_additions as $key => $value) {
- $queried_objects[$id]->$key = $value;
- }
- }
-
- // TODO D7 : to be consistent we might want to make hook_field_load() accept
- // multiple objects too. Which forbids going through _field_invoke(), but
- // requires manually iterating the instances instead.
- foreach ($queried_objects as $id => $object) {
- list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object);
-
- // Make sure empty fields are present as empty arrays.
- $instances = field_info_instances($bundle);
- foreach ($instances as $instance) {
- if (!isset($object->{$instance['field_name']})) {
- $queried_objects[$id]->{$instance['field_name']} = array();
- $additions[$id][$instance['field_name']] = array();
- }
- }
-
- $custom_additions = _field_invoke('load', $obj_type, $object);
- foreach ($custom_additions as $key => $value) {
- $queried_objects[$id]->$key = $value;
- $additions[$id][$key] = $value;
- }
-
- // Let other modules act on loading the object.
- // TODO : this currently doesn't get cached (we cache $additions).
- // This should either be called after we fetch from cache, or return an
- // array of additions.
- foreach (module_implements('field_attach_load') as $module) {
- $function = $module . '_field_attach_load';
- $function($obj_type, $queried_objects[$id]);
- }
-
- // Cache the data.
- if ($cacheable) {
- $cid = "field:$obj_type:$id:$vid";
- $data = isset($additions[$id]) ? $additions[$id] : array();
- cache_set($cid, $data, 'cache_field');
- }
- }
- }
-}
-
-/**
- * Load all fields for a previous version of each of a set of
- * objects of a single object type.
- *
- * @param $obj_type
- * The type of objects for which to load fields; e.g. 'node' or
- * 'user'.
- * @param $objects
- * An array of objects for which to load fields. The keys for
- * primary id, revision id, and bundle name to load are identified by
- * hook_fieldable_info for $obj_type.
- * @returns
- * On return, the objects in $objects are modified by having the
- * appropriate set of fields added.
- */
-function _field_attach_load_revision($obj_type, $objects) {
- return field_attach_load($obj_type, $objects, FIELD_LOAD_REVISION);
-}
-
-/**
- * Perform field validation against the field data in an object.
- * Field validation is distinct from widget validation; the latter
- * occurs during the Form API validation phase.
- *
- * NOTE: This functionality does not yet exist in its final state.
- * Eventually, field validation will occur during field_attach_insert
- * or _update which will throw an exception on failure. For now,
- * fieldable entities must call this during their Form API validation
- * phase, and field validation will call form_set_error for any
- * errors. See http://groups.drupal.org/node/18019.
- *
- * @param $obj_type
- * The type of $object; e.g. 'node' or 'user'.
- * @param $object
- * The object with fields to validate.
- */
-function _field_attach_validate($obj_type, &$object, $form = NULL) {
- _field_invoke('validate', $obj_type, $object, $form);
- _field_invoke_default('validate', $obj_type, $object, $form);
-
- // Let other modules validate the object.
- foreach (module_implements('field_attach_validate') as $module) {
- $function = $module . '_field_attach_validate';
- $function($obj_type, $object, $form);
- }
-}
-
-/**
- * Perform necessary operations on field data submitted by a form.
- *
- * Currently, this accounts for drag-and-drop reordering of
- * field values, and filtering of empty values.
- *
- * @param $obj_type
- * The type of $object; e.g. 'node' or 'user'.
- * @param $object
- * The object being submitted. The 'bundle key', 'id key' and (if applicable)
- * 'revision key' should be present. The actual field values will be read
- * from $form_state['values'].
- * @param $form
- * The form structure to fill in.
- * @param $form_state
- * An associative array containing the current state of the form.
- */
-function _field_attach_submit($obj_type, &$object, $form, &$form_state) {
- _field_invoke_default('submit', $obj_type, $object, $form, $form_state);
-
- // Let other modules act on submitting the object.
- foreach (module_implements('field_attach_submit') as $module) {
- $function = $module . '_field_attach_submit';
- $function($obj_type, $object, $form, $form_state);
- }
-}
-
-/**
- * Perform necessary operations just before fields data get saved.
- *
- * We take no specific action here, we just give other
- * modules the opportunity to act.
- *
- * @param $obj_type
- * The type of $object; e.g. 'node' or 'user'.
- * @param $object
- * The object with fields to process.
- */
-function _field_attach_presave($obj_type, &$object) {
- // TODO : to my knowledge, no field module has any use for 'presave' on D6.
- // should we keep this ?
- _field_invoke('presave', $obj_type, $object);
-
- // Let other modules act on presaving the object.
- foreach (module_implements('field_attach_presave') as $module) {
- $function = $module . '_field_attach_presave';
- $function($obj_type, $object);
- }
-}
-
-/**
- * Save field data for a new object. The passed in object must
- * already contain its id and (if applicable) revision id attributes.
- *
- * @param $obj_type
- * The type of $object; e.g. 'node' or 'user'.
- * @param $object
- * The object with fields to save.
- */
-function _field_attach_insert($obj_type, &$object) {
-
- // Let other modules act on inserting the object.
- foreach (module_implements('field_attach_insert') as $module) {
- $function = $module . '_field_attach_insert';
- $function($obj_type, $object);
- }
-
- _field_invoke('insert', $obj_type, $object);
- module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_write', $obj_type, $object);
-
- list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object);
- if ($cacheable) {
- cache_clear_all("field:$obj_type:$id:", 'cache_field', TRUE);
- }
-}
-
-/**
- * Save field data for an existing object.
- *
- * @param $obj_type
- * The type of $object; e.g. 'node' or 'user'.
- * @param $object
- * The object with fields to save.
- */
-function _field_attach_update($obj_type, &$object) {
-
- // Let other modules act on updating the object.
- foreach (module_implements('field_attach_update') as $module) {
- $function = $module . '_field_attach_update';
- $function($output, $obj_type, $object);
- }
-
- _field_invoke('update', $obj_type, $object);
- module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_write', $obj_type, $object, TRUE);
-
- list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object);
- if ($cacheable) {
- cache_clear_all("field:$obj_type:$id:$vid", 'cache_field');
- }
-}
-
-/**
- * Delete field data for an existing object. This deletes all
- * revisions of field data for the object.
- *
- * @param $obj_type
- * The type of $object; e.g. 'node' or 'user'.
- * @param $object
- * The object whose field data to delete.
- */
-function _field_attach_delete($obj_type, &$object) {
- _field_invoke('delete', $obj_type, $object);
- module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_delete', $obj_type, $object);
-
- // Let other modules act on deleting the object.
- foreach (module_implements('field_attach_delete') as $module) {
- $function = $module . '_field_attach_delete';
- $function($obj_type, $object);
- }
-
- list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object);
- if ($cacheable) {
- cache_clear_all("field:$obj_type:$id:", 'cache_field', TRUE);
- }
-}
-
-/**
- * Delete field data for a single revision of an existing object. The
- * passed object must have a revision id attribute.
- *
- * @param $obj_type
- * The type of $object; e.g. 'node' or 'user'.
- * @param $object
- * The object with fields to save.
- */
-function _field_attach_delete_revision($obj_type, &$object) {
- _field_invoke('delete revision', $obj_type, $object);
- module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_delete_revision', $obj_type, $object);
-
- // Let other modules act on deleting the revision.
- foreach (module_implements('field_attach_delete_revision') as $module) {
- $function = $module . '_field_attach_delete_revision';
- $function($obj_type, $object);
- }
-
- list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object);
- if ($cacheable) {
- cache_clear_all("field:$obj_type:$id:$vid", 'cache_field');
- }
-}
-
-/**
- * Generate and return a structured content array tree suitable for
- * drupal_render() for all of the fields on an object. The format of
- * each field's rendered content depends on the display formatter and
- * its settings.
- *
- * @param $obj_type
- * The type of $object; e.g. 'node' or 'user'.
- * @param $object
- * The object with fields to render.
- * @param $teaser
- * Whether to display the teaser only, as on the main page.
- * @return
- * A structured content array tree for drupal_render().
- */
-function _field_attach_view($obj_type, &$object, $teaser = FALSE) {
- // Let field modules sanitize their data for output.
- _field_invoke('sanitize', $obj_type, $object);
-
- $output = _field_invoke_default('view', $obj_type, $object, $teaser);
-
- // Let other modules make changes after rendering the view.
- foreach (module_implements('field_attach_view') as $module) {
- $function = $module . '_field_attach_view';
- $function($output, $obj_type, $object, $teaser);
- }
-
- return $output;
-
-}
-
-/**
- * To be called in entity preprocessor.
- *
- * - Adds $FIELD_NAME_rendered variables
- * containing the themed output for the whole field.
- * - Adds the formatted values in the 'view' key of the items.
- */
-function _field_attach_preprocess($obj_type, &$object) {
- return _field_invoke_default('preprocess', $obj_type, $object);
-}
-
-/**
- * Implementation of hook_nodeapi_prepare_translation.
- *
- * TODO D7: We do not yet know if this really belongs in Field API.
- */
-function _field_attach_prepare_translation(&$node) {
- // Prevent against invalid 'nodes' built by broken 3rd party code.
- if (isset($node->type)) {
- $type = content_types($node->type);
- // Save cycles if the type has no fields.
- if (!empty($type['instances'])) {
- $default_additions = _field_invoke_default('prepare translation', $node);
- $additions = _field_invoke('prepare translation', $node);
- // Merge module additions after the default ones to enable overriding
- // of field values.
- $node = (object) array_merge((array) $node, $default_additions, $additions);
- }
- }
-}
-
-/**
- * Notify field.module that a new bundle was created.
- *
- * The default SQL-based storage doesn't need to do anytrhing about it, but
- * others might.
- *
- * @param $bundle
- * The name of the newly created bundle.
- */
-function _field_attach_create_bundle($bundle) {
- module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_create_bundle', $bundle);
-
- // Clear the cache.
- field_cache_clear();
-
- foreach (module_implements('field_attach_create_bundle') as $module) {
- $function = $module . '_field_attach_create_bundle';
- $function($bundle);
- }
-}
-
-/**
- * Notify field.module that a bundle was renamed.
- *
- * @param $bundle_old
- * The previous name of the bundle.
- * @param $bundle_new
- * The new name of the bundle.
- */
-function _field_attach_rename_bundle($bundle_old, $bundle_new) {
- module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_rename_bundle', $bundle_old, $bundle_new);
- db_update('field_config_instance')
- ->fields(array('bundle' => $bundle_new))
- ->condition('bundle', $bundle_old)
- ->execute();
-
- // Clear the cache.
- field_cache_clear();
-
- foreach (module_implements('field_attach_rename_bundle') as $module) {
- $function = $module . '_field_attach_rename_bundle';
- $function($bundle_old, $bundle_new);
- }
-}
-
-/**
- * Notify field.module the 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
- * and deleted, leaving the garbage collection for a separate process, because
- * it is not always possible to delete this much data in a single page request
- * (particularly since for some field types, the deletion is more than just a
- * simple DELETE query).
- *
- * @param $bundle
- * The bundle to delete.
- */
-function _field_attach_delete_bundle($bundle) {
- // Let other modules act on deleting the bundle
- foreach (module_implements('field_attach_delete_bundle') as $module) {
- $function = $module . '_field_attach_delete_bundle';
- $function($bundle);
- }
-
- // Delete the instances themseves
- $instances = field_info_instances($bundle);
- foreach ($instances as $instance) {
- field_delete_instance($instance['field_name'], $bundle);
- }
-}
-
-/**
- * Helper function to extract id, vid, and bundle name from an object.
- *
- * @param $obj_type
- * The type of $object; e.g. 'node' or 'user'.
- * @param $object
- * The object from which to extract values.
- * @return
- * A numerically indexed array (not a hash table) containing these
- * elements:
- *
- * 0: primary id of the object
- * 1: revision id of the object, or NULL if $obj_type is not versioned
- * 2: bundle name of the object
- * 3: whether $obj_type's fields should be cached (TRUE/FALSE)
- */
-function _field_attach_extract_ids($object_type, $object) {
- // TODO D7 : prevent against broken 3rd party $node without 'type'.
- $info = field_info_fieldable_types($object_type);
- // Objects being created might not have id/vid yet.
- $id = isset($object->{$info['id key']}) ? $object->{$info['id key']} : NULL;
- $vid = ($info['revision key'] && isset($object->{$info['revision key']})) ? $object->{$info['revision key']} : NULL;
- // If no bundle key provided, then we assume a single bundle, named after the
- // type of the object.
- $bundle = $info['bundle key'] ? $object->{$info['bundle key']} : $object_type;
- $cacheable = isset($info['cacheable']) ? $info['cacheable'] : FALSE;
- return array($id, $vid, $bundle, $cacheable);
-}
-
-/**
- * @autoload} End of "@autoload field_attach"
- */
+<?php
+// $Id$
+
+// TODO D7 : consistency - do field_attach_functions return a value or alter in place ?
+
+// TOTO D7 : consistency - some of these functions process individual fields
+// and others process the combined value of all fields.
+// Should all iteration through available fields be done here instead of in Field?
+
+/**
+ * @defgroup field_storage Field Storage API
+ * @{
+ * Implement a storage engine for Field API data.
+ *
+ * The Field Attach API uses the Field Storage API to perform all
+ * "database access." Each Field Storage API hook function defines a
+ * primitive database operation such as read, write, or delete. The
+ * default field storage module, field_sql_storage.module, uses the
+ * local SQL database to implement these operations, but alternative
+ * field storage engines can choose to represent the data in SQL
+ * differently or use a completely different storage mechanism such as
+ * a cloud-based database.
+ *
+ * The Drupal system variable field_storage_module identifies the
+ * field storage module to use.
+ */
+/**
+ * @} End of "defgroup field_storage"
+ */
+
+/**
+ * @autoload field_attach_.* field_attach FieldException {
+ */
+
+/**
+ * @defgroup field_attach Field Attach API
+ * @{
+ * Operate on Field API data attached to Drupal objects.
+ *
+ * Field Attach API functions load, store, generate Form API
+ * structures, display, and perform a vareity of other functions for
+ * field data connected to individual objects.
+ *
+ * Field Attach API functions generally take $obj_type and $object
+ * arguments along with additional function-specific arguments.
+ * $obj_type is the type of the fieldable entity, such as 'node' or
+ * 'user', and $object is the object itself. An individual object's
+ * bundle, if any, is read from the object's bundle key property
+ * identified by hook_fieldable_info() for $obj_type.
+ *
+ * Fieldable types call Field Attach API functions during their own
+ * API calls; for example, node_load() calls field_attach_load(). A
+ * fieldable type may is not required to use all of the Field Attach
+ * API functions.
+ *
+ * Most Field Attach API functions define a corresponding hook
+ * function that allows any module to act on Field Attach operations
+ * for any object, and access or modify all the field, form, or
+ * display data for that object and operation. These all-module hooks
+ * are distinct from those of the Field Types API, such as
+ * hook_field_load(), that are only invoked for the module that
+ * defines a specific field type.
+ */
+
+/**
+ * Invoke a field hook.
+ *
+ * @param $op
+ * - Possible operations include:
+ * - load
+ * - form
+ * - validate
+ * - presave
+ * - insert
+ * - update
+ * - delete
+ * - delete revision
+ * - sanitize
+ * - view
+ * - preprocess
+ * - prepare translation
+ *
+ * @param $obj_type
+ * - Can be:
+ * - node
+ * - user
+ * - Others not yet implemented.
+ *
+ * @param $object
+ * - The fully formed $obj_type object.
+ *
+ * @param $a
+ * - The $form in the 'form' operation.
+ * - The value of $teaser in the 'view' operation.
+ * - Otherwise NULL.
+ *
+ * @param $b
+ * - The $form_state in the 'submit' operation.
+ * - Otherwise NULL.
+ *
+ * @param $default
+ * - TRUE: render the default field implementation of the field hook.
+ * - FALSE: render the field module's implementation of the field hook.
+ */
+function _field_invoke($op, $obj_type, &$object, &$a = NULL, &$b = NULL, $default = FALSE) {
+ list(, , $bundle) = field_attach_extract_ids($obj_type, $object);
+ $instances = field_info_instances($bundle);
+
+ $return = array();
+ foreach ($instances as $instance) {
+ $field_name = $instance['field_name'];
+ $field = field_info_field($field_name);
+ $items = isset($object->$field_name) ? $object->$field_name : array();
+
+ // Make sure AHAH 'add more' button isn't sent to the fields for processing.
+ // TODO D7 : needed ?
+ unset($items[$field_name . '_add_more']);
+
+ $function = $default ? 'field_default_' . $op : $field['module'] . '_field_' . $op;
+ if (drupal_function_exists($function)) {
+ $result = $function($obj_type, $object, $field, $instance, $items, $a, $b);
+ if (is_array($result)) {
+ $return = array_merge($return, $result);
+ }
+ else if (isset($result)) {
+ $return[] = $result;
+ }
+ }
+ // Put back the altered items in the object, if the field was present to
+ // begin with (avoid replacing missing field with empty array(), those are
+ // not semantically equivalent on update).
+ if (isset($object->$field_name)) {
+ $object->$field_name = $items;
+ }
+ }
+
+ return $return;
+}
+
+/**
+ * Invoke field.module's version of a field hook.
+ */
+function _field_invoke_default($op, $obj_type, &$object, &$a = NULL, &$b = NULL) {
+ return _field_invoke($op, $obj_type, $object, $a, $b, TRUE);
+}
+
+/**
+ * @} End of "defgroup field_attach"
+ *
+ * The rest of the functions in this file are not in a group, but
+ * their automatically-generated autoloaders are (see field.autoload.inc).
+ */
+
+/**
+ * Add form elements for all fields for an object to a form structure.
+ *
+ * @param $obj_type
+ * The type of $object; e.g. 'node' or 'user'.
+ * @param $object
+ * The object for which to load form elements, used to initialize
+ * default form values.
+ * @param $form
+ * The form structure to fill in.
+ * @param $form_state
+ * An associative array containing the current state of the form.
+ *
+ * TODO : document the resulting $form structure, like we do for
+ * field_attach_view().
+ */
+function _field_attach_form($obj_type, $object, &$form, $form_state) {
+ // TODO : something's not right here : do we alter the form or return a value ?
+ $form += (array) _field_invoke_default('form', $obj_type, $object, $form, $form_state);
+
+ // Let other modules make changes to the form.
+ foreach (module_implements('field_attach_form') as $module) {
+ $function = $module . '_field_attach_form';
+ $function($obj_type, $object, $form, $form_state);
+ }
+}
+
+/**
+ * Load all fields for the most current version of each of a set of
+ * objects of a single object type.
+ *
+ * @param $obj_type
+ * The type of objects for which to load fields; e.g. 'node' or
+ * 'user'.
+ * @param $objects
+ * An array of objects for which to load fields. The keys for
+ * primary id and bundle name to load are identified by
+ * hook_fieldable_info for $obj_type.
+ * @param $age
+ * FIELD_LOAD_CURRENT to load the most recent revision for all
+ * fields, or FIELD_LOAD_REVISION to load the version indicated by
+ * each object. Defaults to FIELD_LOAD_CURRENT; use
+ * field_attach_load_revision() instead of passing FIELD_LOAD_REVISION.
+ * @returns
+ * On return, the objects in $objects are modified by having the
+ * appropriate set of fields added.
+ */
+function _field_attach_load($obj_type, $objects, $age = FIELD_LOAD_CURRENT) {
+ $queried_objects = array();
+
+ // Fetch avaliable nodes from cache.
+ foreach ($objects as $object) {
+ list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object);
+ $cid = "field:$obj_type:$id:$vid";
+ if ($cacheable && $cached = cache_get($cid, 'cache_field')) {
+ foreach ($cached->data as $key => $value) {
+ $object->$key = $value;
+ }
+ }
+ else {
+ $queried_objects[$id] = $objects[$id];
+ }
+ }
+ // Fetch other nodes from the database.
+ if ($queried_objects) {
+ // We need the raw additions to be able to cache them, so
+ // content_storage_load() and hook_field_load() must not alter
+ // nodes directly but return their additions.
+ $additions = module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_load', $obj_type, $queried_objects, $age);
+ foreach ($additions as $id => $obj_additions) {
+ foreach ($obj_additions as $key => $value) {
+ $queried_objects[$id]->$key = $value;
+ }
+ }
+
+ // TODO D7 : to be consistent we might want to make hook_field_load() accept
+ // multiple objects too. Which forbids going through _field_invoke(), but
+ // requires manually iterating the instances instead.
+ foreach ($queried_objects as $id => $object) {
+ list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object);
+
+ // Make sure empty fields are present as empty arrays.
+ $instances = field_info_instances($bundle);
+ foreach ($instances as $instance) {
+ if (!isset($object->{$instance['field_name']})) {
+ $queried_objects[$id]->{$instance['field_name']} = array();
+ $additions[$id][$instance['field_name']] = array();
+ }
+ }
+
+ $custom_additions = _field_invoke('load', $obj_type, $object);
+ foreach ($custom_additions as $key => $value) {
+ $queried_objects[$id]->$key = $value;
+ $additions[$id][$key] = $value;
+ }
+
+ // Let other modules act on loading the object.
+ // TODO : this currently doesn't get cached (we cache $additions).
+ // This should either be called after we fetch from cache, or return an
+ // array of additions.
+ foreach (module_implements('field_attach_load') as $module) {
+ $function = $module . '_field_attach_load';
+ $function($obj_type, $queried_objects[$id]);
+ }
+
+ // Cache the data.
+ if ($cacheable) {
+ $cid = "field:$obj_type:$id:$vid";
+ $data = isset($additions[$id]) ? $additions[$id] : array();
+ cache_set($cid, $data, 'cache_field');
+ }
+ }
+ }
+}
+
+/**
+ * Load all fields for a previous version of each of a set of
+ * objects of a single object type.
+ *
+ * @param $obj_type
+ * The type of objects for which to load fields; e.g. 'node' or
+ * 'user'.
+ * @param $objects
+ * An array of objects for which to load fields. The keys for
+ * primary id, revision id, and bundle name to load are identified by
+ * hook_fieldable_info for $obj_type.
+ * @returns
+ * On return, the objects in $objects are modified by having the
+ * appropriate set of fields added.
+ */
+function _field_attach_load_revision($obj_type, $objects) {
+ return field_attach_load($obj_type, $objects, FIELD_LOAD_REVISION);
+}
+
+/**
+ * Perform field validation against the field data in an object.
+ * Field validation is distinct from widget validation; the latter
+ * occurs during the Form API validation phase.
+ *
+ * NOTE: This functionality does not yet exist in its final state.
+ * Eventually, field validation will occur during field_attach_insert
+ * or _update which will throw an exception on failure. For now,
+ * fieldable entities must call this during their Form API validation
+ * phase, and field validation will call form_set_error for any
+ * errors. See http://groups.drupal.org/node/18019.
+ *
+ * @param $obj_type
+ * The type of $object; e.g. 'node' or 'user'.
+ * @param $object
+ * The object with fields to validate.
+ */
+function _field_attach_validate($obj_type, &$object, $form = NULL) {
+ _field_invoke('validate', $obj_type, $object, $form);
+ _field_invoke_default('validate', $obj_type, $object, $form);
+
+ // Let other modules validate the object.
+ foreach (module_implements('field_attach_validate') as $module) {
+ $function = $module . '_field_attach_validate';
+ $function($obj_type, $object, $form);
+ }
+}
+
+/**
+ * Perform necessary operations on field data submitted by a form.
+ *
+ * Currently, this accounts for drag-and-drop reordering of
+ * field values, and filtering of empty values.
+ *
+ * @param $obj_type
+ * The type of $object; e.g. 'node' or 'user'.
+ * @param $object
+ * The object being submitted. The 'bundle key', 'id key' and (if applicable)
+ * 'revision key' should be present. The actual field values will be read
+ * from $form_state['values'].
+ * @param $form
+ * The form structure to fill in.
+ * @param $form_state
+ * An associative array containing the current state of the form.
+ */
+function _field_attach_submit($obj_type, &$object, $form, &$form_state) {
+ _field_invoke_default('submit', $obj_type, $object, $form, $form_state);
+
+ // Let other modules act on submitting the object.
+ foreach (module_implements('field_attach_submit') as $module) {
+ $function = $module . '_field_attach_submit';
+ $function($obj_type, $object, $form, $form_state);
+ }
+}
+
+/**
+ * Perform necessary operations just before fields data get saved.
+ *
+ * We take no specific action here, we just give other
+ * modules the opportunity to act.
+ *
+ * @param $obj_type
+ * The type of $object; e.g. 'node' or 'user'.
+ * @param $object
+ * The object with fields to process.
+ */
+function _field_attach_presave($obj_type, &$object) {
+ // TODO : to my knowledge, no field module has any use for 'presave' on D6.
+ // should we keep this ?
+ _field_invoke('presave', $obj_type, $object);
+
+ // Let other modules act on presaving the object.
+ foreach (module_implements('field_attach_presave') as $module) {
+ $function = $module . '_field_attach_presave';
+ $function($obj_type, $object);
+ }
+}
+
+/**
+ * Save field data for a new object. The passed in object must
+ * already contain its id and (if applicable) revision id attributes.
+ *
+ * @param $obj_type
+ * The type of $object; e.g. 'node' or 'user'.
+ * @param $object
+ * The object with fields to save.
+ */
+function _field_attach_insert($obj_type, &$object) {
+
+ // Let other modules act on inserting the object.
+ foreach (module_implements('field_attach_insert') as $module) {
+ $function = $module . '_field_attach_insert';
+ $function($obj_type, $object);
+ }
+
+ _field_invoke('insert', $obj_type, $object);
+ module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_write', $obj_type, $object);
+
+ list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object);
+ if ($cacheable) {
+ cache_clear_all("field:$obj_type:$id:", 'cache_field', TRUE);
+ }
+}
+
+/**
+ * Save field data for an existing object.
+ *
+ * @param $obj_type
+ * The type of $object; e.g. 'node' or 'user'.
+ * @param $object
+ * The object with fields to save.
+ */
+function _field_attach_update($obj_type, &$object) {
+
+ // Let other modules act on updating the object.
+ foreach (module_implements('field_attach_update') as $module) {
+ $function = $module . '_field_attach_update';
+ $function($output, $obj_type, $object);
+ }
+
+ _field_invoke('update', $obj_type, $object);
+ module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_write', $obj_type, $object, TRUE);
+
+ list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object);
+ if ($cacheable) {
+ cache_clear_all("field:$obj_type:$id:$vid", 'cache_field');
+ }
+}
+
+/**
+ * Delete field data for an existing object. This deletes all
+ * revisions of field data for the object.
+ *
+ * @param $obj_type
+ * The type of $object; e.g. 'node' or 'user'.
+ * @param $object
+ * The object whose field data to delete.
+ */
+function _field_attach_delete($obj_type, &$object) {
+ _field_invoke('delete', $obj_type, $object);
+ module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_delete', $obj_type, $object);
+
+ // Let other modules act on deleting the object.
+ foreach (module_implements('field_attach_delete') as $module) {
+ $function = $module . '_field_attach_delete';
+ $function($obj_type, $object);
+ }
+
+ list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object);
+ if ($cacheable) {
+ cache_clear_all("field:$obj_type:$id:", 'cache_field', TRUE);
+ }
+}
+
+/**
+ * Delete field data for a single revision of an existing object. The
+ * passed object must have a revision id attribute.
+ *
+ * @param $obj_type
+ * The type of $object; e.g. 'node' or 'user'.
+ * @param $object
+ * The object with fields to save.
+ */
+function _field_attach_delete_revision($obj_type, &$object) {
+ _field_invoke('delete revision', $obj_type, $object);
+ module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_delete_revision', $obj_type, $object);
+
+ // Let other modules act on deleting the revision.
+ foreach (module_implements('field_attach_delete_revision') as $module) {
+ $function = $module . '_field_attach_delete_revision';
+ $function($obj_type, $object);
+ }
+
+ list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object);
+ if ($cacheable) {
+ cache_clear_all("field:$obj_type:$id:$vid", 'cache_field');
+ }
+}
+
+/**
+ * Generate and return a structured content array tree suitable for
+ * drupal_render() for all of the fields on an object. The format of
+ * each field's rendered content depends on the display formatter and
+ * its settings.
+ *
+ * @param $obj_type
+ * The type of $object; e.g. 'node' or 'user'.
+ * @param $object
+ * The object with fields to render.
+ * @param $teaser
+ * Whether to display the teaser only, as on the main page.
+ * @return
+ * A structured content array tree for drupal_render().
+ */
+function _field_attach_view($obj_type, &$object, $teaser = FALSE) {
+ // Let field modules sanitize their data for output.
+ _field_invoke('sanitize', $obj_type, $object);
+
+ $output = _field_invoke_default('view', $obj_type, $object, $teaser);
+
+ // Let other modules make changes after rendering the view.
+ foreach (module_implements('field_attach_view') as $module) {
+ $function = $module . '_field_attach_view';
+ $function($output, $obj_type, $object, $teaser);
+ }
+
+ return $output;
+
+}
+
+/**
+ * To be called in entity preprocessor.
+ *
+ * - Adds $FIELD_NAME_rendered variables
+ * containing the themed output for the whole field.
+ * - Adds the formatted values in the 'view' key of the items.
+ */
+function _field_attach_preprocess($obj_type, &$object) {
+ return _field_invoke_default('preprocess', $obj_type, $object);
+}
+
+/**
+ * Implementation of hook_nodeapi_prepare_translation.
+ *
+ * TODO D7: We do not yet know if this really belongs in Field API.
+ */
+function _field_attach_prepare_translation(&$node) {
+ // Prevent against invalid 'nodes' built by broken 3rd party code.
+ if (isset($node->type)) {
+ $type = content_types($node->type);
+ // Save cycles if the type has no fields.
+ if (!empty($type['instances'])) {
+ $default_additions = _field_invoke_default('prepare translation', $node);
+ $additions = _field_invoke('prepare translation', $node);
+ // Merge module additions after the default ones to enable overriding
+ // of field values.
+ $node = (object) array_merge((array) $node, $default_additions, $additions);
+ }
+ }
+}
+
+/**
+ * Notify field.module that a new bundle was created.
+ *
+ * The default SQL-based storage doesn't need to do anytrhing about it, but
+ * others might.
+ *
+ * @param $bundle
+ * The name of the newly created bundle.
+ */
+function _field_attach_create_bundle($bundle) {
+ module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_create_bundle', $bundle);
+
+ // Clear the cache.
+ field_cache_clear();
+
+ foreach (module_implements('field_attach_create_bundle') as $module) {
+ $function = $module . '_field_attach_create_bundle';
+ $function($bundle);
+ }
+}
+
+/**
+ * Notify field.module that a bundle was renamed.
+ *
+ * @param $bundle_old
+ * The previous name of the bundle.
+ * @param $bundle_new
+ * The new name of the bundle.
+ */
+function _field_attach_rename_bundle($bundle_old, $bundle_new) {
+ module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_rename_bundle', $bundle_old, $bundle_new);
+ db_update('field_config_instance')
+ ->fields(array('bundle' => $bundle_new))
+ ->condition('bundle', $bundle_old)
+ ->execute();
+
+ // Clear the cache.
+ field_cache_clear();
+
+ foreach (module_implements('field_attach_rename_bundle') as $module) {
+ $function = $module . '_field_attach_rename_bundle';
+ $function($bundle_old, $bundle_new);
+ }
+}
+
+/**
+ * Notify field.module the 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
+ * and deleted, leaving the garbage collection for a separate process, because
+ * it is not always possible to delete this much data in a single page request
+ * (particularly since for some field types, the deletion is more than just a
+ * simple DELETE query).
+ *
+ * @param $bundle
+ * The bundle to delete.
+ */
+function _field_attach_delete_bundle($bundle) {
+ // Let other modules act on deleting the bundle
+ foreach (module_implements('field_attach_delete_bundle') as $module) {
+ $function = $module . '_field_attach_delete_bundle';
+ $function($bundle);
+ }
+
+ // Delete the instances themseves
+ $instances = field_info_instances($bundle);
+ foreach ($instances as $instance) {
+ field_delete_instance($instance['field_name'], $bundle);
+ }
+}
+
+/**
+ * Helper function to extract id, vid, and bundle name from an object.
+ *
+ * @param $obj_type
+ * The type of $object; e.g. 'node' or 'user'.
+ * @param $object
+ * The object from which to extract values.
+ * @return
+ * A numerically indexed array (not a hash table) containing these
+ * elements:
+ *
+ * 0: primary id of the object
+ * 1: revision id of the object, or NULL if $obj_type is not versioned
+ * 2: bundle name of the object
+ * 3: whether $obj_type's fields should be cached (TRUE/FALSE)
+ */
+function _field_attach_extract_ids($object_type, $object) {
+ // TODO D7 : prevent against broken 3rd party $node without 'type'.
+ $info = field_info_fieldable_types($object_type);
+ // Objects being created might not have id/vid yet.
+ $id = isset($object->{$info['id key']}) ? $object->{$info['id key']} : NULL;
+ $vid = ($info['revision key'] && isset($object->{$info['revision key']})) ? $object->{$info['revision key']} : NULL;
+ // If no bundle key provided, then we assume a single bundle, named after the
+ // type of the object.
+ $bundle = $info['bundle key'] ? $object->{$info['bundle key']} : $object_type;
+ $cacheable = isset($info['cacheable']) ? $info['cacheable'] : FALSE;
+ return array($id, $vid, $bundle, $cacheable);
+}
+
+/**
+ * @autoload} End of "@autoload field_attach"
+ */
diff --git a/modules/field/field.crud.inc b/modules/field/field.crud.inc
index e63838140..01f2743cd 100644
--- a/modules/field/field.crud.inc
+++ b/modules/field/field.crud.inc
@@ -1,558 +1,558 @@
-<?php
-
-class FieldException extends Exception {}
-
-/**
- * @defgroup field_structs Field API data structures
- * @{
- * Represent Field API fields and instances.
- *
- * The Field API defines two primary data structures, Field and
- * Instance, and the concept of a Bundle. A Field defines a
- * particular type of data that can be attached to objects. A Field
- * Instance is a Field attached to a single Bundle. A Bundle is a set
- * of fields that are treated as a group by the Field Attach API.
- *
- * For example, suppose a site administrator wants Article nodes to
- * have a subtitle and photo. Using the Field API or some UI module,
- * the administrator creates a field named 'subtitle' of type 'text'
- * and a field named 'photo' of type 'image'. The administrator
- * (again, via a UI) creates two Field Instances, one attaching the
- * field 'subtitle' to the bundle 'article' and one attaching the field
- * 'photo' to the bundle 'article'. When the node system uses the
- * Field Attach API to load all fields for an Article node, it passes
- * the node's content type (which is 'article') as the node's bundle.
- * field_attach_load() then loads the 'subtitle' and 'photo' fields
- * because they are both attached to the bundle 'article'.
- *
- * Field objects are (currently) represented as an array of key/value
- * pairs. The object properties are:
- *
- * @param array $field:
- * - field_name (string)
- * The name of the field. Each field name is unique within Field API.
- * When a field is attached to an object, the field's data is stored
- * in $object->$field_name.
- * - type (string)
- * The type of the field, such as 'text' or 'image'. Field types
- * are defined by modules that implement hook_field_into().
- * - cardinality (integer)
- * The number of values the field can hold. Legal values are any
- * positive integer or FIELD_CARDINALITY_UNLIMITED.
- * - locked (integer)
- * TODO: undefined.
- * - 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.
- * - 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.
- *
- * Field Instance objects are (currently) represented as an array of
- * key/value pairs. The object properties are:
- *
- * @param array $instance:
- * - field_name (string)
- * The name of field attached by this instance.
- * - 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.
- * - weight (float)
- * The order in which the field should be sorted relative
- * to other fields when used with this bundle. The weight affects
- * ordering in both forms (see field_attach_form()) and rendered output
- * (see field_attach_view()).
- * TODO - this should probably become a context setting so that
- * the weight can be different in the form and in various other
- * contexts.
- * - 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.
- * - 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().
- * - module (string, read-only)
- * The name of the module that implements the widget type.
- * - active (integer, read-only)
- * TRUE if the module that implements the widget type is currently
- * enabled, FALSE otherwise.
- * - 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.
- * - display (array)
- * A sub-array of key/value pairs identifying display contexts
- * and the way the field should be displayed in that context.
- * TODO more work to do here.
- * - (context_1)
- * - label
- * - exclude
- * - type
- * - settings
- * - ...
- * - module (internal)
- * - (context 2)
- * - ...
- *
- * TODO D7 : document max length for field types, widget types,
- * formatter names...
- */
-/**
- * @} End of "defgroup field_structs".
- */
-
-/**
- * @defgroup field_crud Field CRUD API
- * @{
- * Create, update, and delete Field API fields, bundles, and instances.
- *
- * Modules use this API, often in hook_install(), to create custom
- * data structures. UI modules will use it to create a user interface.
- *
- * The Field CRUD API uses
- * @link field_structs Field API data structures @endlink.
- */
-
-/**
- * Create a field. This function does not bind the field to any
- * bundle; use field_create_instance for that.
- *
- * @param $field
- * A field structure. The field_name and type properties are required.
- * @throw
- * FieldException
- */
-function field_create_field($field) {
- // Field name is required.
- if (empty($field['field_name'])) {
- throw new FieldException('Attempt to create an unnamed field.');
- }
- // Field name cannot contain invalid characters.
- if (preg_match('/[^a-z0-9_]/', $field['field_name'])) {
- throw new FieldException('Attempt to create a field with invalid characters. Only alphanumeric characters and underscores are allowed.');
- }
-
- // TODO: check that field_name < 32 chars.
-
- // Check that the field type is known.
- $field_type = field_info_field_types($field['type']);
- if (!$field_type) {
- throw new FieldException(t('Attempt to create a field of unknown type %type.', array('%type' => $field['type'])));
- }
-
- // Ensure the field name is unique. We also check disabled or deleted fields.
- // TODO : do we want specific messages when clashing with a disabled or inactive field ?
- $prior_field = field_read_field($field['field_name'], array('include_inactive' => TRUE, 'include_deleted' => TRUE));
- if (!empty($prior_field)) {
- throw new FieldException(t('Attempt to create field name %name which already exists.', array('%name' => $field['field_name'])));
- }
-
- $field += array(
- 'cardinality' => 1,
- 'locked' => FALSE,
- 'settings' => array(),
- );
- $module = $field_type['module'];
- // Create all per-field-type properties (needed here as long as we have
- // settings that impact column definitions).
- $field['settings'] += field_info_field_settings($field['type']);
- $field['module'] = $module;
- $field['active'] = 1;
- $field['deleted'] = 0;
- // Create the data table. We need to populate the field columns, even though
- // we don't actually store them.
- $field['columns'] = (array) module_invoke($field['module'], 'field_columns', $field);
- module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_create_field', $field);
-
- drupal_write_record('field_config', $field);
-
- // Clear caches
- field_cache_clear(TRUE);
-}
-
-/**
- * Read a single field record directly from the database. Generally,
- * you should use the field_info_field() instead.
- *
- * @param $field_name
- * The field name to read.
- * @param array $include_additional
- * The default behavior of this function is to not return a field that
- * is inactive or has been deleted. Setting
- * $include_additional['include_inactive'] or
- * $include_additional['include_deleted'] to TRUE will override this
- * behavior.
- * @return
- * A field structure, or FALSE.
- */
-function field_read_field($field_name, $include_additional = array()) {
- $fields = field_read_fields(array('field_name' => $field_name), $include_additional);
- return $fields ? current($fields) : FALSE;
-}
-
-/**
- * Read in fields that match an array of conditions.
- *
- * @param array $params
- * An array of conditions to match against.
- * @param array $include_additional
- * The default behavior of this function is to not return fields that
- * are inactive or have been deleted. Setting
- * $include_additional['include_inactive'] or
- * $include_additional['include_deleted'] to TRUE will override this
- * behavior.
- * @return
- * An array of fields matching $params.
- */
-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) {
- $query->condition($key, $value);
- }
- if (!isset($include_additional['include_inactive']) || !$include_additional['include_inactive']) {
- $query->condition('fc.active', 1);
- }
- if (!isset($include_additional['include_deleted']) || !$include_additional['include_deleted']) {
- $query->condition('fc.deleted', 0);
- }
-
- $fields = array();
- $results = $query->execute();
- foreach ($results as $field) {
- // drupal_write_record() writes an empty string for empty arrays.
- $field['settings'] = $field['settings'] ? unserialize($field['settings']) : array();
-
- module_invoke_all('field_read_field', $field);
-
- // Populate storage columns.
- $field['columns'] = (array) module_invoke($field['module'], 'field_columns', $field);
-
- $fields[$field['field_name']] = $field;
- }
- return $fields;
-}
-
-/**
- * Mark a field for deletion, including all its instances and all data
- * associated with it.
- *
- * @param $field_name
- * The field name to delete.
- */
-function field_delete_field($field_name) {
- // Mark the field for deletion.
- db_update('field_config')
- ->fields(array('deleted' => 1))
- ->condition('field_name', $field_name)
- ->execute();
-
- // Mark any instances of the field for deletion.
- db_update('field_config_instance')
- ->fields(array('deleted' => 1))
- ->condition('field_name', $field_name)
- ->execute();
- module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_delete_field', $field_name);
- // Clear the cache.
- field_cache_clear(TRUE);
-}
-
-/**
- * Creates an instance of a field, binding it to a bundle.
- *
- * @param $instance
- * A field instance structure. The field_name and bundle properties
- * are required.
- * @throw
- * FieldException
- */
-function field_create_instance($instance) {
- // Check that the specified field exists.
- $field = field_read_field($instance['field_name']);
- if (empty($field)) {
- throw new FieldException("Attempt to create an instance of a field that doesn't exist.");
- }
-
- // TODO: Check that the specifed bundle exists.
-
- // TODO: Check that the widget type is known and can handle the field type ?
- // TODO: Check that the formatters are known and can handle the field type ?
- // TODO: Check that the display build modes are known for the object type ?
- // Those checks should probably happen in _field_write_instance() ?
- // Problem : this would mean that a UI module cannot update an instance with a disabled formatter.
-
- // Ensure the field instance is unique.
- // TODO : do we want specific messages when clashing with a disabled or inactive instance ?
- $prior_instance = field_read_instance($instance['field_name'], $instance['bundle'], array('include_inactive' => TRUE, 'include_deleted' => TRUE));
- if (!empty($prior_instance)) {
- throw new FieldException('Attempt to create a field instance which already exists.');
- }
-
- _field_write_instance($instance);
-
- module_invoke_all('field_create_instance', $instance);
-
- // Clear caches
- field_cache_clear();
- return FALSE;
-}
-
-/*
- * Update an instance of a field.
- *
- * @param $instance
- * An associative array represeting an instance structure. The required
- * keys and values are:
- * field_name: The name of an existing field.
- * bundle: The bundle this field belongs to.
- * Any other properties specified in $instance overwrite the
- * existing values for the instance.
- * @throw
- * FieldException
- * @see field_create_instance()
- */
-function field_update_instance($instance) {
- // Check that the specified field exists.
- $field = field_read_field($instance['field_name']);
- if (empty($field)) {
- throw new FieldException("Attempt to update an instance of a nonexistent field.");
- }
-
- // Check that the field instance exists (even if it is inactive, since we
- // want to be able to replace inactive widgets with new ones).
- $prior_instance = field_read_instance($instance['field_name'], $instance['bundle'], array('include_inactive' => TRUE));
- if (empty($prior_instance)) {
- throw new FieldException("Attempt to update a field instance that doesn't exist.");
- }
-
- _field_write_instance($instance, TRUE);
-
- // Clear caches.
- field_cache_clear();
-}
-
-/**
- * Store an instance record in the field configuration database.
- *
- * @param $instance
- * An instance structure.
- * @param $update
- * Whether this is a new or existing instance.
- */
-function _field_write_instance($instance, $update = FALSE) {
- $field = field_read_field($instance['field_name']);
- $field_type = field_info_field_types($field['type']);
-
- // Set defaults.
- $instance += array(
- 'settings' => array(),
- 'display' => array(),
- 'widget' => array(),
- 'required' => FALSE,
- 'label' => $instance['field_name'],
- 'description' => '',
- 'weight' => 0,
- 'deleted' => 0,
- );
-
- // Set default instance settings.
- $instance['settings'] += field_info_instance_settings($field['type']);
-
- // Set default widget and settings.
- $instance['widget'] += array(
- // TODO: what if no 'default_widget' specified ?
- 'type' => $field_type['default_widget'],
- 'settings' => array(),
- );
- // Check widget module.
- $widget_type = field_info_widget_types($instance['widget']['type']);
- $widget_module = $widget_type['module'];
- $widget_active = module_exists($widget_module);
- $instance['widget']['settings'] += field_info_widget_settings($instance['widget']['type']);
- $instance['widget']['module'] = $widget_module;
- $instance['widget']['active'] = $widget_active;
-
- // Make sure there is at least display info for the 'full' context.
- $instance['display'] += array(
- 'full' => array(),
- );
- // Set default display settings for each context.
- foreach ($instance['display'] as $context => $display) {
- $instance['display'][$context] += array(
- 'label' => 'above',
- 'exclude' => 0,
- // TODO: what if no 'default_formatter' specified ?
- 'type' => $field_type['default_formatter'],
- 'settings' => array(),
- );
- $formatter_type = field_info_formatter_types($instance['display'][$context]['type']);
- // TODO : 'hidden' will raise PHP warnings.
- $instance['display'][$context]['module'] = $formatter_type['module'];
- $instance['display'][$context]['settings'] += field_info_formatter_settings($instance['display'][$context]['type']);
- }
-
- // Create $data to contain everything from $instance that does not
- // have its own column, and thus will be stored serialized.
- $data = $instance;
- unset($data['field_name'], $data['bundle'], $data['widget']['type'], $data['weight'], $data['deleted']);
-
- $record = array(
- 'field_name' => $instance['field_name'],
- 'bundle' => $instance['bundle'],
- 'widget_type' => $instance['widget']['type'],
- 'widget_module' => $widget_module,
- 'widget_active' => $widget_active,
- 'weight' => $instance['weight'],
- 'data' => $data,
- 'deleted' => $instance['deleted'],
- );
- // We need to tell drupal_update_record() the primary keys to trigger an
- // update.
- $primary_keys = $update ? array('field_name', 'bundle') : array();
- drupal_write_record('field_config_instance', $record, $primary_keys);
-}
-
-/**
- * Read a single instance record directly from the database. Generally,
- * you should use the field_info_instance() instead.
- *
- * @param $field_name
- * The field name to read.
- * @param $bundle
- * The bundle to which the field is bound.
- * @param array $include_additional
- * The default behavior of this function is to not return an instance that
- * is inactive or has been deleted. Setting
- * $include_additional['include_inactive'] or
- * $include_additional['include_deleted'] to TRUE will override this
- * behavior.
- * @return
- * An instance structure, or FALSE.
- */
-function field_read_instance($field_name, $bundle, $include_additional = array()) {
- $instances = field_read_instances(array('field_name' => $field_name, 'bundle' => $bundle), $include_additional);
- return $instances ? current($instances) : FALSE;
-}
-
-/**
- * Read in field instances that match an array of conditions.
- *
- * @param $param
- * An array of properties to use in selecting a field
- * instance. Valid keys include any column of the
- * field_config_instance table. If NULL, all instances will be returned.
- * @param $include_additional
- * The default behavior of this function is to not return field
- * instances that are inactive or have been marked deleted. Setting
- * $include_additional['include_inactive'] or
- * $include_additional['include_deleted'] to TRUE will override this
- * behavior.
- * @return
- * An array of instances matching the arguments.
- */
-function field_read_instances($params = array(), $include_additional = array()) {
- $query = db_select('field_config_instance', 'fci', array('fetch' => PDO::FETCH_ASSOC));
- $query->join('field_config', 'fc', 'fc.field_name = fci.field_name');
- $query->fields('fci');
- #$query->fields('fc', array('type'));
-
- // Turn the conditions into a query.
- foreach ($params as $key => $value) {
- $query->condition('fci.' . $key, $value);
- }
- $query->condition('fc.active', 1);
- if (!isset($include_additional['include_inactive']) || !$include_additional['include_inactive']) {
- $query->condition('fci.widget_active', 1);
- }
- if (!isset($include_additional['include_deleted']) || !$include_additional['include_deleted']) {
- $query->condition('fc.deleted', 0);
- $query->condition('fci.deleted', 0);
- }
-
- $instances = array();
- $results = $query->execute();
-
- foreach ($results as $record) {
- $instance = unserialize($record['data']);
- $instance['field_name'] = $record['field_name'];
- $instance['bundle'] = $record['bundle'];
- $instance['weight'] = $record['weight'];
- $instance['deleted'] = $record['deleted'];
- $instance['widget']['type'] = $record['widget_type'];
- $instance['widget']['module'] = $record['widget_module'];
- $instance['widget']['active'] = $record['widget_active'];
-
- // TODO D7 : Set default widget settings, default instance settings, default display settings.
- // (the modules that defined them might have changed since the instance was last saved).
-
- module_invoke_all('field_read_instance', $instance);
- $instances[] = $instance;
- }
- return $instances;
-}
-
-/**
- * Mark a field instance for deletion, including all data associated with
- * it.
- *
- * @param $field_name
- * The name of the field whose instance will be deleted.
- * @param $bundle
- * The bundle for the instance which will be deleted.
- */
-function field_delete_instance($field_name, $bundle) {
- // Mark the field instance for deletion.
- db_update('field_config_instance')
- ->fields(array('deleted' => 1))
- ->condition('field_name', $field_name)
- ->condition('bundle', $bundle)
- ->execute();
-
- // Mark all data associated with the field for deletion.
- module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_delete_instance', $field_name, $bundle);
- // Clear the cache.
- field_cache_clear();
-}
-
-/**
- * @} End of "defgroup field_crud".
+<?php
+
+class FieldException extends Exception {}
+
+/**
+ * @defgroup field_structs Field API data structures
+ * @{
+ * Represent Field API fields and instances.
+ *
+ * The Field API defines two primary data structures, Field and
+ * Instance, and the concept of a Bundle. A Field defines a
+ * particular type of data that can be attached to objects. A Field
+ * Instance is a Field attached to a single Bundle. A Bundle is a set
+ * of fields that are treated as a group by the Field Attach API.
+ *
+ * For example, suppose a site administrator wants Article nodes to
+ * have a subtitle and photo. Using the Field API or some UI module,
+ * the administrator creates a field named 'subtitle' of type 'text'
+ * and a field named 'photo' of type 'image'. The administrator
+ * (again, via a UI) creates two Field Instances, one attaching the
+ * field 'subtitle' to the bundle 'article' and one attaching the field
+ * 'photo' to the bundle 'article'. When the node system uses the
+ * Field Attach API to load all fields for an Article node, it passes
+ * the node's content type (which is 'article') as the node's bundle.
+ * field_attach_load() then loads the 'subtitle' and 'photo' fields
+ * because they are both attached to the bundle 'article'.
+ *
+ * Field objects are (currently) represented as an array of key/value
+ * pairs. The object properties are:
+ *
+ * @param array $field:
+ * - field_name (string)
+ * The name of the field. Each field name is unique within Field API.
+ * When a field is attached to an object, the field's data is stored
+ * in $object->$field_name.
+ * - type (string)
+ * The type of the field, such as 'text' or 'image'. Field types
+ * are defined by modules that implement hook_field_into().
+ * - cardinality (integer)
+ * The number of values the field can hold. Legal values are any
+ * positive integer or FIELD_CARDINALITY_UNLIMITED.
+ * - locked (integer)
+ * TODO: undefined.
+ * - 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.
+ * - 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.
+ *
+ * Field Instance objects are (currently) represented as an array of
+ * key/value pairs. The object properties are:
+ *
+ * @param array $instance:
+ * - field_name (string)
+ * The name of field attached by this instance.
+ * - 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.
+ * - weight (float)
+ * The order in which the field should be sorted relative
+ * to other fields when used with this bundle. The weight affects
+ * ordering in both forms (see field_attach_form()) and rendered output
+ * (see field_attach_view()).
+ * TODO - this should probably become a context setting so that
+ * the weight can be different in the form and in various other
+ * contexts.
+ * - 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.
+ * - 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().
+ * - module (string, read-only)
+ * The name of the module that implements the widget type.
+ * - active (integer, read-only)
+ * TRUE if the module that implements the widget type is currently
+ * enabled, FALSE otherwise.
+ * - 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.
+ * - display (array)
+ * A sub-array of key/value pairs identifying display contexts
+ * and the way the field should be displayed in that context.
+ * TODO more work to do here.
+ * - (context_1)
+ * - label
+ * - exclude
+ * - type
+ * - settings
+ * - ...
+ * - module (internal)
+ * - (context 2)
+ * - ...
+ *
+ * TODO D7 : document max length for field types, widget types,
+ * formatter names...
+ */
+/**
+ * @} End of "defgroup field_structs".
+ */
+
+/**
+ * @defgroup field_crud Field CRUD API
+ * @{
+ * Create, update, and delete Field API fields, bundles, and instances.
+ *
+ * Modules use this API, often in hook_install(), to create custom
+ * data structures. UI modules will use it to create a user interface.
+ *
+ * The Field CRUD API uses
+ * @link field_structs Field API data structures @endlink.
+ */
+
+/**
+ * Create a field. This function does not bind the field to any
+ * bundle; use field_create_instance for that.
+ *
+ * @param $field
+ * A field structure. The field_name and type properties are required.
+ * @throw
+ * FieldException
+ */
+function field_create_field($field) {
+ // Field name is required.
+ if (empty($field['field_name'])) {
+ throw new FieldException('Attempt to create an unnamed field.');
+ }
+ // Field name cannot contain invalid characters.
+ if (preg_match('/[^a-z0-9_]/', $field['field_name'])) {
+ throw new FieldException('Attempt to create a field with invalid characters. Only alphanumeric characters and underscores are allowed.');
+ }
+
+ // TODO: check that field_name < 32 chars.
+
+ // Check that the field type is known.
+ $field_type = field_info_field_types($field['type']);
+ if (!$field_type) {
+ throw new FieldException(t('Attempt to create a field of unknown type %type.', array('%type' => $field['type'])));
+ }
+
+ // Ensure the field name is unique. We also check disabled or deleted fields.
+ // TODO : do we want specific messages when clashing with a disabled or inactive field ?
+ $prior_field = field_read_field($field['field_name'], array('include_inactive' => TRUE, 'include_deleted' => TRUE));
+ if (!empty($prior_field)) {
+ throw new FieldException(t('Attempt to create field name %name which already exists.', array('%name' => $field['field_name'])));
+ }
+
+ $field += array(
+ 'cardinality' => 1,
+ 'locked' => FALSE,
+ 'settings' => array(),
+ );
+ $module = $field_type['module'];
+ // Create all per-field-type properties (needed here as long as we have
+ // settings that impact column definitions).
+ $field['settings'] += field_info_field_settings($field['type']);
+ $field['module'] = $module;
+ $field['active'] = 1;
+ $field['deleted'] = 0;
+ // Create the data table. We need to populate the field columns, even though
+ // we don't actually store them.
+ $field['columns'] = (array) module_invoke($field['module'], 'field_columns', $field);
+ module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_create_field', $field);
+
+ drupal_write_record('field_config', $field);
+
+ // Clear caches
+ field_cache_clear(TRUE);
+}
+
+/**
+ * Read a single field record directly from the database. Generally,
+ * you should use the field_info_field() instead.
+ *
+ * @param $field_name
+ * The field name to read.
+ * @param array $include_additional
+ * The default behavior of this function is to not return a field that
+ * is inactive or has been deleted. Setting
+ * $include_additional['include_inactive'] or
+ * $include_additional['include_deleted'] to TRUE will override this
+ * behavior.
+ * @return
+ * A field structure, or FALSE.
+ */
+function field_read_field($field_name, $include_additional = array()) {
+ $fields = field_read_fields(array('field_name' => $field_name), $include_additional);
+ return $fields ? current($fields) : FALSE;
+}
+
+/**
+ * Read in fields that match an array of conditions.
+ *
+ * @param array $params
+ * An array of conditions to match against.
+ * @param array $include_additional
+ * The default behavior of this function is to not return fields that
+ * are inactive or have been deleted. Setting
+ * $include_additional['include_inactive'] or
+ * $include_additional['include_deleted'] to TRUE will override this
+ * behavior.
+ * @return
+ * An array of fields matching $params.
+ */
+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) {
+ $query->condition($key, $value);
+ }
+ if (!isset($include_additional['include_inactive']) || !$include_additional['include_inactive']) {
+ $query->condition('fc.active', 1);
+ }
+ if (!isset($include_additional['include_deleted']) || !$include_additional['include_deleted']) {
+ $query->condition('fc.deleted', 0);
+ }
+
+ $fields = array();
+ $results = $query->execute();
+ foreach ($results as $field) {
+ // drupal_write_record() writes an empty string for empty arrays.
+ $field['settings'] = $field['settings'] ? unserialize($field['settings']) : array();
+
+ module_invoke_all('field_read_field', $field);
+
+ // Populate storage columns.
+ $field['columns'] = (array) module_invoke($field['module'], 'field_columns', $field);
+
+ $fields[$field['field_name']] = $field;
+ }
+ return $fields;
+}
+
+/**
+ * Mark a field for deletion, including all its instances and all data
+ * associated with it.
+ *
+ * @param $field_name
+ * The field name to delete.
+ */
+function field_delete_field($field_name) {
+ // Mark the field for deletion.
+ db_update('field_config')
+ ->fields(array('deleted' => 1))
+ ->condition('field_name', $field_name)
+ ->execute();
+
+ // Mark any instances of the field for deletion.
+ db_update('field_config_instance')
+ ->fields(array('deleted' => 1))
+ ->condition('field_name', $field_name)
+ ->execute();
+ module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_delete_field', $field_name);
+ // Clear the cache.
+ field_cache_clear(TRUE);
+}
+
+/**
+ * Creates an instance of a field, binding it to a bundle.
+ *
+ * @param $instance
+ * A field instance structure. The field_name and bundle properties
+ * are required.
+ * @throw
+ * FieldException
+ */
+function field_create_instance($instance) {
+ // Check that the specified field exists.
+ $field = field_read_field($instance['field_name']);
+ if (empty($field)) {
+ throw new FieldException("Attempt to create an instance of a field that doesn't exist.");
+ }
+
+ // TODO: Check that the specifed bundle exists.
+
+ // TODO: Check that the widget type is known and can handle the field type ?
+ // TODO: Check that the formatters are known and can handle the field type ?
+ // TODO: Check that the display build modes are known for the object type ?
+ // Those checks should probably happen in _field_write_instance() ?
+ // Problem : this would mean that a UI module cannot update an instance with a disabled formatter.
+
+ // Ensure the field instance is unique.
+ // TODO : do we want specific messages when clashing with a disabled or inactive instance ?
+ $prior_instance = field_read_instance($instance['field_name'], $instance['bundle'], array('include_inactive' => TRUE, 'include_deleted' => TRUE));
+ if (!empty($prior_instance)) {
+ throw new FieldException('Attempt to create a field instance which already exists.');
+ }
+
+ _field_write_instance($instance);
+
+ module_invoke_all('field_create_instance', $instance);
+
+ // Clear caches
+ field_cache_clear();
+ return FALSE;
+}
+
+/*
+ * Update an instance of a field.
+ *
+ * @param $instance
+ * An associative array represeting an instance structure. The required
+ * keys and values are:
+ * field_name: The name of an existing field.
+ * bundle: The bundle this field belongs to.
+ * Any other properties specified in $instance overwrite the
+ * existing values for the instance.
+ * @throw
+ * FieldException
+ * @see field_create_instance()
+ */
+function field_update_instance($instance) {
+ // Check that the specified field exists.
+ $field = field_read_field($instance['field_name']);
+ if (empty($field)) {
+ throw new FieldException("Attempt to update an instance of a nonexistent field.");
+ }
+
+ // Check that the field instance exists (even if it is inactive, since we
+ // want to be able to replace inactive widgets with new ones).
+ $prior_instance = field_read_instance($instance['field_name'], $instance['bundle'], array('include_inactive' => TRUE));
+ if (empty($prior_instance)) {
+ throw new FieldException("Attempt to update a field instance that doesn't exist.");
+ }
+
+ _field_write_instance($instance, TRUE);
+
+ // Clear caches.
+ field_cache_clear();
+}
+
+/**
+ * Store an instance record in the field configuration database.
+ *
+ * @param $instance
+ * An instance structure.
+ * @param $update
+ * Whether this is a new or existing instance.
+ */
+function _field_write_instance($instance, $update = FALSE) {
+ $field = field_read_field($instance['field_name']);
+ $field_type = field_info_field_types($field['type']);
+
+ // Set defaults.
+ $instance += array(
+ 'settings' => array(),
+ 'display' => array(),
+ 'widget' => array(),
+ 'required' => FALSE,
+ 'label' => $instance['field_name'],
+ 'description' => '',
+ 'weight' => 0,
+ 'deleted' => 0,
+ );
+
+ // Set default instance settings.
+ $instance['settings'] += field_info_instance_settings($field['type']);
+
+ // Set default widget and settings.
+ $instance['widget'] += array(
+ // TODO: what if no 'default_widget' specified ?
+ 'type' => $field_type['default_widget'],
+ 'settings' => array(),
+ );
+ // Check widget module.
+ $widget_type = field_info_widget_types($instance['widget']['type']);
+ $widget_module = $widget_type['module'];
+ $widget_active = module_exists($widget_module);
+ $instance['widget']['settings'] += field_info_widget_settings($instance['widget']['type']);
+ $instance['widget']['module'] = $widget_module;
+ $instance['widget']['active'] = $widget_active;
+
+ // Make sure there is at least display info for the 'full' context.
+ $instance['display'] += array(
+ 'full' => array(),
+ );
+ // Set default display settings for each context.
+ foreach ($instance['display'] as $context => $display) {
+ $instance['display'][$context] += array(
+ 'label' => 'above',
+ 'exclude' => 0,
+ // TODO: what if no 'default_formatter' specified ?
+ 'type' => $field_type['default_formatter'],
+ 'settings' => array(),
+ );
+ $formatter_type = field_info_formatter_types($instance['display'][$context]['type']);
+ // TODO : 'hidden' will raise PHP warnings.
+ $instance['display'][$context]['module'] = $formatter_type['module'];
+ $instance['display'][$context]['settings'] += field_info_formatter_settings($instance['display'][$context]['type']);
+ }
+
+ // Create $data to contain everything from $instance that does not
+ // have its own column, and thus will be stored serialized.
+ $data = $instance;
+ unset($data['field_name'], $data['bundle'], $data['widget']['type'], $data['weight'], $data['deleted']);
+
+ $record = array(
+ 'field_name' => $instance['field_name'],
+ 'bundle' => $instance['bundle'],
+ 'widget_type' => $instance['widget']['type'],
+ 'widget_module' => $widget_module,
+ 'widget_active' => $widget_active,
+ 'weight' => $instance['weight'],
+ 'data' => $data,
+ 'deleted' => $instance['deleted'],
+ );
+ // We need to tell drupal_update_record() the primary keys to trigger an
+ // update.
+ $primary_keys = $update ? array('field_name', 'bundle') : array();
+ drupal_write_record('field_config_instance', $record, $primary_keys);
+}
+
+/**
+ * Read a single instance record directly from the database. Generally,
+ * you should use the field_info_instance() instead.
+ *
+ * @param $field_name
+ * The field name to read.
+ * @param $bundle
+ * The bundle to which the field is bound.
+ * @param array $include_additional
+ * The default behavior of this function is to not return an instance that
+ * is inactive or has been deleted. Setting
+ * $include_additional['include_inactive'] or
+ * $include_additional['include_deleted'] to TRUE will override this
+ * behavior.
+ * @return
+ * An instance structure, or FALSE.
+ */
+function field_read_instance($field_name, $bundle, $include_additional = array()) {
+ $instances = field_read_instances(array('field_name' => $field_name, 'bundle' => $bundle), $include_additional);
+ return $instances ? current($instances) : FALSE;
+}
+
+/**
+ * Read in field instances that match an array of conditions.
+ *
+ * @param $param
+ * An array of properties to use in selecting a field
+ * instance. Valid keys include any column of the
+ * field_config_instance table. If NULL, all instances will be returned.
+ * @param $include_additional
+ * The default behavior of this function is to not return field
+ * instances that are inactive or have been marked deleted. Setting
+ * $include_additional['include_inactive'] or
+ * $include_additional['include_deleted'] to TRUE will override this
+ * behavior.
+ * @return
+ * An array of instances matching the arguments.
+ */
+function field_read_instances($params = array(), $include_additional = array()) {
+ $query = db_select('field_config_instance', 'fci', array('fetch' => PDO::FETCH_ASSOC));
+ $query->join('field_config', 'fc', 'fc.field_name = fci.field_name');
+ $query->fields('fci');
+ #$query->fields('fc', array('type'));
+
+ // Turn the conditions into a query.
+ foreach ($params as $key => $value) {
+ $query->condition('fci.' . $key, $value);
+ }
+ $query->condition('fc.active', 1);
+ if (!isset($include_additional['include_inactive']) || !$include_additional['include_inactive']) {
+ $query->condition('fci.widget_active', 1);
+ }
+ if (!isset($include_additional['include_deleted']) || !$include_additional['include_deleted']) {
+ $query->condition('fc.deleted', 0);
+ $query->condition('fci.deleted', 0);
+ }
+
+ $instances = array();
+ $results = $query->execute();
+
+ foreach ($results as $record) {
+ $instance = unserialize($record['data']);
+ $instance['field_name'] = $record['field_name'];
+ $instance['bundle'] = $record['bundle'];
+ $instance['weight'] = $record['weight'];
+ $instance['deleted'] = $record['deleted'];
+ $instance['widget']['type'] = $record['widget_type'];
+ $instance['widget']['module'] = $record['widget_module'];
+ $instance['widget']['active'] = $record['widget_active'];
+
+ // TODO D7 : Set default widget settings, default instance settings, default display settings.
+ // (the modules that defined them might have changed since the instance was last saved).
+
+ module_invoke_all('field_read_instance', $instance);
+ $instances[] = $instance;
+ }
+ return $instances;
+}
+
+/**
+ * Mark a field instance for deletion, including all data associated with
+ * it.
+ *
+ * @param $field_name
+ * The name of the field whose instance will be deleted.
+ * @param $bundle
+ * The bundle for the instance which will be deleted.
+ */
+function field_delete_instance($field_name, $bundle) {
+ // Mark the field instance for deletion.
+ db_update('field_config_instance')
+ ->fields(array('deleted' => 1))
+ ->condition('field_name', $field_name)
+ ->condition('bundle', $bundle)
+ ->execute();
+
+ // Mark all data associated with the field for deletion.
+ module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_delete_instance', $field_name, $bundle);
+ // Clear the cache.
+ field_cache_clear();
+}
+
+/**
+ * @} End of "defgroup field_crud".
*/ \ No newline at end of file
diff --git a/modules/field/field.form.inc b/modules/field/field.form.inc
index 041fbabbe..dff18fb2c 100644
--- a/modules/field/field.form.inc
+++ b/modules/field/field.form.inc
@@ -1,408 +1,409 @@
-<?php
-// $Id$
-// TODO : merge with field.default.inc ?
-
-/**
- * Create a separate form element for each field.
- */
-function field_default_form($obj_type, $object, $field, $instance, $items, &$form, &$form_state, $get_delta = NULL) {
- // This could be called with no object, as when a UI module creates a
- // dummy form to set default values.
- if ($object) {
- list($id, ,) = field_attach_extract_ids($obj_type, $object);
- }
- $addition = array();
-
- $field_name = $field['field_name'];
-
- // If the field is not accessible, don't add anything. The field value will
- // be left unchanged on update, or considered empty on insert.
- // TODO : if/when field_attach_insert() takes care of default values,
- // unaccessible fields will automatically get the default value on insert.
- if (!field_access('edit', $field)) {
- return $addition;
- }
-
- // Put field information at the top of the form, so that it can be easily
- // retrieved.
- // Note : widgets and other form handling code should *always* fetch
- // field and instance information from $form['#fields'] rather than from
- // field_info_field(). This lets us build forms for 'variants' of a field,
- // for instance on admin screens.
- $form['#fields'][$field_name] = array(
- 'field' => $field,
- 'instance' => $instance,
- );
- // TODO : why do we need this ?
- $form['#cache'] = FALSE;
-
- // Populate widgets with default values if we're creating a new object.
- if (empty($items) && empty($id) && !empty($instance['default_value_function'])) {
- $items = array();
- $function = $instance['default_value_function'];
- if (drupal_function_exists($function)) {
- $items = $function($obj_type, $object, $field, $instance);
- }
- }
-
- $form_element = array();
-
- // 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) {
- $form_element = field_multiple_value_form($field, $instance, $items, $form, $form_state);
- }
- // If the widget is handling multiple values (e.g Options),
- // or if we are displaying an individual element, just get a single form
- // element and make it the $delta value.
- else {
- $delta = isset($get_delta) ? $get_delta : 0;
- $function = $instance['widget']['module'] . '_field_widget';
- if (drupal_function_exists($function)) {
- if ($element = $function($form, $form_state, $field, $instance, $items, $delta)) {
- $defaults = array(
- '#required' => $get_delta > 0 ? FALSE : $instance['required'],
- '#columns' => array_keys($field['columns']),
- '#title' => check_plain(t($instance['label'])),
- '#description' => field_filter_xss($instance['description']),
- '#delta' => $delta,
- '#field_name' => $field['field_name'],
- '#bundle' => $instance['bundle'],
- );
- $element = array_merge($element, $defaults);
- // If we're processing a specific delta value for a field where the
- // field module handles multiples, set the delta in the result.
- // For fields that handle their own processing, we can't make assumptions
- // about how the field is structured, just merge in the returned value.
- if (field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_DEFAULT) {
- $form_element[$delta] = $element;
- }
- else {
- $form_element = $element;
- }
- }
- }
- }
-
- if ($form_element) {
- $defaults = array(
- '#field_name' => $field['field_name'],
- '#tree' => TRUE,
- '#weight' => $instance['weight'],
- );
-
- $addition[$field['field_name']] = array_merge($form_element, $defaults);
- $form['#fields'][$field['field_name']]['form_path'] = array($field['field_name']);
- }
-
- return $addition;
-}
-
-/**
- * Special handling to create form elements for multiple values.
- *
- * Handles generic features for multiple fields:
- * - number of widgets
- * - AHAH-'add more' button
- * - drag-n-drop value reordering
- */
-function field_multiple_value_form($field, $instance, $items, &$form, &$form_state) {
- $field = field_info_field($instance['field_name']);
- $field_name = $field['field_name'];
-
- switch ($field['cardinality']) {
- case FIELD_CARDINALITY_UNLIMITED:
- $filled_items = field_set_empty($field, $items);
- $current_item_count = isset($form_state['field_item_count'][$field_name])
- ? $form_state['field_item_count'][$field_name]
- : count($items);
- // We always want at least one empty icon for the user to fill in.
- $max = ($current_item_count > count($filled_items))
- ? $current_item_count - 1
- : $current_item_count;
-
- break;
- default:
- $max = $field['cardinality'] - 1;
- break;
- }
-
- $title = check_plain(t($instance['label']));
- $description = field_filter_xss(t($instance['description']));
-
- $form_element = array(
- '#theme' => 'field_multiple_value_form',
- '#multiple' => $field['cardinality'],
- '#title' => $title,
- '#required' => $instance['required'],
- '#description' => $description,
- );
-
- $function = $instance['widget']['module'] . '_field_widget';
- if (drupal_function_exists($function)) {
- for ($delta = 0; $delta <= $max; $delta++) {
- if ($element = $function($form, $form_state, $field, $instance, $items, $delta)) {
- $multiple = $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED;
- $defaults = array(
- '#title' => $multiple ? '' : $title,
- '#description' => $multiple ? '' : $description,
- '#required' => $delta == 0 && $instance['required'],
- '#weight' => $delta,
- '#delta' => $delta,
- '#columns' => array_keys($field['columns']),
- '#field_name' => $field_name,
- '#bundle' => $instance['bundle'],
- );
-
- // Add an input field for the delta (drag-n-drop reordering), which will
- // be hidden by tabledrag js behavior.
- if ($multiple) {
- // We name the element '_weight' to avoid clashing with column names
- // defined by field modules.
- $element['_weight'] = array(
- '#type' => 'weight',
- '#delta' => $max, // this 'delta' is the 'weight' element's property
- '#default_value' => isset($items[$delta]['_weight']) ? $items[$delta]['_weight'] : $delta,
- '#weight' => 100,
- );
- }
-
- $form_element[$delta] = array_merge($element, $defaults);
- }
- }
-
- // Add AHAH add more button, if not working with a programmed form.
- if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED && empty($form['#programmed'])) {
- // Make sure the form is cached so ahah can work.
- $form['#cache'] = TRUE;
- $bundle_name_url_str = str_replace('_', '-', $instance['bundle']);
- $field_name_url_str = str_replace('_', '-', $field_name);
-
- $form_element[$field_name . '_add_more'] = array(
- '#type' => 'submit',
- '#name' => $field_name . '_add_more',
- '#value' => t('Add another item'),
- '#weight' => $instance['weight'] + $max + 1,
- // Submit callback for disabled JavaScript.
- '#submit' => array('field_add_more_submit'),
- '#ahah' => array(
- 'path' => 'field/js_add_more/' . $bundle_name_url_str . '/' . $field_name_url_str,
- 'wrapper' => $field_name_url_str . '-items',
- 'method' => 'replace',
- 'effect' => 'fade',
- ),
- // When JS is disabled, the field_add_more_submit handler will find
- // the relevant field using these entries.
- '#field_name' => $field_name,
- '#bundle' => $instance['bundle'],
- );
-
- // Add wrappers for the fields and 'more' button.
- $form_element['#prefix'] = '<div class="clear-block" id="' . $field_name_url_str . '-add-more-wrapper"><div id="' . $field_name_url_str . '-items">';
- $form_element[$field_name . '_add_more']['#prefix'] = '<div class="field-add-more">';
- $form_element[$field_name . '_add_more']['#suffix'] = '</div></div></div>';
- }
- }
- return $form_element;
-}
-
-/**
- * Theme an individual form element.
- *
- * Combine multiple values into a table with drag-n-drop reordering.
- * TODO : convert to a template.
- */
-function theme_field_multiple_value_form($element) {
- $output = '';
-
- if ($element['#multiple'] > 1 || $element['#multiple'] == FIELD_CARDINALITY_UNLIMITED) {
- $table_id = $element['#field_name'] . '_values';
- $order_class = $element['#field_name'] . '-delta-order';
- $required = !empty($element['#required']) ? '<span class="form-required" title="' . t('This field is required. ') . '">*</span>' : '';
-
- $header = array(
- array(
- 'data' => t('!title: !required', array('!title' => $element['#title'], '!required' => $required)),
- 'colspan' => 2
- ),
- t('Order'),
- );
- $rows = array();
-
- // Sort items according to '_weight' (needed when the form comes back after
- // preview or failed validation)
- $items = array();
- foreach (element_children($element) as $key) {
- if ($key !== $element['#field_name'] . '_add_more') {
- $items[] = &$element[$key];
- }
- }
- usort($items, '_field_sort_items_value_helper');
-
- // Add the items as table rows.
- foreach ($items as $key => $item) {
- $item['_weight']['#attributes']['class'] = $order_class;
- $delta_element = drupal_render($item['_weight']);
- $cells = array(
- array('data' => '', 'class' => 'field-multiple-drag'),
- drupal_render($item),
- array('data' => $delta_element, 'class' => 'delta-order'),
- );
- $rows[] = array(
- 'data' => $cells,
- 'class' => 'draggable',
- );
- }
-
- $output .= theme('table', $header, $rows, array('id' => $table_id, 'class' => 'field-multiple-table'));
- $output .= $element['#description'] ? '<div class="description">' . $element['#description'] . '</div>' : '';
- $output .= drupal_render($element[$element['#field_name'] . '_add_more']);
-
- drupal_add_tabledrag($table_id, 'order', 'sibling', $order_class);
- }
- else {
- foreach (element_children($element) as $key) {
- $output .= drupal_render($element[$key]);
- }
- }
-
- return $output;
-}
-
-/**
- * Submit handler to add more choices to a field form. This handler is used when
- * JavaScript is not available. It makes changes to the form state and the
- * entire form is rebuilt during the page reload.
- */
-function field_add_more_submit($form, &$form_state) {
- // Set the form to rebuild and run submit handlers.
- if (isset($form['#builder_function']) && drupal_function_exists($form['#builder_function'])) {
- $form['#builder_function']($form, $form_state);
-
- // Make the changes we want to the form state.
- $field_name = $form_state['clicked_button']['#field_name'];
- if ($form_state['values'][$field_name][$field_name . '_add_more']) {
- $form_state['field_item_count'][$field_name] = count($form_state['values'][$field_name]);
- }
- }
-}
-
-/**
- * Menu callback for AHAH addition of new empty widgets.
- */
-function field_add_more_js($bundle_name, $field_name) {
- // Arguments are coming from the url, so we translate back dashes.
- $field_name = str_replace('-', '_', $field_name);
-
- $invalid = FALSE;
- if (empty($_POST['form_build_id'])) {
- // Invalid request.
- $invalid = TRUE;
- }
-
- // Retrieve the cached form.
- $form_state = array('submitted' => FALSE);
- $form_build_id = $_POST['form_build_id'];
- $form = form_get_cache($form_build_id, $form_state);
- if (!$form) {
- // Invalid form_build_id.
- $invalid = TRUE;
- }
-
- // Retrieve field information.
- $field = $form['#fields'][$field_name]['field'];
- $instance = $form['#fields'][$field_name]['instance'];
- $form_path = $form['#fields'][$field_name]['form_path'];
- if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED) {
- // Ivnalid
- $invalid = TRUE;
- }
-
- if ($invalid) {
- drupal_json(array('data' => ''));
- exit;
- }
-
- // We don't simply return a new empty widget row to append to existing ones,
- // because:
- // - ahah.js won't simply let us add a new row to a table
- // - attaching the 'draggable' behavior won't be easy
- // So we resort to rebuilding the whole table of widgets including the
- // existing ones, which makes us jump through a few hoops.
-
- // The form that we get from the cache is unbuilt. We need to build it so
- // that _value callbacks can be executed and $form_state['values'] populated.
- // We only want to affect $form_state['values'], not the $form itself
- // (built forms aren't supposed to enter the cache) nor the rest of
- // $form_state, so we use copies of $form and $form_state.
- $form_copy = $form;
- $form_state_copy = $form_state;
- $form_copy['#post'] = array();
- form_builder($_POST['form_id'], $form_copy, $form_state_copy);
- // Just grab the data we need.
- $form_state['values'] = $form_state_copy['values'];
- // Reset cached ids, so that they don't affect the actual form we output.
- form_clean_id(NULL, TRUE);
-
- // Sort the $form_state['values'] we just built *and* the incoming $_POST data
- // according to d-n-d reordering.
- unset($form_state['values'][$field_name][$field['field_name'] . '_add_more']);
- foreach ($_POST[$field_name] as $delta => $item) {
- $form_state['values'][$field_name][$delta]['_weight'] = $item['_weight'];
- }
- $form_state['values'][$field_name] = _field_sort_items($field, $form_state['values'][$field_name]);
- $_POST[$field_name] = _field_sort_items($field, $_POST[$field_name]);
-
- // Build our new form element for the whole field, asking for one more element.
- $form_state['field_item_count'] = array($field_name => count($_POST[$field_name]) + 1);
- $items = $form_state['values'][$field_name];
- $form_element = field_default_form(NULL, NULL, $field, $instance, $items, $form, $form_state);
- // Let other modules alter it.
- drupal_alter('form', $form_element, array(), 'field_add_more_js');
-
- // Add the new element at the right location in the (original, unbuilt) form.
- $target = &$form;
- foreach ($form_path as $key) {
- $target = &$target[$key];
- }
- $target = $form_element[$field_name];
-
- // Save the new definition of the form.
- $form_state['values'] = array();
- form_set_cache($form_build_id, $form, $form_state);
-
- // Build the new form against the incoming $_POST values so that we can
- // render the new element.
- $delta = max(array_keys($_POST[$field_name])) + 1;
- $_POST[$field_name][$delta]['_weight'] = $delta;
- $form_state = array('submitted' => FALSE);
- $form += array(
- '#post' => $_POST,
- '#programmed' => FALSE,
- );
- $form = form_builder($_POST['form_id'], $form, $form_state);
-
- // Render the new output.
- // We get fetch the form element from the built $form.
- $field_form = $form;
- foreach ($form_path as $key) {
- $field_form = $field_form[$key];
- }
- // We add a div around the new field to receive the ahah effect.
- $field_form[$delta]['#prefix'] = '<div class="ahah-new-field">' . (isset($field_form[$delta]['#prefix']) ? $field_form[$delta]['#prefix'] : '');
- $field_form[$delta]['#suffix'] = (isset($field_form[$delta]['#suffix']) ? $field_form[$delta]['#suffix'] : '') . '</div>';
- // TODO : this causes duplication of the wrapping divs
-
- // If a newly inserted widget contains AHAH behaviors, they normally won't
- // work because AHAH doesn't know about those - it just attaches to the exact
- // form elements that were initially specified in the Drupal.settings object.
- // The new ones didn't exist then, so we need to update Drupal.settings
- // by ourselves in order to let AHAH know about those new form elements.
- $javascript = drupal_add_js(NULL, NULL);
- $output_js = isset($javascript['setting']) ? '<script type="text/javascript">jQuery.extend(Drupal.settings, ' . drupal_to_js(call_user_func_array('array_merge_recursive', $javascript['setting'])) . ');</script>' : '';
-
- $output = theme('status_messages') . drupal_render($field_form) . $output_js;
- drupal_json(array('status' => TRUE, 'data' => $output));
- exit;
-}
+<?php
+// $Id$
+
+// TODO : merge with field.default.inc ?
+
+/**
+ * Create a separate form element for each field.
+ */
+function field_default_form($obj_type, $object, $field, $instance, $items, &$form, &$form_state, $get_delta = NULL) {
+ // This could be called with no object, as when a UI module creates a
+ // dummy form to set default values.
+ if ($object) {
+ list($id, ,) = field_attach_extract_ids($obj_type, $object);
+ }
+ $addition = array();
+
+ $field_name = $field['field_name'];
+
+ // If the field is not accessible, don't add anything. The field value will
+ // be left unchanged on update, or considered empty on insert.
+ // TODO : if/when field_attach_insert() takes care of default values,
+ // unaccessible fields will automatically get the default value on insert.
+ if (!field_access('edit', $field)) {
+ return $addition;
+ }
+
+ // Put field information at the top of the form, so that it can be easily
+ // retrieved.
+ // Note : widgets and other form handling code should *always* fetch
+ // field and instance information from $form['#fields'] rather than from
+ // field_info_field(). This lets us build forms for 'variants' of a field,
+ // for instance on admin screens.
+ $form['#fields'][$field_name] = array(
+ 'field' => $field,
+ 'instance' => $instance,
+ );
+ // TODO : why do we need this ?
+ $form['#cache'] = FALSE;
+
+ // Populate widgets with default values if we're creating a new object.
+ if (empty($items) && empty($id) && !empty($instance['default_value_function'])) {
+ $items = array();
+ $function = $instance['default_value_function'];
+ if (drupal_function_exists($function)) {
+ $items = $function($obj_type, $object, $field, $instance);
+ }
+ }
+
+ $form_element = array();
+
+ // 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) {
+ $form_element = field_multiple_value_form($field, $instance, $items, $form, $form_state);
+ }
+ // If the widget is handling multiple values (e.g Options),
+ // or if we are displaying an individual element, just get a single form
+ // element and make it the $delta value.
+ else {
+ $delta = isset($get_delta) ? $get_delta : 0;
+ $function = $instance['widget']['module'] . '_field_widget';
+ if (drupal_function_exists($function)) {
+ if ($element = $function($form, $form_state, $field, $instance, $items, $delta)) {
+ $defaults = array(
+ '#required' => $get_delta > 0 ? FALSE : $instance['required'],
+ '#columns' => array_keys($field['columns']),
+ '#title' => check_plain(t($instance['label'])),
+ '#description' => field_filter_xss($instance['description']),
+ '#delta' => $delta,
+ '#field_name' => $field['field_name'],
+ '#bundle' => $instance['bundle'],
+ );
+ $element = array_merge($element, $defaults);
+ // If we're processing a specific delta value for a field where the
+ // field module handles multiples, set the delta in the result.
+ // For fields that handle their own processing, we can't make assumptions
+ // about how the field is structured, just merge in the returned value.
+ if (field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_DEFAULT) {
+ $form_element[$delta] = $element;
+ }
+ else {
+ $form_element = $element;
+ }
+ }
+ }
+ }
+
+ if ($form_element) {
+ $defaults = array(
+ '#field_name' => $field['field_name'],
+ '#tree' => TRUE,
+ '#weight' => $instance['weight'],
+ );
+
+ $addition[$field['field_name']] = array_merge($form_element, $defaults);
+ $form['#fields'][$field['field_name']]['form_path'] = array($field['field_name']);
+ }
+
+ return $addition;
+}
+
+/**
+ * Special handling to create form elements for multiple values.
+ *
+ * Handles generic features for multiple fields:
+ * - number of widgets
+ * - AHAH-'add more' button
+ * - drag-n-drop value reordering
+ */
+function field_multiple_value_form($field, $instance, $items, &$form, &$form_state) {
+ $field = field_info_field($instance['field_name']);
+ $field_name = $field['field_name'];
+
+ switch ($field['cardinality']) {
+ case FIELD_CARDINALITY_UNLIMITED:
+ $filled_items = field_set_empty($field, $items);
+ $current_item_count = isset($form_state['field_item_count'][$field_name])
+ ? $form_state['field_item_count'][$field_name]
+ : count($items);
+ // We always want at least one empty icon for the user to fill in.
+ $max = ($current_item_count > count($filled_items))
+ ? $current_item_count - 1
+ : $current_item_count;
+
+ break;
+ default:
+ $max = $field['cardinality'] - 1;
+ break;
+ }
+
+ $title = check_plain(t($instance['label']));
+ $description = field_filter_xss(t($instance['description']));
+
+ $form_element = array(
+ '#theme' => 'field_multiple_value_form',
+ '#multiple' => $field['cardinality'],
+ '#title' => $title,
+ '#required' => $instance['required'],
+ '#description' => $description,
+ );
+
+ $function = $instance['widget']['module'] . '_field_widget';
+ if (drupal_function_exists($function)) {
+ for ($delta = 0; $delta <= $max; $delta++) {
+ if ($element = $function($form, $form_state, $field, $instance, $items, $delta)) {
+ $multiple = $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED;
+ $defaults = array(
+ '#title' => $multiple ? '' : $title,
+ '#description' => $multiple ? '' : $description,
+ '#required' => $delta == 0 && $instance['required'],
+ '#weight' => $delta,
+ '#delta' => $delta,
+ '#columns' => array_keys($field['columns']),
+ '#field_name' => $field_name,
+ '#bundle' => $instance['bundle'],
+ );
+
+ // Add an input field for the delta (drag-n-drop reordering), which will
+ // be hidden by tabledrag js behavior.
+ if ($multiple) {
+ // We name the element '_weight' to avoid clashing with column names
+ // defined by field modules.
+ $element['_weight'] = array(
+ '#type' => 'weight',
+ '#delta' => $max, // this 'delta' is the 'weight' element's property
+ '#default_value' => isset($items[$delta]['_weight']) ? $items[$delta]['_weight'] : $delta,
+ '#weight' => 100,
+ );
+ }
+
+ $form_element[$delta] = array_merge($element, $defaults);
+ }
+ }
+
+ // Add AHAH add more button, if not working with a programmed form.
+ if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED && empty($form['#programmed'])) {
+ // Make sure the form is cached so ahah can work.
+ $form['#cache'] = TRUE;
+ $bundle_name_url_str = str_replace('_', '-', $instance['bundle']);
+ $field_name_url_str = str_replace('_', '-', $field_name);
+
+ $form_element[$field_name . '_add_more'] = array(
+ '#type' => 'submit',
+ '#name' => $field_name . '_add_more',
+ '#value' => t('Add another item'),
+ '#weight' => $instance['weight'] + $max + 1,
+ // Submit callback for disabled JavaScript.
+ '#submit' => array('field_add_more_submit'),
+ '#ahah' => array(
+ 'path' => 'field/js_add_more/' . $bundle_name_url_str . '/' . $field_name_url_str,
+ 'wrapper' => $field_name_url_str . '-items',
+ 'method' => 'replace',
+ 'effect' => 'fade',
+ ),
+ // When JS is disabled, the field_add_more_submit handler will find
+ // the relevant field using these entries.
+ '#field_name' => $field_name,
+ '#bundle' => $instance['bundle'],
+ );
+
+ // Add wrappers for the fields and 'more' button.
+ $form_element['#prefix'] = '<div class="clear-block" id="' . $field_name_url_str . '-add-more-wrapper"><div id="' . $field_name_url_str . '-items">';
+ $form_element[$field_name . '_add_more']['#prefix'] = '<div class="field-add-more">';
+ $form_element[$field_name . '_add_more']['#suffix'] = '</div></div></div>';
+ }
+ }
+ return $form_element;
+}
+
+/**
+ * Theme an individual form element.
+ *
+ * Combine multiple values into a table with drag-n-drop reordering.
+ * TODO : convert to a template.
+ */
+function theme_field_multiple_value_form($element) {
+ $output = '';
+
+ if ($element['#multiple'] > 1 || $element['#multiple'] == FIELD_CARDINALITY_UNLIMITED) {
+ $table_id = $element['#field_name'] . '_values';
+ $order_class = $element['#field_name'] . '-delta-order';
+ $required = !empty($element['#required']) ? '<span class="form-required" title="' . t('This field is required. ') . '">*</span>' : '';
+
+ $header = array(
+ array(
+ 'data' => t('!title: !required', array('!title' => $element['#title'], '!required' => $required)),
+ 'colspan' => 2
+ ),
+ t('Order'),
+ );
+ $rows = array();
+
+ // Sort items according to '_weight' (needed when the form comes back after
+ // preview or failed validation)
+ $items = array();
+ foreach (element_children($element) as $key) {
+ if ($key !== $element['#field_name'] . '_add_more') {
+ $items[] = &$element[$key];
+ }
+ }
+ usort($items, '_field_sort_items_value_helper');
+
+ // Add the items as table rows.
+ foreach ($items as $key => $item) {
+ $item['_weight']['#attributes']['class'] = $order_class;
+ $delta_element = drupal_render($item['_weight']);
+ $cells = array(
+ array('data' => '', 'class' => 'field-multiple-drag'),
+ drupal_render($item),
+ array('data' => $delta_element, 'class' => 'delta-order'),
+ );
+ $rows[] = array(
+ 'data' => $cells,
+ 'class' => 'draggable',
+ );
+ }
+
+ $output .= theme('table', $header, $rows, array('id' => $table_id, 'class' => 'field-multiple-table'));
+ $output .= $element['#description'] ? '<div class="description">' . $element['#description'] . '</div>' : '';
+ $output .= drupal_render($element[$element['#field_name'] . '_add_more']);
+
+ drupal_add_tabledrag($table_id, 'order', 'sibling', $order_class);
+ }
+ else {
+ foreach (element_children($element) as $key) {
+ $output .= drupal_render($element[$key]);
+ }
+ }
+
+ return $output;
+}
+
+/**
+ * Submit handler to add more choices to a field form. This handler is used when
+ * JavaScript is not available. It makes changes to the form state and the
+ * entire form is rebuilt during the page reload.
+ */
+function field_add_more_submit($form, &$form_state) {
+ // Set the form to rebuild and run submit handlers.
+ if (isset($form['#builder_function']) && drupal_function_exists($form['#builder_function'])) {
+ $form['#builder_function']($form, $form_state);
+
+ // Make the changes we want to the form state.
+ $field_name = $form_state['clicked_button']['#field_name'];
+ if ($form_state['values'][$field_name][$field_name . '_add_more']) {
+ $form_state['field_item_count'][$field_name] = count($form_state['values'][$field_name]);
+ }
+ }
+}
+
+/**
+ * Menu callback for AHAH addition of new empty widgets.
+ */
+function field_add_more_js($bundle_name, $field_name) {
+ // Arguments are coming from the url, so we translate back dashes.
+ $field_name = str_replace('-', '_', $field_name);
+
+ $invalid = FALSE;
+ if (empty($_POST['form_build_id'])) {
+ // Invalid request.
+ $invalid = TRUE;
+ }
+
+ // Retrieve the cached form.
+ $form_state = array('submitted' => FALSE);
+ $form_build_id = $_POST['form_build_id'];
+ $form = form_get_cache($form_build_id, $form_state);
+ if (!$form) {
+ // Invalid form_build_id.
+ $invalid = TRUE;
+ }
+
+ // Retrieve field information.
+ $field = $form['#fields'][$field_name]['field'];
+ $instance = $form['#fields'][$field_name]['instance'];
+ $form_path = $form['#fields'][$field_name]['form_path'];
+ if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED) {
+ // Ivnalid
+ $invalid = TRUE;
+ }
+
+ if ($invalid) {
+ drupal_json(array('data' => ''));
+ exit;
+ }
+
+ // We don't simply return a new empty widget row to append to existing ones,
+ // because:
+ // - ahah.js won't simply let us add a new row to a table
+ // - attaching the 'draggable' behavior won't be easy
+ // So we resort to rebuilding the whole table of widgets including the
+ // existing ones, which makes us jump through a few hoops.
+
+ // The form that we get from the cache is unbuilt. We need to build it so
+ // that _value callbacks can be executed and $form_state['values'] populated.
+ // We only want to affect $form_state['values'], not the $form itself
+ // (built forms aren't supposed to enter the cache) nor the rest of
+ // $form_state, so we use copies of $form and $form_state.
+ $form_copy = $form;
+ $form_state_copy = $form_state;
+ $form_copy['#post'] = array();
+ form_builder($_POST['form_id'], $form_copy, $form_state_copy);
+ // Just grab the data we need.
+ $form_state['values'] = $form_state_copy['values'];
+ // Reset cached ids, so that they don't affect the actual form we output.
+ form_clean_id(NULL, TRUE);
+
+ // Sort the $form_state['values'] we just built *and* the incoming $_POST data
+ // according to d-n-d reordering.
+ unset($form_state['values'][$field_name][$field['field_name'] . '_add_more']);
+ foreach ($_POST[$field_name] as $delta => $item) {
+ $form_state['values'][$field_name][$delta]['_weight'] = $item['_weight'];
+ }
+ $form_state['values'][$field_name] = _field_sort_items($field, $form_state['values'][$field_name]);
+ $_POST[$field_name] = _field_sort_items($field, $_POST[$field_name]);
+
+ // Build our new form element for the whole field, asking for one more element.
+ $form_state['field_item_count'] = array($field_name => count($_POST[$field_name]) + 1);
+ $items = $form_state['values'][$field_name];
+ $form_element = field_default_form(NULL, NULL, $field, $instance, $items, $form, $form_state);
+ // Let other modules alter it.
+ drupal_alter('form', $form_element, array(), 'field_add_more_js');
+
+ // Add the new element at the right location in the (original, unbuilt) form.
+ $target = &$form;
+ foreach ($form_path as $key) {
+ $target = &$target[$key];
+ }
+ $target = $form_element[$field_name];
+
+ // Save the new definition of the form.
+ $form_state['values'] = array();
+ form_set_cache($form_build_id, $form, $form_state);
+
+ // Build the new form against the incoming $_POST values so that we can
+ // render the new element.
+ $delta = max(array_keys($_POST[$field_name])) + 1;
+ $_POST[$field_name][$delta]['_weight'] = $delta;
+ $form_state = array('submitted' => FALSE);
+ $form += array(
+ '#post' => $_POST,
+ '#programmed' => FALSE,
+ );
+ $form = form_builder($_POST['form_id'], $form, $form_state);
+
+ // Render the new output.
+ // We get fetch the form element from the built $form.
+ $field_form = $form;
+ foreach ($form_path as $key) {
+ $field_form = $field_form[$key];
+ }
+ // We add a div around the new field to receive the ahah effect.
+ $field_form[$delta]['#prefix'] = '<div class="ahah-new-field">' . (isset($field_form[$delta]['#prefix']) ? $field_form[$delta]['#prefix'] : '');
+ $field_form[$delta]['#suffix'] = (isset($field_form[$delta]['#suffix']) ? $field_form[$delta]['#suffix'] : '') . '</div>';
+ // TODO : this causes duplication of the wrapping divs
+
+ // If a newly inserted widget contains AHAH behaviors, they normally won't
+ // work because AHAH doesn't know about those - it just attaches to the exact
+ // form elements that were initially specified in the Drupal.settings object.
+ // The new ones didn't exist then, so we need to update Drupal.settings
+ // by ourselves in order to let AHAH know about those new form elements.
+ $javascript = drupal_add_js(NULL, NULL);
+ $output_js = isset($javascript['setting']) ? '<script type="text/javascript">jQuery.extend(Drupal.settings, ' . drupal_to_js(call_user_func_array('array_merge_recursive', $javascript['setting'])) . ');</script>' : '';
+
+ $output = theme('status_messages') . drupal_render($field_form) . $output_js;
+ drupal_json(array('status' => TRUE, 'data' => $output));
+ exit;
+}
diff --git a/modules/field/field.test b/modules/field/field.test
index c99ba080b..cb99571c3 100644
--- a/modules/field/field.test
+++ b/modules/field/field.test
@@ -1,1204 +1,1204 @@
-<?php
-// $Id$
-
-// TODO : use drupalCreateField() / drupalCreateFieldInstance() all over ?
-
-class FieldAttachTestCase extends DrupalWebTestCase {
- function getInfo() {
- return array(
- 'name' => t('Field attach tests'),
- 'description' => t("Test Field Attach API functions."),
- 'group' => t('Field')
- );
- }
-
- function setUp() {
- parent::setUp('field_sql_storage', 'field', 'field_test');
-
- $this->field_name = strtolower($this->randomName(). '_field_name');
- $this->table = _field_sql_storage_tablename($this->field_name);
- $this->revision_table = _field_sql_storage_revision_tablename($this->field_name);
- $this->field = array('field_name' => $this->field_name, 'type' => 'test_field', 'cardinality' => 4);
- field_create_field($this->field);
- $this->instance = array(
- 'field_name' => $this->field_name,
- 'bundle' => 'test_bundle',
- 'label' => $this->randomName(). '_label',
- 'description' => $this->randomName(). '_description',
- 'weight' => mt_rand(0, 127),
- 'settings' => array(
- 'test_instance_setting' => $this->randomName(),
- ),
- 'widget' => array(
- 'type' => 'test_field_widget',
- 'label' => 'Test Field',
- 'settings' => array(
- 'test_widget_setting' => $this->randomName(),
- )
- )
- );
- field_create_instance($this->instance);
- }
-
- function testFieldAttachLoad() {
- $entity_type = 'test_entity';
- $eid = 0;
-
- $etid = _field_sql_storage_etid($entity_type);
- $columns = array('etid', 'entity_id', 'revision_id', 'delta', $this->field_name . '_value');
-
- // Insert data for four revisions to the field revisions table
- $query = db_insert($this->revision_table)->fields($columns);
- for ($evid = 0; $evid < 4; ++$evid) {
- $values[$evid] = array();
- // Note: we insert one extra value ('<=' instead of '<').
- for ($delta = 0; $delta <= $this->field['cardinality']; $delta++) {
- $value = mt_rand(0, 127);
- $values[$evid][] = $value;
- $query->values(array($etid, $eid, $evid, $delta, $value));
- }
- }
- $query->execute();
-
- // Insert data for the "most current revision" into the field table
- $query = db_insert($this->table)->fields($columns);
- foreach ($values[0] as $delta => $value) {
- $query->values(array($etid, $eid, 0, $delta, $value));
- }
- $query->execute();
-
- // Load the "most current revision"
- $entity = field_test_create_stub_entity($eid, 0, $this->instance['bundle']);
- field_attach_load($entity_type, array($eid => $entity));
- foreach ($values[0] as $delta => $value) {
- if ($delta < $this->field['cardinality']) {
- $this->assertEqual($entity->{$this->field_name}[$delta]['value'], $value, "Value $delta is loaded correctly for current revision");
- }
- else {
- $this->assertFalse(array_key_exists($delta, $entity->{$this->field_name}), "No extraneous value gets loaded for current revision.");
- }
- }
-
- // Load every revision
- for ($evid = 0; $evid < 4; ++$evid) {
- $entity = field_test_create_stub_entity($eid, $evid, $this->instance['bundle']);
- field_attach_load_revision($entity_type, array($eid => $entity));
- foreach ($values[$evid] as $delta => $value) {
- if ($delta < $this->field['cardinality']) {
- $this->assertEqual($entity->{$this->field_name}[$delta]['value'], $value, "Value $delta for revision $evid is loaded correctly");
- }
- else {
- $this->assertFalse(array_key_exists($delta, $entity->{$this->field_name}), "No extraneous value gets loaded for revision $evid.");
- }
- }
- }
- }
-
-// function testFieldAttachLoadMultiple() {
- // TODO : test the 'multiple' aspect of load:
- // define 2 bundles, 3 fields
- // bundle1 gets instances of field1, field2
- // bundle2 gets instances of field1, field3
- // load 2 entities (one for each bundle) in a single load
- // check that everything gets loaded ok.
-// }
-
- function testFieldAttachInsertAndUpdate() {
- $entity_type = 'test_entity';
- $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
-
- // Test insert.
- $values = array();
- // Note: we try to insert one extra value ('<=' instead of '<').
- // TODO : test empty values filtering and "compression" (store consecutive deltas).
- for ($delta = 0; $delta <= $this->field['cardinality']; $delta++) {
- $values[$delta]['value'] = mt_rand(0, 127);
- }
- $entity->{$this->field_name} = $rev_values[0] = $values;
- field_attach_insert($entity_type, $entity);
-
- $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC);
- foreach ($values as $delta => $value) {
- if ($delta < $this->field['cardinality']) {
- $this->assertEqual($rows[$delta][$this->field_name . '_value'], $value['value'], t("Value $delta is inserted correctly"));
- }
- else {
- $this->assertFalse(array_key_exists($delta, $rows), "No extraneous value gets inserted.");
- }
- }
-
- // Test update.
- $entity = field_test_create_stub_entity(0, 1, $this->instance['bundle']);
- $values = array();
- // Note: we try to update one extra value ('<=' instead of '<').
- for ($delta = 0; $delta <= $this->field['cardinality']; $delta++) {
- $values[$delta]['value'] = mt_rand(0, 127);
- }
- $entity->{$this->field_name} = $rev_values[1] = $values;
- field_attach_update($entity_type, $entity);
- $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC);
- foreach ($values as $delta => $value) {
- if ($delta < $this->field['cardinality']) {
- $this->assertEqual($rows[$delta][$this->field_name . '_value'], $value['value'], t("Value $delta is updated correctly"));
- }
- else {
- $this->assertFalse(array_key_exists($delta, $rows), "No extraneous value gets updated.");
- }
- }
-
- // Check that data for both revisions are in the revision table.
- // We make sure each value is stored correctly, then unset it.
- // When an entire revision's values are unset (remembering that we
- // put one extra value in $values per revision), unset the entire
- // revision. Then, if $rev_values is empty at the end, all
- // revision data was found.
- $results = db_select($this->revision_table, 't')->fields('t')->execute();
- foreach ($results as $row) {
- $this->assertEqual($row->{$this->field_name . '_value'}, $rev_values[$row->revision_id][$row->delta]['value'], "Value {$row->delta} for revision {$row->revision_id} stored correctly");
- unset($rev_values[$row->revision_id][$row->delta]);
- if (count($rev_values[$row->revision_id]) == 1) {
- unset($rev_values[$row->revision_id]);
- }
- }
- $this->assertTrue(empty($rev_values), "All values for all revisions are stored in revision table {$this->revision_table}");
-
- // Check that update leaves the field data untouched if $object has no
- // $field_name key.
- unset($entity->{$this->field_name});
- field_attach_update($entity_type, $entity);
- $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC);
- foreach ($values as $delta => $value) {
- if ($delta < $this->field['cardinality']) {
- $this->assertEqual($rows[$delta][$this->field_name . '_value'], $value['value'], t("Update with no field_name entry leaves value $delta untouched"));
- }
- }
-
- // Check that update with an empty $object->$field_name empties the field.
- $entity->{$this->field_name} = NULL;
- field_attach_update($entity_type, $entity);
- $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC);
- $this->assertEqual(count($rows), 0, t("Update with an empty field_name entry empties the field."));
- }
-
- // Test insert and update with missing or invalid fields. For the
- // most part, these tests pass by not crashing or causing exceptions.
- function testFieldAttachSaveMissingData() {
- $entity_type = 'test_entity';
- $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
-
- // Insert: Field is missing
- field_attach_insert($entity_type, $entity);
- $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}"));
- $this->assertEqual($count, 0, 'Missing field results in no inserts');
-
- // Insert: Field is NULL
- $entity->{$this->field_name} = NULL;
- field_attach_insert($entity_type, $entity);
- $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}"));
- $this->assertEqual($count, 0, 'NULL field results in no inserts');
-
- // Add some real data
- $entity->{$this->field_name} = array(0 => array('value' => 1));
- field_attach_insert($entity_type, $entity);
- $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}"));
- $this->assertEqual($count, 1, 'Field data saved');
-
- // Update: Field is missing. Data should survive.
- unset($entity->{$this->field_name});
- field_attach_update($entity_type, $entity);
- $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}"));
- $this->assertEqual($count, 1, 'Missing field leaves data in table');
-
- // Update: Field is NULL Data should be wiped.
- $entity->{$this->field_name} = NULL;
- field_attach_update($entity_type, $entity);
- $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}"));
- $this->assertEqual($count, 0, 'NULL field leaves no data in table');
- }
-
- function testFieldAttachViewAndPreprocess() {
- $entity_type = 'test_entity';
- $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
-
- // Populate values to be displayed.
- $values = array();
- for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
- $values[$delta]['value'] = mt_rand(0, 127);
- }
- $entity->{$this->field_name} = $values;
-
- // Simple formatter, label displayed.
- $formatter_setting = $this->randomName();
- $this->instance['display'] = array(
- 'full' => array(
- 'label' => 'above',
- 'type' => 'field_test_default',
- 'settings' => array(
- 'test_formatter_setting' => $formatter_setting,
- )
- ),
- );
- field_update_instance($this->instance);
- $entity->content = field_attach_view($entity_type, $entity);
- $output = drupal_render($entity->content);
- $variables = field_attach_preprocess($entity_type, $entity);
- $variable = $this->instance['field_name'] . '_rendered';
- $this->assertTrue(isset($variables[$variable]), "Variable $variable is available in templates.");
- $this->content = $output;
- $this->assertRaw($this->instance['label'], "Label is displayed.");
- $this->content = $variables[$variable];
- $this->assertRaw($this->instance['label'], "Label is displayed (template variable).");
- foreach ($values as $delta => $value) {
- $this->content = $output;
- $this->assertRaw("$formatter_setting|{$value['value']}", "Value $delta is displayed, formatter settings are applied.");
- $this->content = $variables[$variable];
- $this->assertRaw("$formatter_setting|{$value['value']}", "Value $delta is displayed, formatter settings are applied (template variable).");
- }
-
- // Label hidden.
- $this->instance['display']['full']['label'] = 'hidden';
- field_update_instance($this->instance);
- $entity->content = field_attach_view($entity_type, $entity);
- $output = drupal_render($entity->content);
- $variables = field_attach_preprocess($entity_type, $entity);
- $this->content = $output;
- $this->assertNoRaw($this->instance['label'], "Hidden label: label is not displayed.");
- $this->content = $variables[$variable];
- $this->assertNoRaw($this->instance['label'], "Hidden label: label is not displayed (template variable).");
-
- // Field hidden.
- $this->instance['display'] = array(
- 'full' => array(
- 'label' => 'above',
- 'type' => 'hidden',
-
- ),
- );
- field_update_instance($this->instance);
- $entity->content = field_attach_view($entity_type, $entity);
- $output = drupal_render($entity->content);
- $variables = field_attach_preprocess($entity_type, $entity);
- $this->assertTrue(isset($variables[$variable]), "Hidden field: variable $variable is available in templates.");
- $this->content = $output;
- $this->assertNoRaw($this->instance['label'], "Hidden field: label is not displayed.");
- foreach ($values as $delta => $value) {
- $this->assertNoRaw($value['value'], "Hidden field: value $delta is not displayed.");
- }
-
- // Multiple formatter.
- $formatter_setting = $this->randomName();
- $this->instance['display'] = array(
- 'full' => array(
- 'label' => 'above',
- 'type' => 'field_test_multiple',
- 'settings' => array(
- 'test_formatter_setting_multiple' => $formatter_setting,
- )
- ),
- );
- field_update_instance($this->instance);
- $entity->content = field_attach_view($entity_type, $entity);
- $output = drupal_render($entity->content);
- $variables = field_attach_preprocess($entity_type, $entity);
- $display = $formatter_setting;
- foreach ($values as $delta => $value) {
- $display .= "|$delta:{$value['value']}";
- }
- $this->content = $output;
- $this->assertRaw($display, "Multiple formatter: all values are displayed, formatter settings are applied.");
- $this->content = $variables[$variable];
- $this->assertRaw($display, "Multiple formatter: all values are displayed, formatter settings are applied (template variable).");
-
- // TODO:
- // - check that the 'exclude' option works (if we keep it in core)
- // - check display order with several fields
- }
-
- function testFieldAttachDelete() {
- $entity_type = 'test_entity';
- $rev[0] = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
-
- // Create revision 0
- $values = array();
- for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
- $values[$delta]['value'] = mt_rand(0, 127);
- }
- $rev[0]->{$this->field_name} = $values;
- field_attach_insert($entity_type, $rev[0]);
-
- // Create revision 1
- $rev[1] = field_test_create_stub_entity(0, 1, $this->instance['bundle']);
- $rev[1]->{$this->field_name} = $values;
- field_attach_update($entity_type, $rev[1]);
-
- // Create revision 2
- $rev[2] = field_test_create_stub_entity(0, 2, $this->instance['bundle']);
- $rev[2]->{$this->field_name} = $values;
- field_attach_update($entity_type, $rev[2]);
-
- // Confirm each revision loads
- foreach (array_keys($rev) as $vid) {
- $read = field_test_create_stub_entity(0, $vid, $this->instance['bundle']);
- field_attach_load_revision($entity_type, array(0 => $read));
- $this->assertEqual(count($read->{$this->field_name}), $this->field['cardinality'], "The test object revision $vid has {$this->field['cardinality']} values.");
- }
-
- // Delete revision 1, confirm the other two still load.
- field_attach_delete_revision($entity_type, $rev[1]);
- foreach (array(0, 2) as $vid) {
- $read = field_test_create_stub_entity(0, $vid, $this->instance['bundle']);
- field_attach_load_revision($entity_type, array(0 => $read));
- $this->assertEqual(count($read->{$this->field_name}), $this->field['cardinality'], "The test object revision $vid has {$this->field['cardinality']} values.");
- }
-
- // Confirm the current revision still loads
- $read = field_test_create_stub_entity(0, 2, $this->instance['bundle']);
- field_attach_load($entity_type, array(0 => $read));
- $this->assertEqual(count($read->{$this->field_name}), $this->field['cardinality'], "The test object current revision has {$this->field['cardinality']} values.");
-
- // Delete all field data, confirm nothing loads
- field_attach_delete($entity_type, $rev[2]);
- foreach (array(0, 1, 2) as $vid) {
- $read = field_test_create_stub_entity(0, $vid, $this->instance['bundle']);
- field_attach_load_revision($entity_type, array(0 => $read));
- $this->assertIdentical($read->{$this->field_name}, array(), "The test object revision $vid is deleted.");
- }
- $read = field_test_create_stub_entity(0, 2, $this->instance['bundle']);
- field_attach_load($entity_type, array(0 => $read));
- $this->assertIdentical($read->{$this->field_name}, array(), "The test object current revision is deleted.");
- }
-
- function testFieldAttachCreateRenameBundle() {
- // Create a new bundle. This has to be initiated by the module so that its
- // hook_fieldable_info() is consistent.
- $new_bundle = 'test_bundle_' . strtolower($this->randomName());
- field_test_create_bundle($new_bundle, $this->randomName());
-
- // Add an instance to that bundle.
- $this->instance['bundle'] = $new_bundle;
- field_create_instance($this->instance);
-
- // Save an object with data in the field.
- $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
- $values = array();
- for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
- $values[$delta]['value'] = mt_rand(0, 127);
- }
- $entity->{$this->field_name} = $values;
- $entity_type = 'test_entity';
- field_attach_insert($entity_type, $entity);
-
- // Verify the field data is present on load.
- $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
- field_attach_load($entity_type, array(0 => $entity));
- $this->assertEqual(count($entity->{$this->field_name}), $this->field['cardinality'], "Data are retrieved for the new bundle");
-
- // Rename the bundle. This has to be initiated by the module so that its
- // hook_fieldable_info() is consistent.
- $new_bundle = 'test_bundle_' . strtolower($this->randomName());
- field_test_rename_bundle($this->instance['bundle'], $new_bundle);
-
- // Check that the instance definition has been updated.
- $this->instance = field_info_instance($this->field_name, $new_bundle);
- $this->assertIdentical($this->instance['bundle'], $new_bundle, "Bundle name has been updated in the instance.");
-
- // Verify the field data is present on load.
- $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
- field_attach_load($entity_type, array(0 => $entity));
- $this->assertEqual(count($entity->{$this->field_name}), $this->field['cardinality'], "Bundle name has been updated in the field storage");
- }
-
- function testFieldAttachDeleteBundle() {
- // Create a new bundle. This has to be initiated by the module so that its
- // hook_fieldable_info() is consistent.
- $new_bundle = 'test_bundle_' . strtolower($this->randomName());
- field_test_create_bundle($new_bundle, $this->randomName());
-
- // Add an instance to that bundle.
- $this->instance['bundle'] = $new_bundle;
- field_create_instance($this->instance);
-
- // Create a second field for the test bundle
- $field_name = strtolower($this->randomName(). '_field_name');
- $table = _field_sql_storage_tablename($field_name);
- $revision_table = _field_sql_storage_revision_tablename($field_name);
- $field = array('field_name' => $field_name, 'type' => 'test_field', 'cardinality' => 1);
- field_create_field($field);
- $instance = array(
- 'field_name' => $field_name,
- 'bundle' => $this->instance['bundle'],
- 'label' => $this->randomName(). '_label',
- 'description' => $this->randomName(). '_description',
- 'weight' => mt_rand(0, 127),
- // test_field has no instance settings
- 'widget' => array(
- 'type' => 'test_field_widget',
- 'settings' => array(
- 'size' => mt_rand(0, 255))));
- field_create_instance($instance);
-
- // Save an object with data for both fields
- $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
- $values = array();
- for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
- $values[$delta]['value'] = mt_rand(0, 127);
- }
- $entity->{$this->field_name} = $values;
- $entity->{$field_name} = array(0 => array('value' => 99));
- $entity_type = 'test_entity';
- field_attach_insert($entity_type, $entity);
-
- // Verify the fields are present on load
- $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
- field_attach_load($entity_type, array(0 => $entity));
- $this->assertEqual(count($entity->{$this->field_name}), 4, "First field got loaded");
- $this->assertEqual(count($entity->{$field_name}), 1, "Second field got loaded");
-
- // Delete the bundle. This has to be initiated by the module so that its
- // hook_fieldable_info() is consistent.
- field_test_delete_bundle($this->instance['bundle']);
-
- // Verify no data gets loaded
- $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
- field_attach_load($entity_type, array(0 => $entity));
- $this->assertFalse(isset($entity->{$this->field_name}), "No data for first field");
- $this->assertFalse(isset($entity->{$field_name}), "No data for second field");
-
- // Verify that the instances are gone
- $this->assertFalse(field_read_instance($this->field_name, $this->instance['bundle']), "First field is deleted");
- $this->assertFalse(field_read_instance($field_name, $instance['bundle']), "Second field is deleted");
- }
-
- function testFieldAttachCache() {
- // Create a revision
- $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
- $values = array();
- for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
- $values[$delta]['value'] = mt_rand(0, 127);
- }
- $entity->{$this->field_name} = $values;
-
- $noncached_type = 'test_entity';
- $cached_type = 'test_cacheable_entity';
-
- // Non-cached type:
- $cid = "field:$noncached_type:0:0";
-
- // Confirm no initial cache entry
- $this->assertFalse(cache_get($cid, 'cache_field'), 'Non-cached: no initial cache entry');
-
- // Save, and confirm no cache entry
- field_attach_insert($noncached_type, $entity);
- $this->assertFalse(cache_get($cid, 'cache_field'), 'Non-cached: no cache entry on save');
-
- // Load, and confirm no cache entry
- field_attach_load($noncached_type, array(0 => $entity));
- $this->assertFalse(cache_get($cid, 'cache_field'), 'Non-cached: no cache entry on load');
-
- // Cached type:
- $cid = "field:$cached_type:0:0";
-
- // Confirm no initial cache entry
- $this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no initial cache entry');
-
- // Save, and confirm no cache entry
- field_attach_insert($cached_type, $entity);
- $this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no cache entry on save');
-
- // Load, and confirm cache entry
- field_attach_load($cached_type, array(0 => $entity));
- $cache = cache_get($cid, 'cache_field');
- $this->assertEqual($cache->data[$this->field_name], $values, 'Cached: correct cache entry on load');
-
- // Delete, and confirm no cache entry
- field_attach_delete($cached_type, $entity);
- $this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no cache entry on save');
- }
-
- // Verify that field_attach_validate() invokes the correct
- // hook_field_validate. NOTE: This tests the FAPI-connected
- // behavior of hook_field_validate. As discussed at
- // http://groups.drupal.org/node/18019, field validation will
- // eventually be disconnected from FAPI, at which point this
- // function will have to be rewritten.
- function testFieldAttachValidate() {
- $entity_type = 'test_entity';
- $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
-
- // Set up values to generate errors
- $values = array();
- for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
- $values[$delta]['value'] = -1;
- $values[$delta]['_error_element'] = 'field_error_' . $delta;
- }
- // Arrange for item 1 not to generate an error
- $values[1]['value'] = 1;
- $entity->{$this->field_name} = $values;
-
- field_attach_validate($entity_type, $entity, array());
-
- $errors = form_get_errors();
- foreach ($values as $delta => $value) {
- if ($value['value'] != 1) {
- $this->assertTrue(isset($errors[$value['_error_element']]), "Error is set on {$value['_error_element']}: {$errors[$value['_error_element']]}");
- unset($errors[$value['_error_element']]);
- }
- else {
- $this->assertFalse(isset($errors[$value['_error_element']]), "Error is not set on {$value['_error_element']}");
- }
- }
- $this->assertEqual(count($errors), 0, 'No extraneous form errors set');
- }
-
- // Validate that FAPI elements are generated. This could be much
- // more thorough, but it does verify that the correct widgets show up.
- function testFieldAttachForm() {
- $entity_type = 'test_entity';
- $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
-
- $form = $form_state = array();
- field_attach_form($entity_type, $entity, $form, $form_state);
-
- $this->assertEqual($form[$this->field_name]['#title'], $this->instance['label'], "Form title is {$this->instance['label']}");
- for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
- // field_test_widget uses 'textfield'
- $this->assertEqual($form[$this->field_name][$delta]['value']['#type'], 'textfield', "Form delta $delta widget is textfield");
- }
- }
-
- function testFieldAttachSubmit() {
- $entity_type = 'test_entity';
- $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
-
- // Build the form.
- $form = $form_state = array();
- field_attach_form($entity_type, $entity, $form, $form_state);
-
- // Simulate incoming values.
- $values = array();
- $weights = array();
- for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
- $values[$delta]['value'] = mt_rand(1, 127);
- // Assign random weight.
- do {
- $weight = mt_rand(0, $this->field['cardinality']);
- } while (in_array($weight, $weights));
- $weights[$delta] = $weight;
- $values[$delta]['_weight'] = $weight;
- }
- // Leave an empty value. 'field_test' fields are empty if empty().
- $values[1]['value'] = 0;
-
- $form_state['values'] = array($this->field_name => $values);
- field_attach_submit($entity_type, $entity, $form, $form_state);
-
- asort($weights);
- $expected_values = array();
- foreach ($weights as $key => $value) {
- if ($key != 1) {
- $expected_values[] = array('value' => $values[$key]['value']);
- }
- }
- $this->assertIdentical($entity->{$this->field_name}, $expected_values, 'Submit filters empty values');
- }
-}
-
-class FieldInfoTestCase extends DrupalWebTestCase {
-
- function getInfo() {
- return array(
- 'name' => t('Field info tests'),
- 'description' => t("Get information about existing fields, instances and bundles."),
- 'group' => t('Field')
- );
- }
-
- function setUp() {
- parent::setUp('field_sql_storage', 'field', 'field_test');
- }
-
- function testFieldInfo() {
- // Test that field_test module's fields, widgets, and formatters show up.
- $field_test_info = field_test_field_info();
- $formatter_info = field_test_field_formatter_info();
- $widget_info = field_test_field_widget_info();
-
- $info = field_info_field_types();
- foreach ($field_test_info as $t_key => $field_type) {
- foreach ($field_type as $key => $val) {
- $this->assertEqual($info[$t_key][$key], $val, t("Field type $t_key key $key is $val"));
- }
- $this->assertEqual($info[$t_key]['module'], 'field_test', t("Field type field_test module appears"));
- }
-
- $info = field_info_formatter_types();
- foreach ($formatter_info as $f_key => $formatter) {
- foreach ($formatter as $key => $val) {
- $this->assertEqual($info[$f_key][$key], $val, t("Formatter type $f_key key $key is $val"));
- }
- $this->assertEqual($info[$f_key]['module'], 'field_test', t("Formatter type field_test module appears"));
- }
-
- $info = field_info_widget_types();
- foreach ($widget_info as $w_key => $widget) {
- foreach ($widget as $key => $val) {
- $this->assertEqual($info[$w_key][$key], $val, t("Widget type $w_key key $key is $val"));
- }
- $this->assertEqual($info[$w_key]['module'], 'field_test', t("Widget type field_test module appears"));
- }
-
- // Verify that no fields or instances exist
- $fields = field_info_fields();
- $instances = field_info_instances(FIELD_TEST_BUNDLE);
- $this->assertTrue(empty($fields), t('With no fields, info fields is empty.'));
- $this->assertTrue(empty($instances), t('With no instances, info bundles is empty.'));
-
- // Create a field, verify it shows up.
- $field = array(
- 'field_name' => strtolower($this->randomName()),
- 'type' => 'test_field',
- );
- field_create_field($field);
- $fields = field_info_fields();
- $this->assertEqual(count($fields), 1, t('One field exists'));
- $this->assertEqual($fields[$field['field_name']]['field_name'], $field['field_name'], t('info fields contains field name'));
- $this->assertEqual($fields[$field['field_name']]['type'], $field['type'], t('info fields contains field type'));
- $this->assertEqual($fields[$field['field_name']]['module'], 'field_test', t('info fields contains field module'));
- $settings = array('test_field_setting' => 'dummy test string');
- foreach ($settings as $key => $val) {
- $this->assertEqual($fields[$field['field_name']]['settings'][$key], $val, t("Field setting $key has correct default value $val"));
- }
- $this->assertEqual($fields[$field['field_name']]['cardinality'], 1, t('info fields contains cardinality 1'));
- $this->assertEqual($fields[$field['field_name']]['active'], 1, t('info fields contains active 1'));
-
- // Create an instance, verify that it shows up
- $instance = array(
- 'field_name' => $field['field_name'],
- 'bundle' => FIELD_TEST_BUNDLE,
- 'label' => $this->randomName(),
- 'description' => $this->randomName(),
- 'weight' => mt_rand(0, 127),
- // test_field has no instance settings
- 'widget' => array(
- 'type' => 'test_field_widget',
- 'settings' => array(
- 'test_setting' => 999)));
- field_create_instance($instance);
-
- $instances = field_info_instances($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 that the field_info settings convenience functions work
- function testSettingsInfo() {
- $info = field_test_field_info();
- foreach ($info as $type => $data) {
- $this->assertIdentical(field_info_field_settings($type), $data['settings'], "field_info_field_settings returns {$type}'s field settings");
- $this->assertIdentical(field_info_instance_settings($type), $data['instance_settings'], "field_info_field_settings returns {$type}'s field instance settings");
- }
-
- $info = field_test_field_widget_info();
- foreach ($info as $type => $data) {
- $this->assertIdentical(field_info_widget_settings($type), $data['settings'], "field_info_widget_settings returns {$type}'s widget settings");
- }
-
- $info = field_test_field_formatter_info();
- foreach ($info as $type => $data) {
- $this->assertIdentical(field_info_formatter_settings($type), $data['settings'], "field_info_formatter_settings returns {$type}'s formatter settings");
- }
- }
-}
-
-class FieldFormTestCase extends DrupalWebTestCase {
- function getInfo() {
- return array(
- 'name' => t('Field form tests'),
- 'description' => t("Test Field form handling."),
- 'group' => t('Field')
- );
- }
-
- function setUp() {
- parent::setUp('field_sql_storage', 'field', 'field_test');
-
- $web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content'));
- $this->drupalLogin($web_user);
-
- $this->field_single = array('field_name' => strtolower($this->randomName(). '_field_name'), 'type' => 'test_field');
- $this->field_multiple = array('field_name' => strtolower($this->randomName(). '_field_name'), 'type' => 'test_field', 'cardinality' => 4);
- $this->field_unlimited = array('field_name' => strtolower($this->randomName(). '_field_name'), 'type' => 'test_field', 'cardinality' => FIELD_CARDINALITY_UNLIMITED);
-
- $this->instance = array(
- 'bundle' => 'test_bundle',
- 'label' => $this->randomName(). '_label',
- 'description' => $this->randomName(). '_description',
- 'weight' => mt_rand(0, 127),
- 'settings' => array(
- 'test_instance_setting' => $this->randomName(),
- ),
- 'widget' => array(
- 'type' => 'test_field_widget',
- 'label' => 'Test Field',
- 'settings' => array(
- 'test_widget_setting' => $this->randomName(),
- )
- )
- );
- }
-
- function testFieldFormSingle() {
- $this->field = $this->field_single;
- $this->field_name = $this->field['field_name'];
- $this->instance['field_name'] = $this->field_name;
- field_create_field($this->field);
- field_create_instance($this->instance);
-
- // Display creation form.
- $this->drupalGet('test-entity/add/test-bundle');
- $this->assertFieldByName($this->field_name . '[0][value]', '', 'Widget is displayed');
- $this->assertNoField($this->field_name . '[1][value]', 'No extraneous widget is displayed');
- // TODO : check that the widget is populated with default value ?
-
- // Submit with invalid value (field-level validation).
- $edit = array($this->field_name . '[0][value]' => -1);
- $this->drupalPost(NULL, $edit, t('Save'));
- $this->assertRaw(t('%name does not accept the value -1.', array('%name' => $this->instance['label'])), 'Field validation fails with invalid input.');
- // TODO : check that the correct field is flagged for error.
-
- // Create an entity
- $value = mt_rand(0, 127);
- $edit = array($this->field_name . '[0][value]' => $value);
- $this->drupalPost(NULL, $edit, t('Save'));
- preg_match('|test-entity/(\d+)/edit|', $this->url, $match);
- $id = $match[1];
- $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created');
- $entity = field_test_entity_load($id);
- $this->assertEqual($entity->{$this->field_name}[0]['value'], $value, 'Field value was saved');
-
- // Display edit form.
- $this->drupalGet('test-entity/' . $id . '/edit');
- $this->assertFieldByName($this->field_name . '[0][value]', $value, 'Widget is displayed with the correct default value');
- $this->assertNoField($this->field_name . '[1][value]', 'No extraneous widget is displayed');
-
- // Update the entity.
- $value = mt_rand(0, 127);
- $edit = array($this->field_name . '[0][value]' => $value);
- $this->drupalPost(NULL, $edit, t('Save'));
- $this->assertRaw(t('test_entity @id has been updated.', array('@id' => $id)), 'Entity was updated');
- $entity = field_test_entity_load($id);
- $this->assertEqual($entity->{$this->field_name}[0]['value'], $value, 'Field value was updated');
-
- // Empty the field.
- $value = '';
- $edit = array($this->field_name . '[0][value]' => $value);
- $this->drupalPost('test-entity/' . $id . '/edit', $edit, t('Save'));
- $this->assertRaw(t('test_entity @id has been updated.', array('@id' => $id)), 'Entity was updated');
- $entity = field_test_entity_load($id);
- $this->assertIdentical($entity->{$this->field_name}, array(), 'Field was emptied');
-
- }
-
- function testFieldFormSingleRequired() {
- $this->field = $this->field_single;
- $this->field_name = $this->field['field_name'];
- $this->instance['field_name'] = $this->field_name;
- $this->instance['required'] = TRUE;
- field_create_field($this->field);
- field_create_instance($this->instance);
-
- // Submit with missing required value.
- $edit = array();
- $this->drupalPost('test-entity/add/test-bundle', $edit, t('Save'));
- $this->assertRaw(t('!name field is required.', array('!name' => $this->instance['label'])), 'Required field with no value fails validation');
-
- // Create an entity
- $value = mt_rand(0, 127);
- $edit = array($this->field_name . '[0][value]' => $value);
- $this->drupalPost(NULL, $edit, t('Save'));
- preg_match('|test-entity/(\d+)/edit|', $this->url, $match);
- $id = $match[1];
- $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created');
- $entity = field_test_entity_load($id);
- $this->assertEqual($entity->{$this->field_name}[0]['value'], $value, 'Field value was saved');
-
- // Edit with missing required value.
- $value = '';
- $edit = array($this->field_name . '[0][value]' => $value);
- $this->drupalPost('test-entity/' . $id . '/edit', $edit, t('Save'));
- $this->assertRaw(t('!name field is required.', array('!name' => $this->instance['label'])), 'Required field with no value fails validation');
- }
-
-// function testFieldFormMultiple() {
-// $this->field = $this->field_multiple;
-// $this->field_name = $this->field['field_name'];
-// $this->instance['field_name'] = $this->field_name;
-// field_create_field($this->field);
-// field_create_instance($this->instance);
-// }
-
- function testFieldFormUnlimited() {
- $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);
-
- // Display creation form -> 1 widget.
- $this->drupalGet('test-entity/add/test-bundle');
- $this->assertFieldByName($this->field_name . '[0][value]', '', 'Widget 1 is displayed');
- $this->assertNoField($this->field_name . '[1][value]', 'No extraneous widget is displayed');
-
- // Press 'add more' button -> 2 widgets.
- $this->drupalPost(NULL, array(), t('Add another item'));
- $this->assertFieldByName($this->field_name . '[0][value]', '', 'Widget 1 is displayed');
- $this->assertFieldByName($this->field_name . '[1][value]', '', 'New widget is displayed');
- $this->assertNoField($this->field_name . '[2][value]', 'No extraneous widget is displayed');
- // TODO : check that non-field inpurs are preserved ('title')...
-
- // Yet another time so that we can play with more values -> 3 widgets.
- $this->drupalPost(NULL, array(), t('Add another item'));
-
- // Prepare values and weights.
- $count = 3;
- $delta_range = $count - 1;
- $values = $weights = $pattern = $expected_values = $edit = array();
- for ($delta = 0; $delta <= $delta_range; $delta++) {
- // Assign unique random weights.
- do {
- $weight = mt_rand(-$delta_range, $delta_range);
- } while (in_array($weight, $weights));
- $weights[] = $weight;
- $value = mt_rand(0, 127);
- $edit["$this->field_name[$delta][value]"] = $value;
- $edit["$this->field_name[$delta][_weight]"] = $weight;
- // We'll need three slightly different formats to check the values.
- $values[$weight] = $value;
- $field_values[$weight]['value'] = (string)$value;
- $pattern[$weight] = " value=\"$value\"";
- }
-
- // Press 'add more' button -> 4 widgets
- $this->drupalPost(NULL, $edit, t('Add another item'));
- ksort($values);
- $values = array_values($values);
- for ($delta = 0; $delta <= $delta_range; $delta++) {
- $this->assertFieldByName("$this->field_name[$delta][value]", $values[$delta], "Widget $delta is displayed and has the right value");
- $this->assertFieldByName("$this->field_name[$delta][_weight]", $delta, "Widget $delta has the right weight");
- }
- ksort($pattern);
- $pattern = implode('.*', array_values($pattern));
- $this->assertPattern("|$pattern|s", 'Widgets are displayed in the correct order');
- $this->assertFieldByName("$this->field_name[$delta][value]", '', "New widget is displayed");
- $this->assertFieldByName("$this->field_name[$delta][_weight]", $delta, "New widget has the right weight");
- $this->assertNoField("$this->field_name[". ($delta + 1) . '][value]', 'No extraneous widget is displayed');
-
- // Submit the form and create the entity.
- $this->drupalPost(NULL, $edit, t('Save'));
- preg_match('|test-entity/(\d+)/edit|', $this->url, $match);
- $id = $match[1];
- $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created');
- $entity = field_test_entity_load($id);
- ksort($field_values);
- $field_values = array_values($field_values);
- $this->assertIdentical($entity->{$this->field_name}, $field_values, 'Field values were saved in the correct order');
-
- // display edit form: check that the expected number of widgets is displayed, with correct values
- // change values, reorder, leave an empty value in the middle, submit: check that the entity is updated with correct values
- // re-submit: check that the field can be emptied.
-
- // Test with several multiple fields in a form
- }
-
- // check with a multiple widget (implement a textfield with comma separated values)
-
- // check inaccessible fields are preserved on update
- // check inaccessible fields get default value on insert (not implemented yet)
-
-}
-
-class FieldTestCase extends DrupalWebTestCase {
- function getInfo() {
- return array(
- 'name' => t('Field tests'),
- 'description' => t("Create / read /update a field."),
- 'group' => t('Field')
- );
- }
-
- function setUp() {
- parent::setUp('field_sql_storage', 'field', 'field_test');
- }
-
- // TODO : test creation with
- // - a full fledged $field structure, check that all the values are there
- // - a minimal $field structure, check all default values are set
- // defer actual $field comparison to a helper function, used for the two cases above
- /**
- * Test the creation of a field.
- */
- function testCreateField() {
- $field_definition = array(
- 'field_name' => strtolower($this->randomName()),
- 'type' => 'test_field',
- );
- field_create_field($field_definition);
-
- $field = field_read_field($field_definition['field_name']);
-
- // Ensure that basic properties are preserved.
- $this->assertEqual($field['field_name'], $field_definition['field_name'], t('The field name is properly saved.'));
- $this->assertEqual($field['type'], $field_definition['type'], t('The field type is properly saved.'));
-
- // Ensure that cardinality defaults to 1.
- $this->assertEqual($field['cardinality'], 1, t('Cardinality defaults to 1.'));
-
- // Ensure that default settings are present.
- $info = field_info_field_types($field['type']);
- $settings = $info['settings'];
- $this->assertIdentical($settings, $field['settings'] , t('Default field settings have been written.'));
-
- // Check that a table has been created for the field.
- $this->assertTrue(db_table_exists('field_data_' . $field_definition['field_name']), t('A table has been created for the field.'));
-
- // Guarantee that the name is unique.
- try {
- field_create_field($field_definition);
- $this->fail(t('Cannot create two fields with the same name.'));
- } catch (FieldException $e) {
- $this->pass(t('Cannot create two fields with the same name.'));
- }
-
- // Check that invalid field names are rejected.
- $field_definition['field_name'] += '_#';
- try {
- field_create_field($field_definition);
- $this->fail(t('Cannot create a field with an invalid name.'));
- } catch (FieldException $e) {
- $this->pass(t('Cannot create a field with an invalid name.'));
- }
-
- // TODO : other failures
- }
-
- function testReadField() {
-
- }
-
- /**
- * Test the deletion of a field.
- */
- function testDeleteField() {
- // TODO: Also test deletion of the data stored in the field ?
-
- // Create two fields (so we can test that only one is deleted).
- $this->field = $this->drupalCreateField('test_field', 'test_field_name');
- $this->another_field = $this->drupalCreateField('test_field', 'another_test_field_name');
-
- // Create instances for each.
- $this->instance_definition = array(
- 'field_name' => $this->field['field_name'],
- 'bundle' => FIELD_TEST_BUNDLE,
- 'widget' => array(
- 'type' => 'test_field_widget',
- ),
- );
- field_create_instance($this->instance_definition);
- $this->another_instance_definition = $this->instance_definition;
- $this->another_instance_definition['field_name'] = $this->another_field['field_name'];
- field_create_instance($this->another_instance_definition);
-
- // Test that the first field is not deleted, and then delete it.
- $field = field_read_field($this->field['field_name'], array('include_deleted' => TRUE));
- $this->assertTrue(!empty($field) && empty($field['deleted']), t('A new field is not marked for deletion.'));
- field_delete_field($this->field['field_name']);
-
- // 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']), t('A deleted field is marked for deletion.'));
-
- // Make sure that this field's instance is marked as deleted when it is
- // specifically loaded.
- $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle'], array('include_deleted' => TRUE));
- $this->assertTrue(!empty($instance['deleted']), t('An instance for a deleted field is marked for deletion.'));
-
- // Try to load the field normally and make sure it does not show up.
- $field = field_read_field($this->field['field_name']);
- $this->assertTrue(empty($field), t('A deleted field is not loaded by default.'));
-
- // Try to load the instance normally and make sure it does not show up.
- $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
- $this->assertTrue(empty($instance), t('An instance for a deleted field is not loaded by default.'));
-
- // Make sure the other field (and its field instance) are not deleted.
- $another_field = field_read_field($this->another_field['field_name']);
- $this->assertTrue(!empty($another_field) && empty($another_field['deleted']), t('A non-deleted field is not marked for deletion.'));
- $another_instance = field_read_instance($this->another_instance_definition['field_name'], $this->another_instance_definition['bundle']);
- $this->assertTrue(!empty($another_instance) && empty($another_instance['deleted']), t('An instance of a non-deleted field is not marked for deletion.'));
- }
-}
-
-class FieldInstanceTestCase extends DrupalWebTestCase {
- protected $field;
-
- function getInfo() {
- return array(
- 'name' => t('Field instance tests'),
- 'description' => t("Create field entities by attaching fields to entities."),
- 'group' => t('Field')
- );
- }
-
- function setUp() {
- parent::setUp('field_sql_storage', 'field', 'field_test');
-
- $this->field = $this->drupalCreateField('test_field');
- $this->instance_definition = array(
- 'field_name' => $this->field['field_name'],
- 'bundle' => FIELD_TEST_BUNDLE,
- );
- }
-
- // TODO : test creation with
- // - a full fledged $instance structure, check that all the values are there
- // - a minimal $instance structure, check all default values are set
- // defer actual $instance comparison to a helper function, used for the two cases above,
- // and for testUpdateFieldInstance
- function testCreateFieldInstance() {
- field_create_instance($this->instance_definition);
- $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
- $field_type = field_info_field_types($this->field['type']);
-
- // Check that default values are set.
- $this->assertIdentical($instance['required'], FALSE, t('Required defaults to false.'));
- $this->assertIdentical($instance['label'], $this->instance_definition['field_name'], t('Label defaults to field name.'));
- $this->assertIdentical($instance['description'], '', t('Description defaults to empty string.'));
-
- // Check that default instance settings are set.
- $settings = array('test_instance_setting' => 'dummy test string');
- $this->assertIdentical($settings, $instance['settings'] , t('Default instance settings have been written.'));
- // Check that the widget is the default one.
- $this->assertIdentical($instance['widget']['type'], $field_type['default_widget'], t('Default widget has been written.'));
- // Check that default widget settings are set.
- $settings = array('test_widget_setting' => 'dummy test string');
- $this->assertIdentical($settings, $instance['widget']['settings'] , t('Default widget settings have been written.'));
- // Check that we have display info for 'full' build_mode.
- $this->assertTrue(isset($instance['display']['full']), t('Display for "full" build_mode has been written.'));
- // Check that the formatter is the default one.
- $this->assertIdentical($instance['display']['full']['type'], $field_type['default_formatter'], t('Default formatter for "full" build_mode has been written.'));
- // Check that the default formatter settings are set.
- $info = field_info_formatter_types($instance['display']['full']['type']);
- $settings = $info['settings'];
- $this->assertIdentical($settings, $instance['display']['full']['settings'] , t('Default formatter settings for "full" build_mode have been written.'));
-
- // Guarantee that the field/bundle combination is unique.
- try {
- field_create_instance($this->instance_definition);
- $this->fail(t('Cannot create two instances with the same field / bundle combination.'));
- }
- catch (FieldException $e) {
- $this->pass(t('Cannot create two instances with the same field / bundle combination.'));
- }
-
- // Check that the specified field exists.
- try {
- $this->instance_definition['field_name'] = $this->randomName();
- field_create_instance($this->instance_definition);
- $this->fail(t('Cannot create an instance of a non-existing field.'));
- }
- catch (FieldException $e) {
- $this->pass(t('Cannot create an instance of a non-existing field.'));
- }
-
- // TODO: test other failures.
- }
-
- function testReadFieldInstance() {
-
- }
-
- function testUpdateFieldInstance() {
- field_create_instance($this->instance_definition);
- $field_type = field_info_field_types($this->field['type']);
-
- // Check that basic changes are saved.
- $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
- $instance['required'] = !$instance['required'];
- $instance['weight']++;
- $instance['label'] = $this->randomName();
- $instance['description'] = $this->randomName();
- $instance['settings']['test_instance_setting'] = $this->randomName();
- $instance['widget']['settings']['test_widget_setting'] =$this->randomName();
- $instance['display']['full']['settings']['test_formatter_setting'] = $this->randomName();
- field_update_instance($instance);
-
- $instance_new = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
- $this->assertEqual($instance['required'], $instance_new['required'], t('"required" change is saved'));
- $this->assertEqual($instance['weight'], $instance_new['weight'], t('"weight" change is saved'));
- $this->assertEqual($instance['label'], $instance_new['label'], t('"label" change is saved'));
- $this->assertEqual($instance['description'], $instance_new['description'], t('"description" change is saved'));
- $this->assertEqual($instance['widget']['settings']['test_widget_setting'], $instance_new['widget']['settings']['test_widget_setting'], t('Widget setting change is saved'));
- $this->assertEqual($instance['display']['full']['settings']['test_formatter_setting'], $instance_new['display']['full']['settings']['test_formatter_setting'], t('Formatter setting change is saved'));
-
- // Check that changing widget and formatter types updates the default settings.
- $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
- $instance['widget']['type'] = 'test_field_widget_multiple';
- $instance['display']['full']['type'] = 'field_test_multiple';
- field_update_instance($instance);
-
- $instance_new = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
- $this->assertEqual($instance['widget']['type'], $instance_new['widget']['type'] , t('Widget type change is saved.'));
- $settings = field_info_widget_settings($instance_new['widget']['type']);
- $this->assertIdentical($settings, array_intersect_key($instance_new['widget']['settings'], $settings) , t('Widget type change updates default settings.'));
- $this->assertEqual($instance['display']['full']['type'], $instance_new['display']['full']['type'] , t('Formatter type change is saved.'));
- $info = field_info_formatter_types($instance_new['display']['full']['type']);
- $settings = $info['settings'];
- $this->assertIdentical($settings, array_intersect_key($instance_new['display']['full']['settings'], $settings) , t('Changing formatter type updates default settings.'));
-
- // Check that adding a new build mode is saved and gets default settings.
- $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
- $instance['display']['teaser'] = array();
- field_update_instance($instance);
-
- $instance_new = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
- $this->assertTrue(isset($instance_new['display']['teaser']), t('Display for the new build_mode has been written.'));
- $this->assertIdentical($instance_new['display']['teaser']['type'], $field_type['default_formatter'], t('Default formatter for the new build_mode has been written.'));
- $info = field_info_formatter_types($instance_new['display']['teaser']['type']);
- $settings = $info['settings'];
- $this->assertIdentical($settings, $instance_new['display']['teaser']['settings'] , t('Default formatter settings for the new build_mode have been written.'));
-
- // TODO: test failures.
- }
-
- function testDeleteFieldInstance() {
- // TODO: Test deletion of the data stored in the field also.
- // Need to check that data for a 'deleted' field / instance doesn't get loaded
- // Need to check data marked deleted is cleaned on cron (not implemented yet...)
-
- // Create two instances for the same field so we can test that only one
- // is deleted.
- field_create_instance($this->instance_definition);
- $this->another_instance_definition = $this->instance_definition;
- $this->another_instance_definition['bundle'] .= '_another_bundle';
- field_create_instance($this->another_instance_definition);
-
- // Test that the first instance is not deleted, and then delete it.
- $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle'], array('include_deleted' => TRUE));
- $this->assertTrue(!empty($instance) && empty($instance['deleted']), t('A new field instance is not marked for deletion.'));
- field_delete_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
-
- // Make sure the instance is marked as deleted when the instance is
- // specifically loaded.
- $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle'], array('include_deleted' => TRUE));
- $this->assertTrue(!empty($instance['deleted']), t('A deleted field instance is marked for deletion.'));
-
- // Try to load the instance normally and make sure it does not show up.
- $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
- $this->assertTrue(empty($instance), t('A deleted field instance is not loaded by default.'));
-
- // Make sure the other field instance is not deleted.
- $another_instance = field_read_instance($this->another_instance_definition['field_name'], $this->another_instance_definition['bundle']);
- $this->assertTrue(!empty($another_instance) && empty($another_instance['deleted']), t('A non-deleted field instance is not marked for deletion.'));
- }
-}
+<?php
+// $Id$
+
+// TODO : use drupalCreateField() / drupalCreateFieldInstance() all over ?
+
+class FieldAttachTestCase extends DrupalWebTestCase {
+ function getInfo() {
+ return array(
+ 'name' => t('Field attach tests'),
+ 'description' => t("Test Field Attach API functions."),
+ 'group' => t('Field')
+ );
+ }
+
+ function setUp() {
+ parent::setUp('field_sql_storage', 'field', 'field_test');
+
+ $this->field_name = strtolower($this->randomName(). '_field_name');
+ $this->table = _field_sql_storage_tablename($this->field_name);
+ $this->revision_table = _field_sql_storage_revision_tablename($this->field_name);
+ $this->field = array('field_name' => $this->field_name, 'type' => 'test_field', 'cardinality' => 4);
+ field_create_field($this->field);
+ $this->instance = array(
+ 'field_name' => $this->field_name,
+ 'bundle' => 'test_bundle',
+ 'label' => $this->randomName(). '_label',
+ 'description' => $this->randomName(). '_description',
+ 'weight' => mt_rand(0, 127),
+ 'settings' => array(
+ 'test_instance_setting' => $this->randomName(),
+ ),
+ 'widget' => array(
+ 'type' => 'test_field_widget',
+ 'label' => 'Test Field',
+ 'settings' => array(
+ 'test_widget_setting' => $this->randomName(),
+ )
+ )
+ );
+ field_create_instance($this->instance);
+ }
+
+ function testFieldAttachLoad() {
+ $entity_type = 'test_entity';
+ $eid = 0;
+
+ $etid = _field_sql_storage_etid($entity_type);
+ $columns = array('etid', 'entity_id', 'revision_id', 'delta', $this->field_name . '_value');
+
+ // Insert data for four revisions to the field revisions table
+ $query = db_insert($this->revision_table)->fields($columns);
+ for ($evid = 0; $evid < 4; ++$evid) {
+ $values[$evid] = array();
+ // Note: we insert one extra value ('<=' instead of '<').
+ for ($delta = 0; $delta <= $this->field['cardinality']; $delta++) {
+ $value = mt_rand(0, 127);
+ $values[$evid][] = $value;
+ $query->values(array($etid, $eid, $evid, $delta, $value));
+ }
+ }
+ $query->execute();
+
+ // Insert data for the "most current revision" into the field table
+ $query = db_insert($this->table)->fields($columns);
+ foreach ($values[0] as $delta => $value) {
+ $query->values(array($etid, $eid, 0, $delta, $value));
+ }
+ $query->execute();
+
+ // Load the "most current revision"
+ $entity = field_test_create_stub_entity($eid, 0, $this->instance['bundle']);
+ field_attach_load($entity_type, array($eid => $entity));
+ foreach ($values[0] as $delta => $value) {
+ if ($delta < $this->field['cardinality']) {
+ $this->assertEqual($entity->{$this->field_name}[$delta]['value'], $value, "Value $delta is loaded correctly for current revision");
+ }
+ else {
+ $this->assertFalse(array_key_exists($delta, $entity->{$this->field_name}), "No extraneous value gets loaded for current revision.");
+ }
+ }
+
+ // Load every revision
+ for ($evid = 0; $evid < 4; ++$evid) {
+ $entity = field_test_create_stub_entity($eid, $evid, $this->instance['bundle']);
+ field_attach_load_revision($entity_type, array($eid => $entity));
+ foreach ($values[$evid] as $delta => $value) {
+ if ($delta < $this->field['cardinality']) {
+ $this->assertEqual($entity->{$this->field_name}[$delta]['value'], $value, "Value $delta for revision $evid is loaded correctly");
+ }
+ else {
+ $this->assertFalse(array_key_exists($delta, $entity->{$this->field_name}), "No extraneous value gets loaded for revision $evid.");
+ }
+ }
+ }
+ }
+
+// function testFieldAttachLoadMultiple() {
+ // TODO : test the 'multiple' aspect of load:
+ // define 2 bundles, 3 fields
+ // bundle1 gets instances of field1, field2
+ // bundle2 gets instances of field1, field3
+ // load 2 entities (one for each bundle) in a single load
+ // check that everything gets loaded ok.
+// }
+
+ function testFieldAttachInsertAndUpdate() {
+ $entity_type = 'test_entity';
+ $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+
+ // Test insert.
+ $values = array();
+ // Note: we try to insert one extra value ('<=' instead of '<').
+ // TODO : test empty values filtering and "compression" (store consecutive deltas).
+ for ($delta = 0; $delta <= $this->field['cardinality']; $delta++) {
+ $values[$delta]['value'] = mt_rand(0, 127);
+ }
+ $entity->{$this->field_name} = $rev_values[0] = $values;
+ field_attach_insert($entity_type, $entity);
+
+ $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC);
+ foreach ($values as $delta => $value) {
+ if ($delta < $this->field['cardinality']) {
+ $this->assertEqual($rows[$delta][$this->field_name . '_value'], $value['value'], t("Value $delta is inserted correctly"));
+ }
+ else {
+ $this->assertFalse(array_key_exists($delta, $rows), "No extraneous value gets inserted.");
+ }
+ }
+
+ // Test update.
+ $entity = field_test_create_stub_entity(0, 1, $this->instance['bundle']);
+ $values = array();
+ // Note: we try to update one extra value ('<=' instead of '<').
+ for ($delta = 0; $delta <= $this->field['cardinality']; $delta++) {
+ $values[$delta]['value'] = mt_rand(0, 127);
+ }
+ $entity->{$this->field_name} = $rev_values[1] = $values;
+ field_attach_update($entity_type, $entity);
+ $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC);
+ foreach ($values as $delta => $value) {
+ if ($delta < $this->field['cardinality']) {
+ $this->assertEqual($rows[$delta][$this->field_name . '_value'], $value['value'], t("Value $delta is updated correctly"));
+ }
+ else {
+ $this->assertFalse(array_key_exists($delta, $rows), "No extraneous value gets updated.");
+ }
+ }
+
+ // Check that data for both revisions are in the revision table.
+ // We make sure each value is stored correctly, then unset it.
+ // When an entire revision's values are unset (remembering that we
+ // put one extra value in $values per revision), unset the entire
+ // revision. Then, if $rev_values is empty at the end, all
+ // revision data was found.
+ $results = db_select($this->revision_table, 't')->fields('t')->execute();
+ foreach ($results as $row) {
+ $this->assertEqual($row->{$this->field_name . '_value'}, $rev_values[$row->revision_id][$row->delta]['value'], "Value {$row->delta} for revision {$row->revision_id} stored correctly");
+ unset($rev_values[$row->revision_id][$row->delta]);
+ if (count($rev_values[$row->revision_id]) == 1) {
+ unset($rev_values[$row->revision_id]);
+ }
+ }
+ $this->assertTrue(empty($rev_values), "All values for all revisions are stored in revision table {$this->revision_table}");
+
+ // Check that update leaves the field data untouched if $object has no
+ // $field_name key.
+ unset($entity->{$this->field_name});
+ field_attach_update($entity_type, $entity);
+ $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC);
+ foreach ($values as $delta => $value) {
+ if ($delta < $this->field['cardinality']) {
+ $this->assertEqual($rows[$delta][$this->field_name . '_value'], $value['value'], t("Update with no field_name entry leaves value $delta untouched"));
+ }
+ }
+
+ // Check that update with an empty $object->$field_name empties the field.
+ $entity->{$this->field_name} = NULL;
+ field_attach_update($entity_type, $entity);
+ $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC);
+ $this->assertEqual(count($rows), 0, t("Update with an empty field_name entry empties the field."));
+ }
+
+ // Test insert and update with missing or invalid fields. For the
+ // most part, these tests pass by not crashing or causing exceptions.
+ function testFieldAttachSaveMissingData() {
+ $entity_type = 'test_entity';
+ $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+
+ // Insert: Field is missing
+ field_attach_insert($entity_type, $entity);
+ $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}"));
+ $this->assertEqual($count, 0, 'Missing field results in no inserts');
+
+ // Insert: Field is NULL
+ $entity->{$this->field_name} = NULL;
+ field_attach_insert($entity_type, $entity);
+ $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}"));
+ $this->assertEqual($count, 0, 'NULL field results in no inserts');
+
+ // Add some real data
+ $entity->{$this->field_name} = array(0 => array('value' => 1));
+ field_attach_insert($entity_type, $entity);
+ $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}"));
+ $this->assertEqual($count, 1, 'Field data saved');
+
+ // Update: Field is missing. Data should survive.
+ unset($entity->{$this->field_name});
+ field_attach_update($entity_type, $entity);
+ $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}"));
+ $this->assertEqual($count, 1, 'Missing field leaves data in table');
+
+ // Update: Field is NULL Data should be wiped.
+ $entity->{$this->field_name} = NULL;
+ field_attach_update($entity_type, $entity);
+ $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}"));
+ $this->assertEqual($count, 0, 'NULL field leaves no data in table');
+ }
+
+ function testFieldAttachViewAndPreprocess() {
+ $entity_type = 'test_entity';
+ $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+
+ // Populate values to be displayed.
+ $values = array();
+ for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
+ $values[$delta]['value'] = mt_rand(0, 127);
+ }
+ $entity->{$this->field_name} = $values;
+
+ // Simple formatter, label displayed.
+ $formatter_setting = $this->randomName();
+ $this->instance['display'] = array(
+ 'full' => array(
+ 'label' => 'above',
+ 'type' => 'field_test_default',
+ 'settings' => array(
+ 'test_formatter_setting' => $formatter_setting,
+ )
+ ),
+ );
+ field_update_instance($this->instance);
+ $entity->content = field_attach_view($entity_type, $entity);
+ $output = drupal_render($entity->content);
+ $variables = field_attach_preprocess($entity_type, $entity);
+ $variable = $this->instance['field_name'] . '_rendered';
+ $this->assertTrue(isset($variables[$variable]), "Variable $variable is available in templates.");
+ $this->content = $output;
+ $this->assertRaw($this->instance['label'], "Label is displayed.");
+ $this->content = $variables[$variable];
+ $this->assertRaw($this->instance['label'], "Label is displayed (template variable).");
+ foreach ($values as $delta => $value) {
+ $this->content = $output;
+ $this->assertRaw("$formatter_setting|{$value['value']}", "Value $delta is displayed, formatter settings are applied.");
+ $this->content = $variables[$variable];
+ $this->assertRaw("$formatter_setting|{$value['value']}", "Value $delta is displayed, formatter settings are applied (template variable).");
+ }
+
+ // Label hidden.
+ $this->instance['display']['full']['label'] = 'hidden';
+ field_update_instance($this->instance);
+ $entity->content = field_attach_view($entity_type, $entity);
+ $output = drupal_render($entity->content);
+ $variables = field_attach_preprocess($entity_type, $entity);
+ $this->content = $output;
+ $this->assertNoRaw($this->instance['label'], "Hidden label: label is not displayed.");
+ $this->content = $variables[$variable];
+ $this->assertNoRaw($this->instance['label'], "Hidden label: label is not displayed (template variable).");
+
+ // Field hidden.
+ $this->instance['display'] = array(
+ 'full' => array(
+ 'label' => 'above',
+ 'type' => 'hidden',
+
+ ),
+ );
+ field_update_instance($this->instance);
+ $entity->content = field_attach_view($entity_type, $entity);
+ $output = drupal_render($entity->content);
+ $variables = field_attach_preprocess($entity_type, $entity);
+ $this->assertTrue(isset($variables[$variable]), "Hidden field: variable $variable is available in templates.");
+ $this->content = $output;
+ $this->assertNoRaw($this->instance['label'], "Hidden field: label is not displayed.");
+ foreach ($values as $delta => $value) {
+ $this->assertNoRaw($value['value'], "Hidden field: value $delta is not displayed.");
+ }
+
+ // Multiple formatter.
+ $formatter_setting = $this->randomName();
+ $this->instance['display'] = array(
+ 'full' => array(
+ 'label' => 'above',
+ 'type' => 'field_test_multiple',
+ 'settings' => array(
+ 'test_formatter_setting_multiple' => $formatter_setting,
+ )
+ ),
+ );
+ field_update_instance($this->instance);
+ $entity->content = field_attach_view($entity_type, $entity);
+ $output = drupal_render($entity->content);
+ $variables = field_attach_preprocess($entity_type, $entity);
+ $display = $formatter_setting;
+ foreach ($values as $delta => $value) {
+ $display .= "|$delta:{$value['value']}";
+ }
+ $this->content = $output;
+ $this->assertRaw($display, "Multiple formatter: all values are displayed, formatter settings are applied.");
+ $this->content = $variables[$variable];
+ $this->assertRaw($display, "Multiple formatter: all values are displayed, formatter settings are applied (template variable).");
+
+ // TODO:
+ // - check that the 'exclude' option works (if we keep it in core)
+ // - check display order with several fields
+ }
+
+ function testFieldAttachDelete() {
+ $entity_type = 'test_entity';
+ $rev[0] = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+
+ // Create revision 0
+ $values = array();
+ for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
+ $values[$delta]['value'] = mt_rand(0, 127);
+ }
+ $rev[0]->{$this->field_name} = $values;
+ field_attach_insert($entity_type, $rev[0]);
+
+ // Create revision 1
+ $rev[1] = field_test_create_stub_entity(0, 1, $this->instance['bundle']);
+ $rev[1]->{$this->field_name} = $values;
+ field_attach_update($entity_type, $rev[1]);
+
+ // Create revision 2
+ $rev[2] = field_test_create_stub_entity(0, 2, $this->instance['bundle']);
+ $rev[2]->{$this->field_name} = $values;
+ field_attach_update($entity_type, $rev[2]);
+
+ // Confirm each revision loads
+ foreach (array_keys($rev) as $vid) {
+ $read = field_test_create_stub_entity(0, $vid, $this->instance['bundle']);
+ field_attach_load_revision($entity_type, array(0 => $read));
+ $this->assertEqual(count($read->{$this->field_name}), $this->field['cardinality'], "The test object revision $vid has {$this->field['cardinality']} values.");
+ }
+
+ // Delete revision 1, confirm the other two still load.
+ field_attach_delete_revision($entity_type, $rev[1]);
+ foreach (array(0, 2) as $vid) {
+ $read = field_test_create_stub_entity(0, $vid, $this->instance['bundle']);
+ field_attach_load_revision($entity_type, array(0 => $read));
+ $this->assertEqual(count($read->{$this->field_name}), $this->field['cardinality'], "The test object revision $vid has {$this->field['cardinality']} values.");
+ }
+
+ // Confirm the current revision still loads
+ $read = field_test_create_stub_entity(0, 2, $this->instance['bundle']);
+ field_attach_load($entity_type, array(0 => $read));
+ $this->assertEqual(count($read->{$this->field_name}), $this->field['cardinality'], "The test object current revision has {$this->field['cardinality']} values.");
+
+ // Delete all field data, confirm nothing loads
+ field_attach_delete($entity_type, $rev[2]);
+ foreach (array(0, 1, 2) as $vid) {
+ $read = field_test_create_stub_entity(0, $vid, $this->instance['bundle']);
+ field_attach_load_revision($entity_type, array(0 => $read));
+ $this->assertIdentical($read->{$this->field_name}, array(), "The test object revision $vid is deleted.");
+ }
+ $read = field_test_create_stub_entity(0, 2, $this->instance['bundle']);
+ field_attach_load($entity_type, array(0 => $read));
+ $this->assertIdentical($read->{$this->field_name}, array(), "The test object current revision is deleted.");
+ }
+
+ function testFieldAttachCreateRenameBundle() {
+ // Create a new bundle. This has to be initiated by the module so that its
+ // hook_fieldable_info() is consistent.
+ $new_bundle = 'test_bundle_' . strtolower($this->randomName());
+ field_test_create_bundle($new_bundle, $this->randomName());
+
+ // Add an instance to that bundle.
+ $this->instance['bundle'] = $new_bundle;
+ field_create_instance($this->instance);
+
+ // Save an object with data in the field.
+ $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+ $values = array();
+ for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
+ $values[$delta]['value'] = mt_rand(0, 127);
+ }
+ $entity->{$this->field_name} = $values;
+ $entity_type = 'test_entity';
+ field_attach_insert($entity_type, $entity);
+
+ // Verify the field data is present on load.
+ $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+ field_attach_load($entity_type, array(0 => $entity));
+ $this->assertEqual(count($entity->{$this->field_name}), $this->field['cardinality'], "Data are retrieved for the new bundle");
+
+ // Rename the bundle. This has to be initiated by the module so that its
+ // hook_fieldable_info() is consistent.
+ $new_bundle = 'test_bundle_' . strtolower($this->randomName());
+ field_test_rename_bundle($this->instance['bundle'], $new_bundle);
+
+ // Check that the instance definition has been updated.
+ $this->instance = field_info_instance($this->field_name, $new_bundle);
+ $this->assertIdentical($this->instance['bundle'], $new_bundle, "Bundle name has been updated in the instance.");
+
+ // Verify the field data is present on load.
+ $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+ field_attach_load($entity_type, array(0 => $entity));
+ $this->assertEqual(count($entity->{$this->field_name}), $this->field['cardinality'], "Bundle name has been updated in the field storage");
+ }
+
+ function testFieldAttachDeleteBundle() {
+ // Create a new bundle. This has to be initiated by the module so that its
+ // hook_fieldable_info() is consistent.
+ $new_bundle = 'test_bundle_' . strtolower($this->randomName());
+ field_test_create_bundle($new_bundle, $this->randomName());
+
+ // Add an instance to that bundle.
+ $this->instance['bundle'] = $new_bundle;
+ field_create_instance($this->instance);
+
+ // Create a second field for the test bundle
+ $field_name = strtolower($this->randomName(). '_field_name');
+ $table = _field_sql_storage_tablename($field_name);
+ $revision_table = _field_sql_storage_revision_tablename($field_name);
+ $field = array('field_name' => $field_name, 'type' => 'test_field', 'cardinality' => 1);
+ field_create_field($field);
+ $instance = array(
+ 'field_name' => $field_name,
+ 'bundle' => $this->instance['bundle'],
+ 'label' => $this->randomName(). '_label',
+ 'description' => $this->randomName(). '_description',
+ 'weight' => mt_rand(0, 127),
+ // test_field has no instance settings
+ 'widget' => array(
+ 'type' => 'test_field_widget',
+ 'settings' => array(
+ 'size' => mt_rand(0, 255))));
+ field_create_instance($instance);
+
+ // Save an object with data for both fields
+ $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+ $values = array();
+ for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
+ $values[$delta]['value'] = mt_rand(0, 127);
+ }
+ $entity->{$this->field_name} = $values;
+ $entity->{$field_name} = array(0 => array('value' => 99));
+ $entity_type = 'test_entity';
+ field_attach_insert($entity_type, $entity);
+
+ // Verify the fields are present on load
+ $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+ field_attach_load($entity_type, array(0 => $entity));
+ $this->assertEqual(count($entity->{$this->field_name}), 4, "First field got loaded");
+ $this->assertEqual(count($entity->{$field_name}), 1, "Second field got loaded");
+
+ // Delete the bundle. This has to be initiated by the module so that its
+ // hook_fieldable_info() is consistent.
+ field_test_delete_bundle($this->instance['bundle']);
+
+ // Verify no data gets loaded
+ $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+ field_attach_load($entity_type, array(0 => $entity));
+ $this->assertFalse(isset($entity->{$this->field_name}), "No data for first field");
+ $this->assertFalse(isset($entity->{$field_name}), "No data for second field");
+
+ // Verify that the instances are gone
+ $this->assertFalse(field_read_instance($this->field_name, $this->instance['bundle']), "First field is deleted");
+ $this->assertFalse(field_read_instance($field_name, $instance['bundle']), "Second field is deleted");
+ }
+
+ function testFieldAttachCache() {
+ // Create a revision
+ $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+ $values = array();
+ for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
+ $values[$delta]['value'] = mt_rand(0, 127);
+ }
+ $entity->{$this->field_name} = $values;
+
+ $noncached_type = 'test_entity';
+ $cached_type = 'test_cacheable_entity';
+
+ // Non-cached type:
+ $cid = "field:$noncached_type:0:0";
+
+ // Confirm no initial cache entry
+ $this->assertFalse(cache_get($cid, 'cache_field'), 'Non-cached: no initial cache entry');
+
+ // Save, and confirm no cache entry
+ field_attach_insert($noncached_type, $entity);
+ $this->assertFalse(cache_get($cid, 'cache_field'), 'Non-cached: no cache entry on save');
+
+ // Load, and confirm no cache entry
+ field_attach_load($noncached_type, array(0 => $entity));
+ $this->assertFalse(cache_get($cid, 'cache_field'), 'Non-cached: no cache entry on load');
+
+ // Cached type:
+ $cid = "field:$cached_type:0:0";
+
+ // Confirm no initial cache entry
+ $this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no initial cache entry');
+
+ // Save, and confirm no cache entry
+ field_attach_insert($cached_type, $entity);
+ $this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no cache entry on save');
+
+ // Load, and confirm cache entry
+ field_attach_load($cached_type, array(0 => $entity));
+ $cache = cache_get($cid, 'cache_field');
+ $this->assertEqual($cache->data[$this->field_name], $values, 'Cached: correct cache entry on load');
+
+ // Delete, and confirm no cache entry
+ field_attach_delete($cached_type, $entity);
+ $this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no cache entry on save');
+ }
+
+ // Verify that field_attach_validate() invokes the correct
+ // hook_field_validate. NOTE: This tests the FAPI-connected
+ // behavior of hook_field_validate. As discussed at
+ // http://groups.drupal.org/node/18019, field validation will
+ // eventually be disconnected from FAPI, at which point this
+ // function will have to be rewritten.
+ function testFieldAttachValidate() {
+ $entity_type = 'test_entity';
+ $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+
+ // Set up values to generate errors
+ $values = array();
+ for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
+ $values[$delta]['value'] = -1;
+ $values[$delta]['_error_element'] = 'field_error_' . $delta;
+ }
+ // Arrange for item 1 not to generate an error
+ $values[1]['value'] = 1;
+ $entity->{$this->field_name} = $values;
+
+ field_attach_validate($entity_type, $entity, array());
+
+ $errors = form_get_errors();
+ foreach ($values as $delta => $value) {
+ if ($value['value'] != 1) {
+ $this->assertTrue(isset($errors[$value['_error_element']]), "Error is set on {$value['_error_element']}: {$errors[$value['_error_element']]}");
+ unset($errors[$value['_error_element']]);
+ }
+ else {
+ $this->assertFalse(isset($errors[$value['_error_element']]), "Error is not set on {$value['_error_element']}");
+ }
+ }
+ $this->assertEqual(count($errors), 0, 'No extraneous form errors set');
+ }
+
+ // Validate that FAPI elements are generated. This could be much
+ // more thorough, but it does verify that the correct widgets show up.
+ function testFieldAttachForm() {
+ $entity_type = 'test_entity';
+ $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+
+ $form = $form_state = array();
+ field_attach_form($entity_type, $entity, $form, $form_state);
+
+ $this->assertEqual($form[$this->field_name]['#title'], $this->instance['label'], "Form title is {$this->instance['label']}");
+ for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
+ // field_test_widget uses 'textfield'
+ $this->assertEqual($form[$this->field_name][$delta]['value']['#type'], 'textfield', "Form delta $delta widget is textfield");
+ }
+ }
+
+ function testFieldAttachSubmit() {
+ $entity_type = 'test_entity';
+ $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+
+ // Build the form.
+ $form = $form_state = array();
+ field_attach_form($entity_type, $entity, $form, $form_state);
+
+ // Simulate incoming values.
+ $values = array();
+ $weights = array();
+ for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
+ $values[$delta]['value'] = mt_rand(1, 127);
+ // Assign random weight.
+ do {
+ $weight = mt_rand(0, $this->field['cardinality']);
+ } while (in_array($weight, $weights));
+ $weights[$delta] = $weight;
+ $values[$delta]['_weight'] = $weight;
+ }
+ // Leave an empty value. 'field_test' fields are empty if empty().
+ $values[1]['value'] = 0;
+
+ $form_state['values'] = array($this->field_name => $values);
+ field_attach_submit($entity_type, $entity, $form, $form_state);
+
+ asort($weights);
+ $expected_values = array();
+ foreach ($weights as $key => $value) {
+ if ($key != 1) {
+ $expected_values[] = array('value' => $values[$key]['value']);
+ }
+ }
+ $this->assertIdentical($entity->{$this->field_name}, $expected_values, 'Submit filters empty values');
+ }
+}
+
+class FieldInfoTestCase extends DrupalWebTestCase {
+
+ function getInfo() {
+ return array(
+ 'name' => t('Field info tests'),
+ 'description' => t("Get information about existing fields, instances and bundles."),
+ 'group' => t('Field')
+ );
+ }
+
+ function setUp() {
+ parent::setUp('field_sql_storage', 'field', 'field_test');
+ }
+
+ function testFieldInfo() {
+ // Test that field_test module's fields, widgets, and formatters show up.
+ $field_test_info = field_test_field_info();
+ $formatter_info = field_test_field_formatter_info();
+ $widget_info = field_test_field_widget_info();
+
+ $info = field_info_field_types();
+ foreach ($field_test_info as $t_key => $field_type) {
+ foreach ($field_type as $key => $val) {
+ $this->assertEqual($info[$t_key][$key], $val, t("Field type $t_key key $key is $val"));
+ }
+ $this->assertEqual($info[$t_key]['module'], 'field_test', t("Field type field_test module appears"));
+ }
+
+ $info = field_info_formatter_types();
+ foreach ($formatter_info as $f_key => $formatter) {
+ foreach ($formatter as $key => $val) {
+ $this->assertEqual($info[$f_key][$key], $val, t("Formatter type $f_key key $key is $val"));
+ }
+ $this->assertEqual($info[$f_key]['module'], 'field_test', t("Formatter type field_test module appears"));
+ }
+
+ $info = field_info_widget_types();
+ foreach ($widget_info as $w_key => $widget) {
+ foreach ($widget as $key => $val) {
+ $this->assertEqual($info[$w_key][$key], $val, t("Widget type $w_key key $key is $val"));
+ }
+ $this->assertEqual($info[$w_key]['module'], 'field_test', t("Widget type field_test module appears"));
+ }
+
+ // Verify that no fields or instances exist
+ $fields = field_info_fields();
+ $instances = field_info_instances(FIELD_TEST_BUNDLE);
+ $this->assertTrue(empty($fields), t('With no fields, info fields is empty.'));
+ $this->assertTrue(empty($instances), t('With no instances, info bundles is empty.'));
+
+ // Create a field, verify it shows up.
+ $field = array(
+ 'field_name' => strtolower($this->randomName()),
+ 'type' => 'test_field',
+ );
+ field_create_field($field);
+ $fields = field_info_fields();
+ $this->assertEqual(count($fields), 1, t('One field exists'));
+ $this->assertEqual($fields[$field['field_name']]['field_name'], $field['field_name'], t('info fields contains field name'));
+ $this->assertEqual($fields[$field['field_name']]['type'], $field['type'], t('info fields contains field type'));
+ $this->assertEqual($fields[$field['field_name']]['module'], 'field_test', t('info fields contains field module'));
+ $settings = array('test_field_setting' => 'dummy test string');
+ foreach ($settings as $key => $val) {
+ $this->assertEqual($fields[$field['field_name']]['settings'][$key], $val, t("Field setting $key has correct default value $val"));
+ }
+ $this->assertEqual($fields[$field['field_name']]['cardinality'], 1, t('info fields contains cardinality 1'));
+ $this->assertEqual($fields[$field['field_name']]['active'], 1, t('info fields contains active 1'));
+
+ // Create an instance, verify that it shows up
+ $instance = array(
+ 'field_name' => $field['field_name'],
+ 'bundle' => FIELD_TEST_BUNDLE,
+ 'label' => $this->randomName(),
+ 'description' => $this->randomName(),
+ 'weight' => mt_rand(0, 127),
+ // test_field has no instance settings
+ 'widget' => array(
+ 'type' => 'test_field_widget',
+ 'settings' => array(
+ 'test_setting' => 999)));
+ field_create_instance($instance);
+
+ $instances = field_info_instances($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 that the field_info settings convenience functions work
+ function testSettingsInfo() {
+ $info = field_test_field_info();
+ foreach ($info as $type => $data) {
+ $this->assertIdentical(field_info_field_settings($type), $data['settings'], "field_info_field_settings returns {$type}'s field settings");
+ $this->assertIdentical(field_info_instance_settings($type), $data['instance_settings'], "field_info_field_settings returns {$type}'s field instance settings");
+ }
+
+ $info = field_test_field_widget_info();
+ foreach ($info as $type => $data) {
+ $this->assertIdentical(field_info_widget_settings($type), $data['settings'], "field_info_widget_settings returns {$type}'s widget settings");
+ }
+
+ $info = field_test_field_formatter_info();
+ foreach ($info as $type => $data) {
+ $this->assertIdentical(field_info_formatter_settings($type), $data['settings'], "field_info_formatter_settings returns {$type}'s formatter settings");
+ }
+ }
+}
+
+class FieldFormTestCase extends DrupalWebTestCase {
+ function getInfo() {
+ return array(
+ 'name' => t('Field form tests'),
+ 'description' => t("Test Field form handling."),
+ 'group' => t('Field')
+ );
+ }
+
+ function setUp() {
+ parent::setUp('field_sql_storage', 'field', 'field_test');
+
+ $web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content'));
+ $this->drupalLogin($web_user);
+
+ $this->field_single = array('field_name' => strtolower($this->randomName(). '_field_name'), 'type' => 'test_field');
+ $this->field_multiple = array('field_name' => strtolower($this->randomName(). '_field_name'), 'type' => 'test_field', 'cardinality' => 4);
+ $this->field_unlimited = array('field_name' => strtolower($this->randomName(). '_field_name'), 'type' => 'test_field', 'cardinality' => FIELD_CARDINALITY_UNLIMITED);
+
+ $this->instance = array(
+ 'bundle' => 'test_bundle',
+ 'label' => $this->randomName(). '_label',
+ 'description' => $this->randomName(). '_description',
+ 'weight' => mt_rand(0, 127),
+ 'settings' => array(
+ 'test_instance_setting' => $this->randomName(),
+ ),
+ 'widget' => array(
+ 'type' => 'test_field_widget',
+ 'label' => 'Test Field',
+ 'settings' => array(
+ 'test_widget_setting' => $this->randomName(),
+ )
+ )
+ );
+ }
+
+ function testFieldFormSingle() {
+ $this->field = $this->field_single;
+ $this->field_name = $this->field['field_name'];
+ $this->instance['field_name'] = $this->field_name;
+ field_create_field($this->field);
+ field_create_instance($this->instance);
+
+ // Display creation form.
+ $this->drupalGet('test-entity/add/test-bundle');
+ $this->assertFieldByName($this->field_name . '[0][value]', '', 'Widget is displayed');
+ $this->assertNoField($this->field_name . '[1][value]', 'No extraneous widget is displayed');
+ // TODO : check that the widget is populated with default value ?
+
+ // Submit with invalid value (field-level validation).
+ $edit = array($this->field_name . '[0][value]' => -1);
+ $this->drupalPost(NULL, $edit, t('Save'));
+ $this->assertRaw(t('%name does not accept the value -1.', array('%name' => $this->instance['label'])), 'Field validation fails with invalid input.');
+ // TODO : check that the correct field is flagged for error.
+
+ // Create an entity
+ $value = mt_rand(0, 127);
+ $edit = array($this->field_name . '[0][value]' => $value);
+ $this->drupalPost(NULL, $edit, t('Save'));
+ preg_match('|test-entity/(\d+)/edit|', $this->url, $match);
+ $id = $match[1];
+ $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created');
+ $entity = field_test_entity_load($id);
+ $this->assertEqual($entity->{$this->field_name}[0]['value'], $value, 'Field value was saved');
+
+ // Display edit form.
+ $this->drupalGet('test-entity/' . $id . '/edit');
+ $this->assertFieldByName($this->field_name . '[0][value]', $value, 'Widget is displayed with the correct default value');
+ $this->assertNoField($this->field_name . '[1][value]', 'No extraneous widget is displayed');
+
+ // Update the entity.
+ $value = mt_rand(0, 127);
+ $edit = array($this->field_name . '[0][value]' => $value);
+ $this->drupalPost(NULL, $edit, t('Save'));
+ $this->assertRaw(t('test_entity @id has been updated.', array('@id' => $id)), 'Entity was updated');
+ $entity = field_test_entity_load($id);
+ $this->assertEqual($entity->{$this->field_name}[0]['value'], $value, 'Field value was updated');
+
+ // Empty the field.
+ $value = '';
+ $edit = array($this->field_name . '[0][value]' => $value);
+ $this->drupalPost('test-entity/' . $id . '/edit', $edit, t('Save'));
+ $this->assertRaw(t('test_entity @id has been updated.', array('@id' => $id)), 'Entity was updated');
+ $entity = field_test_entity_load($id);
+ $this->assertIdentical($entity->{$this->field_name}, array(), 'Field was emptied');
+
+ }
+
+ function testFieldFormSingleRequired() {
+ $this->field = $this->field_single;
+ $this->field_name = $this->field['field_name'];
+ $this->instance['field_name'] = $this->field_name;
+ $this->instance['required'] = TRUE;
+ field_create_field($this->field);
+ field_create_instance($this->instance);
+
+ // Submit with missing required value.
+ $edit = array();
+ $this->drupalPost('test-entity/add/test-bundle', $edit, t('Save'));
+ $this->assertRaw(t('!name field is required.', array('!name' => $this->instance['label'])), 'Required field with no value fails validation');
+
+ // Create an entity
+ $value = mt_rand(0, 127);
+ $edit = array($this->field_name . '[0][value]' => $value);
+ $this->drupalPost(NULL, $edit, t('Save'));
+ preg_match('|test-entity/(\d+)/edit|', $this->url, $match);
+ $id = $match[1];
+ $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created');
+ $entity = field_test_entity_load($id);
+ $this->assertEqual($entity->{$this->field_name}[0]['value'], $value, 'Field value was saved');
+
+ // Edit with missing required value.
+ $value = '';
+ $edit = array($this->field_name . '[0][value]' => $value);
+ $this->drupalPost('test-entity/' . $id . '/edit', $edit, t('Save'));
+ $this->assertRaw(t('!name field is required.', array('!name' => $this->instance['label'])), 'Required field with no value fails validation');
+ }
+
+// function testFieldFormMultiple() {
+// $this->field = $this->field_multiple;
+// $this->field_name = $this->field['field_name'];
+// $this->instance['field_name'] = $this->field_name;
+// field_create_field($this->field);
+// field_create_instance($this->instance);
+// }
+
+ function testFieldFormUnlimited() {
+ $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);
+
+ // Display creation form -> 1 widget.
+ $this->drupalGet('test-entity/add/test-bundle');
+ $this->assertFieldByName($this->field_name . '[0][value]', '', 'Widget 1 is displayed');
+ $this->assertNoField($this->field_name . '[1][value]', 'No extraneous widget is displayed');
+
+ // Press 'add more' button -> 2 widgets.
+ $this->drupalPost(NULL, array(), t('Add another item'));
+ $this->assertFieldByName($this->field_name . '[0][value]', '', 'Widget 1 is displayed');
+ $this->assertFieldByName($this->field_name . '[1][value]', '', 'New widget is displayed');
+ $this->assertNoField($this->field_name . '[2][value]', 'No extraneous widget is displayed');
+ // TODO : check that non-field inpurs are preserved ('title')...
+
+ // Yet another time so that we can play with more values -> 3 widgets.
+ $this->drupalPost(NULL, array(), t('Add another item'));
+
+ // Prepare values and weights.
+ $count = 3;
+ $delta_range = $count - 1;
+ $values = $weights = $pattern = $expected_values = $edit = array();
+ for ($delta = 0; $delta <= $delta_range; $delta++) {
+ // Assign unique random weights.
+ do {
+ $weight = mt_rand(-$delta_range, $delta_range);
+ } while (in_array($weight, $weights));
+ $weights[] = $weight;
+ $value = mt_rand(0, 127);
+ $edit["$this->field_name[$delta][value]"] = $value;
+ $edit["$this->field_name[$delta][_weight]"] = $weight;
+ // We'll need three slightly different formats to check the values.
+ $values[$weight] = $value;
+ $field_values[$weight]['value'] = (string)$value;
+ $pattern[$weight] = " value=\"$value\"";
+ }
+
+ // Press 'add more' button -> 4 widgets
+ $this->drupalPost(NULL, $edit, t('Add another item'));
+ ksort($values);
+ $values = array_values($values);
+ for ($delta = 0; $delta <= $delta_range; $delta++) {
+ $this->assertFieldByName("$this->field_name[$delta][value]", $values[$delta], "Widget $delta is displayed and has the right value");
+ $this->assertFieldByName("$this->field_name[$delta][_weight]", $delta, "Widget $delta has the right weight");
+ }
+ ksort($pattern);
+ $pattern = implode('.*', array_values($pattern));
+ $this->assertPattern("|$pattern|s", 'Widgets are displayed in the correct order');
+ $this->assertFieldByName("$this->field_name[$delta][value]", '', "New widget is displayed");
+ $this->assertFieldByName("$this->field_name[$delta][_weight]", $delta, "New widget has the right weight");
+ $this->assertNoField("$this->field_name[". ($delta + 1) . '][value]', 'No extraneous widget is displayed');
+
+ // Submit the form and create the entity.
+ $this->drupalPost(NULL, $edit, t('Save'));
+ preg_match('|test-entity/(\d+)/edit|', $this->url, $match);
+ $id = $match[1];
+ $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created');
+ $entity = field_test_entity_load($id);
+ ksort($field_values);
+ $field_values = array_values($field_values);
+ $this->assertIdentical($entity->{$this->field_name}, $field_values, 'Field values were saved in the correct order');
+
+ // display edit form: check that the expected number of widgets is displayed, with correct values
+ // change values, reorder, leave an empty value in the middle, submit: check that the entity is updated with correct values
+ // re-submit: check that the field can be emptied.
+
+ // Test with several multiple fields in a form
+ }
+
+ // check with a multiple widget (implement a textfield with comma separated values)
+
+ // check inaccessible fields are preserved on update
+ // check inaccessible fields get default value on insert (not implemented yet)
+
+}
+
+class FieldTestCase extends DrupalWebTestCase {
+ function getInfo() {
+ return array(
+ 'name' => t('Field tests'),
+ 'description' => t("Create / read /update a field."),
+ 'group' => t('Field')
+ );
+ }
+
+ function setUp() {
+ parent::setUp('field_sql_storage', 'field', 'field_test');
+ }
+
+ // TODO : test creation with
+ // - a full fledged $field structure, check that all the values are there
+ // - a minimal $field structure, check all default values are set
+ // defer actual $field comparison to a helper function, used for the two cases above
+ /**
+ * Test the creation of a field.
+ */
+ function testCreateField() {
+ $field_definition = array(
+ 'field_name' => strtolower($this->randomName()),
+ 'type' => 'test_field',
+ );
+ field_create_field($field_definition);
+
+ $field = field_read_field($field_definition['field_name']);
+
+ // Ensure that basic properties are preserved.
+ $this->assertEqual($field['field_name'], $field_definition['field_name'], t('The field name is properly saved.'));
+ $this->assertEqual($field['type'], $field_definition['type'], t('The field type is properly saved.'));
+
+ // Ensure that cardinality defaults to 1.
+ $this->assertEqual($field['cardinality'], 1, t('Cardinality defaults to 1.'));
+
+ // Ensure that default settings are present.
+ $info = field_info_field_types($field['type']);
+ $settings = $info['settings'];
+ $this->assertIdentical($settings, $field['settings'] , t('Default field settings have been written.'));
+
+ // Check that a table has been created for the field.
+ $this->assertTrue(db_table_exists('field_data_' . $field_definition['field_name']), t('A table has been created for the field.'));
+
+ // Guarantee that the name is unique.
+ try {
+ field_create_field($field_definition);
+ $this->fail(t('Cannot create two fields with the same name.'));
+ } catch (FieldException $e) {
+ $this->pass(t('Cannot create two fields with the same name.'));
+ }
+
+ // Check that invalid field names are rejected.
+ $field_definition['field_name'] += '_#';
+ try {
+ field_create_field($field_definition);
+ $this->fail(t('Cannot create a field with an invalid name.'));
+ } catch (FieldException $e) {
+ $this->pass(t('Cannot create a field with an invalid name.'));
+ }
+
+ // TODO : other failures
+ }
+
+ function testReadField() {
+
+ }
+
+ /**
+ * Test the deletion of a field.
+ */
+ function testDeleteField() {
+ // TODO: Also test deletion of the data stored in the field ?
+
+ // Create two fields (so we can test that only one is deleted).
+ $this->field = $this->drupalCreateField('test_field', 'test_field_name');
+ $this->another_field = $this->drupalCreateField('test_field', 'another_test_field_name');
+
+ // Create instances for each.
+ $this->instance_definition = array(
+ 'field_name' => $this->field['field_name'],
+ 'bundle' => FIELD_TEST_BUNDLE,
+ 'widget' => array(
+ 'type' => 'test_field_widget',
+ ),
+ );
+ field_create_instance($this->instance_definition);
+ $this->another_instance_definition = $this->instance_definition;
+ $this->another_instance_definition['field_name'] = $this->another_field['field_name'];
+ field_create_instance($this->another_instance_definition);
+
+ // Test that the first field is not deleted, and then delete it.
+ $field = field_read_field($this->field['field_name'], array('include_deleted' => TRUE));
+ $this->assertTrue(!empty($field) && empty($field['deleted']), t('A new field is not marked for deletion.'));
+ field_delete_field($this->field['field_name']);
+
+ // 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']), t('A deleted field is marked for deletion.'));
+
+ // Make sure that this field's instance is marked as deleted when it is
+ // specifically loaded.
+ $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle'], array('include_deleted' => TRUE));
+ $this->assertTrue(!empty($instance['deleted']), t('An instance for a deleted field is marked for deletion.'));
+
+ // Try to load the field normally and make sure it does not show up.
+ $field = field_read_field($this->field['field_name']);
+ $this->assertTrue(empty($field), t('A deleted field is not loaded by default.'));
+
+ // Try to load the instance normally and make sure it does not show up.
+ $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
+ $this->assertTrue(empty($instance), t('An instance for a deleted field is not loaded by default.'));
+
+ // Make sure the other field (and its field instance) are not deleted.
+ $another_field = field_read_field($this->another_field['field_name']);
+ $this->assertTrue(!empty($another_field) && empty($another_field['deleted']), t('A non-deleted field is not marked for deletion.'));
+ $another_instance = field_read_instance($this->another_instance_definition['field_name'], $this->another_instance_definition['bundle']);
+ $this->assertTrue(!empty($another_instance) && empty($another_instance['deleted']), t('An instance of a non-deleted field is not marked for deletion.'));
+ }
+}
+
+class FieldInstanceTestCase extends DrupalWebTestCase {
+ protected $field;
+
+ function getInfo() {
+ return array(
+ 'name' => t('Field instance tests'),
+ 'description' => t("Create field entities by attaching fields to entities."),
+ 'group' => t('Field')
+ );
+ }
+
+ function setUp() {
+ parent::setUp('field_sql_storage', 'field', 'field_test');
+
+ $this->field = $this->drupalCreateField('test_field');
+ $this->instance_definition = array(
+ 'field_name' => $this->field['field_name'],
+ 'bundle' => FIELD_TEST_BUNDLE,
+ );
+ }
+
+ // TODO : test creation with
+ // - a full fledged $instance structure, check that all the values are there
+ // - a minimal $instance structure, check all default values are set
+ // defer actual $instance comparison to a helper function, used for the two cases above,
+ // and for testUpdateFieldInstance
+ function testCreateFieldInstance() {
+ field_create_instance($this->instance_definition);
+ $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
+ $field_type = field_info_field_types($this->field['type']);
+
+ // Check that default values are set.
+ $this->assertIdentical($instance['required'], FALSE, t('Required defaults to false.'));
+ $this->assertIdentical($instance['label'], $this->instance_definition['field_name'], t('Label defaults to field name.'));
+ $this->assertIdentical($instance['description'], '', t('Description defaults to empty string.'));
+
+ // Check that default instance settings are set.
+ $settings = array('test_instance_setting' => 'dummy test string');
+ $this->assertIdentical($settings, $instance['settings'] , t('Default instance settings have been written.'));
+ // Check that the widget is the default one.
+ $this->assertIdentical($instance['widget']['type'], $field_type['default_widget'], t('Default widget has been written.'));
+ // Check that default widget settings are set.
+ $settings = array('test_widget_setting' => 'dummy test string');
+ $this->assertIdentical($settings, $instance['widget']['settings'] , t('Default widget settings have been written.'));
+ // Check that we have display info for 'full' build_mode.
+ $this->assertTrue(isset($instance['display']['full']), t('Display for "full" build_mode has been written.'));
+ // Check that the formatter is the default one.
+ $this->assertIdentical($instance['display']['full']['type'], $field_type['default_formatter'], t('Default formatter for "full" build_mode has been written.'));
+ // Check that the default formatter settings are set.
+ $info = field_info_formatter_types($instance['display']['full']['type']);
+ $settings = $info['settings'];
+ $this->assertIdentical($settings, $instance['display']['full']['settings'] , t('Default formatter settings for "full" build_mode have been written.'));
+
+ // Guarantee that the field/bundle combination is unique.
+ try {
+ field_create_instance($this->instance_definition);
+ $this->fail(t('Cannot create two instances with the same field / bundle combination.'));
+ }
+ catch (FieldException $e) {
+ $this->pass(t('Cannot create two instances with the same field / bundle combination.'));
+ }
+
+ // Check that the specified field exists.
+ try {
+ $this->instance_definition['field_name'] = $this->randomName();
+ field_create_instance($this->instance_definition);
+ $this->fail(t('Cannot create an instance of a non-existing field.'));
+ }
+ catch (FieldException $e) {
+ $this->pass(t('Cannot create an instance of a non-existing field.'));
+ }
+
+ // TODO: test other failures.
+ }
+
+ function testReadFieldInstance() {
+
+ }
+
+ function testUpdateFieldInstance() {
+ field_create_instance($this->instance_definition);
+ $field_type = field_info_field_types($this->field['type']);
+
+ // Check that basic changes are saved.
+ $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
+ $instance['required'] = !$instance['required'];
+ $instance['weight']++;
+ $instance['label'] = $this->randomName();
+ $instance['description'] = $this->randomName();
+ $instance['settings']['test_instance_setting'] = $this->randomName();
+ $instance['widget']['settings']['test_widget_setting'] =$this->randomName();
+ $instance['display']['full']['settings']['test_formatter_setting'] = $this->randomName();
+ field_update_instance($instance);
+
+ $instance_new = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
+ $this->assertEqual($instance['required'], $instance_new['required'], t('"required" change is saved'));
+ $this->assertEqual($instance['weight'], $instance_new['weight'], t('"weight" change is saved'));
+ $this->assertEqual($instance['label'], $instance_new['label'], t('"label" change is saved'));
+ $this->assertEqual($instance['description'], $instance_new['description'], t('"description" change is saved'));
+ $this->assertEqual($instance['widget']['settings']['test_widget_setting'], $instance_new['widget']['settings']['test_widget_setting'], t('Widget setting change is saved'));
+ $this->assertEqual($instance['display']['full']['settings']['test_formatter_setting'], $instance_new['display']['full']['settings']['test_formatter_setting'], t('Formatter setting change is saved'));
+
+ // Check that changing widget and formatter types updates the default settings.
+ $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
+ $instance['widget']['type'] = 'test_field_widget_multiple';
+ $instance['display']['full']['type'] = 'field_test_multiple';
+ field_update_instance($instance);
+
+ $instance_new = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
+ $this->assertEqual($instance['widget']['type'], $instance_new['widget']['type'] , t('Widget type change is saved.'));
+ $settings = field_info_widget_settings($instance_new['widget']['type']);
+ $this->assertIdentical($settings, array_intersect_key($instance_new['widget']['settings'], $settings) , t('Widget type change updates default settings.'));
+ $this->assertEqual($instance['display']['full']['type'], $instance_new['display']['full']['type'] , t('Formatter type change is saved.'));
+ $info = field_info_formatter_types($instance_new['display']['full']['type']);
+ $settings = $info['settings'];
+ $this->assertIdentical($settings, array_intersect_key($instance_new['display']['full']['settings'], $settings) , t('Changing formatter type updates default settings.'));
+
+ // Check that adding a new build mode is saved and gets default settings.
+ $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
+ $instance['display']['teaser'] = array();
+ field_update_instance($instance);
+
+ $instance_new = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
+ $this->assertTrue(isset($instance_new['display']['teaser']), t('Display for the new build_mode has been written.'));
+ $this->assertIdentical($instance_new['display']['teaser']['type'], $field_type['default_formatter'], t('Default formatter for the new build_mode has been written.'));
+ $info = field_info_formatter_types($instance_new['display']['teaser']['type']);
+ $settings = $info['settings'];
+ $this->assertIdentical($settings, $instance_new['display']['teaser']['settings'] , t('Default formatter settings for the new build_mode have been written.'));
+
+ // TODO: test failures.
+ }
+
+ function testDeleteFieldInstance() {
+ // TODO: Test deletion of the data stored in the field also.
+ // Need to check that data for a 'deleted' field / instance doesn't get loaded
+ // Need to check data marked deleted is cleaned on cron (not implemented yet...)
+
+ // Create two instances for the same field so we can test that only one
+ // is deleted.
+ field_create_instance($this->instance_definition);
+ $this->another_instance_definition = $this->instance_definition;
+ $this->another_instance_definition['bundle'] .= '_another_bundle';
+ field_create_instance($this->another_instance_definition);
+
+ // Test that the first instance is not deleted, and then delete it.
+ $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle'], array('include_deleted' => TRUE));
+ $this->assertTrue(!empty($instance) && empty($instance['deleted']), t('A new field instance is not marked for deletion.'));
+ field_delete_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
+
+ // Make sure the instance is marked as deleted when the instance is
+ // specifically loaded.
+ $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle'], array('include_deleted' => TRUE));
+ $this->assertTrue(!empty($instance['deleted']), t('A deleted field instance is marked for deletion.'));
+
+ // Try to load the instance normally and make sure it does not show up.
+ $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
+ $this->assertTrue(empty($instance), t('A deleted field instance is not loaded by default.'));
+
+ // Make sure the other field instance is not deleted.
+ $another_instance = field_read_instance($this->another_instance_definition['field_name'], $this->another_instance_definition['bundle']);
+ $this->assertTrue(!empty($another_instance) && empty($another_instance['deleted']), t('A non-deleted field instance is not marked for deletion.'));
+ }
+}
diff --git a/modules/field/modules/field_sql_storage/field_sql_storage.module b/modules/field/modules/field_sql_storage/field_sql_storage.module
index d7270ff85..dcca1aa55 100644
--- a/modules/field/modules/field_sql_storage/field_sql_storage.module
+++ b/modules/field/modules/field_sql_storage/field_sql_storage.module
@@ -1,388 +1,388 @@
-<?php
-// $Id$
-
-/**
- * @file
- * Default implementation of the field storage API.
- */
-
-/**
- * Implementation of hook_help().
- */
-function field_sql_storage_help($path, $arg) {
- switch ($path) {
- case 'admin/help#field_sql_storage':
- $output = '<p>' . t('The Field SQL Storage module stores Field API data in the database. It is the default field storage module, but other field storage modules may be available in the contributions repository.') . '</p>';
- return $output;
- }
-}
-
-/**
- * Generate a table name for a field data table.
- *
- * @param $name
- * The name of the field
- * @return
- * A string containing the generated name for the database table
- */
-function _field_sql_storage_tablename($name) {
- return 'field_data_' . $name;
-}
-
-/**
- * Generate a table name for a field revision archive table.
- *
- * @param $name
- * The name of the field
- * @return
- * A string containing the generated name for the database table
- */
-function _field_sql_storage_revision_tablename($name) {
- return 'field_data_revision_' . $name;
-}
-
-/**
- * Generate a column name for a field data table.
- *
- * @param $name
- * The name of the field
- * @param $column
- * The name of the column
- * @return
- * A string containing a generated column name for a field data
- * table that is unique among all other fields.
- */
-function _field_sql_storage_columnname($name, $column) {
- return $name . '_' . $column;
-}
-
-/**
- * Retrieve or assign an entity type id for an object type.
- *
- * @param $obj_type
- * The object type, such as 'node' or 'user'.
- * @return
- * The entity type id.
- *
- * TODO: We need to decide on 'entity' or 'object'.
- */
-function _field_sql_storage_etid($obj_type) {
- $etid = variable_get('field_sql_storage_' . $obj_type . '_etid', NULL);
- if (is_null($etid)) {
- $etid = db_insert('field_config_entity_type')->fields(array('type' => $obj_type))->execute();
- variable_set('field_sql_storage_' . $obj_type . '_etid', $etid);
- }
- return $etid;
-}
-
-/**
- * Return the database schema for a field. This may contain one or
- * more tables. Each table will contain the columns relevant for the
- * specified field. Leave $field['columns'] empty to get only the
- * base schema.
- *
- * @param $field
- * The field structure for which to generate a database schema.
- * @return
- * One or more tables representing the schema for the field.
- */
-function _field_sql_storage_schema($field) {
- $current = array(
- 'description' => 'Data storage for field ' . $field['field_name'],
- 'fields' => array(
- 'etid' => array(
- 'type' => 'int',
- 'unsigned' => TRUE,
- 'not null' => TRUE,
- 'description' => 'The entity type id this data is attached to',
- ),
- 'bundle' => array(
- 'type' => 'varchar',
- 'length' => 32,
- 'not null' => TRUE,
- 'default' => '',
- 'description' => 'The field instance bundle to which this row belongs, used when deleting a field instance',
- ),
- 'deleted' => array(
- 'type' => 'int',
- 'size' => 'tiny',
- 'not null' => TRUE,
- 'default' => 0,
- 'description' => 'A boolean indicating whether this data item has been deleted'
- ),
- 'entity_id' => array(
- 'type' => 'int',
- 'unsigned' => TRUE,
- 'not null' => TRUE,
- 'description' => 'The entity id this data is attached to',
- ),
- 'revision_id' => array(
- 'type' => 'int',
- 'unsigned' => TRUE,
- 'not null' => FALSE,
- 'description' => 'The entity revision id this data is attached to, or NULL if the entity type is not versioned',
- ),
- 'delta' => array(
- 'type' => 'int',
- 'unsigned' => TRUE,
- 'not null' => TRUE,
- 'description' => 'The sequence number for this data item, used for multi-value fields',
- ),
- ),
- 'primary key' => array('etid', 'entity_id', 'deleted', 'delta'),
- // TODO : index on 'bundle'
- );
-
- // Add field columns.
- foreach ($field['columns'] as $column_name => $attributes) {
- $current['fields'][_field_sql_storage_columnname($field['field_name'], $column_name)] = $attributes;
- }
-
- // Construct the revision table. The primary key includes
- // revision_id but not entity_id so that multiple revision loads can
- // use the IN operator.
- $revision = $current;
- $revision['description'] = 'Revision archive storage for field ' . $field['field_name'];
- $revision['revision_id']['description'] = 'The entity revision id this data is attached to';
- $revision['primary key'] = array('etid', 'revision_id', 'deleted', 'delta');
-
- return array(
- _field_sql_storage_tablename($field['field_name']) => $current,
- _field_sql_storage_revision_tablename($field['field_name']) => $revision,
- );
-}
-
-function field_sql_storage_field_storage_create_field($field) {
- $schema = _field_sql_storage_schema($field);
- foreach ($schema as $name => $table) {
- db_create_table($ret, $name, $table);
- }
-}
-
-function field_sql_storage_field_storage_delete_field($field_name) {
- // Mark all data associated with the field for deletion.
- $table = _field_sql_storage_tablename($field_name);
- db_update($table)
- ->fields(array('deleted' => 1))
- ->execute();
-}
-
-/**
- * Load field data for a set of objects from the database.
- *
- * @param $obj_type
- * The entity type of objects being loaded, such as 'node' or
- * 'user'.
- * @param $objects
- * The array of objects for which to load data.
- * @param $age
- * FIELD_LOAD_CURRENT to load the most recent revision for all
- * fields, or FIELD_LOAD_REVISION to load the version indicated by
- * each object.
- * @return
- * An array of field data for the objects, keyed by entity id, field
- * name, and item delta number.
- */
-function field_sql_storage_field_storage_load($obj_type, $objects, $age) {
- $etid = _field_sql_storage_etid($obj_type);
- $load_current = $age == FIELD_LOAD_CURRENT;
-
- // Gather ids needed for each field.
- $field_ids = array();
- $delta_count = array();
- foreach ($objects as $obj) {
- list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $obj);
- foreach (field_info_instances($bundle) as $instance) {
- $field_ids[$instance['field_name']][] = $load_current ? $id : $vid;
- $delta_count[$id][$instance['field_name']] = 0;
- }
- }
-
- $additions = array();
- foreach ($field_ids as $field_name => $ids) {
- $field = field_info_field($field_name);
- $table = $load_current ? _field_sql_storage_tablename($field_name) : _field_sql_storage_revision_tablename($field_name);
-
- $results = db_select($table, 't')
- ->fields('t')
- ->condition('etid', $etid)
- ->condition($load_current ? 'entity_id' : 'revision_id', $ids, 'IN')
- ->condition('deleted', 0)
- ->orderBy('delta')
- ->execute();
-
- foreach ($results as $row) {
- if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || $delta_count[$row->entity_id][$field_name] < $field['cardinality']) {
- $item = array();
- // For each column declared by the field, populate the item
- // from the prefixed database column.
- foreach ($field['columns'] as $column => $attributes) {
- $item[$column] = $row->{_field_sql_storage_columnname($field_name, $column)};
- }
-
- // Add the item to the field values for the entity.
- $additions[$row->entity_id][$field_name][] = $item;
- $delta_count[$row->entity_id][$field_name]++;
- }
- }
- }
- return $additions;
-}
-
-function field_sql_storage_field_storage_write($obj_type, $object, $update = FALSE) {
- list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
- $etid = _field_sql_storage_etid($obj_type);
-
- $instances = field_info_instances($bundle);
- foreach ($instances as $instance) {
- $field_name = $instance['field_name'];
- $table_name = _field_sql_storage_tablename($field_name);
- $revision_name = _field_sql_storage_revision_tablename($field_name);
- $field = field_read_field($field_name);
-
- // Leave the field untouched if $object comes with no $field_name property.
- // Empty the field if $object->$field_name is NULL or an empty array.
-
- // Function property_exists() is slower, so we catch the more frequent cases
- // where it's an empty array with the faster isset().
- if (isset($object->$field_name) || property_exists($object, $field_name)) {
- // Delete and insert, rather than update, in case a value was added.
- if ($update) {
- db_delete($table_name)->condition('etid', $etid)->condition('entity_id', $id)->execute();
- if (isset($vid)) {
- db_delete($revision_name)->condition('etid', $etid)->condition('entity_id', $id)->condition('revision_id', $vid)->execute();
- }
- }
-
- if ($object->$field_name) {
- // Prepare the multi-insert query.
- $columns = array('etid', 'entity_id', 'revision_id', 'bundle', 'delta');
- foreach ($field['columns'] as $column => $attributes) {
- $columns[] = _field_sql_storage_columnname($field_name, $column);
- }
- $query = db_insert($table_name)->fields($columns);
- if (isset($vid)) {
- $revision_query = db_insert($revision_name)->fields($columns);
- }
-
- $delta_count = 0;
- foreach ($object->$field_name as $delta => $item) {
- $record = array(
- 'etid' => $etid,
- 'entity_id' => $id,
- 'revision_id' => $vid,
- 'bundle' => $bundle,
- 'delta' => $delta,
- );
- foreach ($field['columns'] as $column => $attributes) {
- $record[_field_sql_storage_columnname($field_name, $column)] = isset($item[$column]) ? $item[$column] : NULL;
- }
- $query->values($record);
- if (isset($vid)) {
- $revision_query->values($record);
- }
-
- if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED && ++$delta_count == $field['cardinality']) {
- break;
- }
- }
-
- // Execute the insert.
- $query->execute();
- if (isset($vid)) {
- $revision_query->execute();
- }
- }
- }
- }
-}
-
-/**
- * Delete all field data for a single object. This function actually
- * deletes the data from the database.
- *
- * @param $obj_type
- * The entity type of the object being deleted, such as 'node' or
- * 'user'.
- * @param $object
- * The object for which to delete field data.
- */
-function field_sql_storage_field_storage_delete($obj_type, $object) {
- list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
- $etid = _field_sql_storage_etid($obj_type);
-
- $instances = field_info_instances($bundle);
- foreach ($instances as $instance) {
- $field_name = $instance['field_name'];
- $table_name = _field_sql_storage_tablename($field_name);
- $revision_name = _field_sql_storage_revision_tablename($field_name);
- db_delete($table_name)
- ->condition('etid', $etid)
- ->condition('entity_id', $id)
- ->execute();
- db_delete($revision_name)
- ->condition('etid', $etid)
- ->condition('entity_id', $id)
- ->execute();
- }
-}
-
-/**
- * Delete field data for a single revision of a single object.
- * Deleting the current (most recently written) revision is not
- * allowed as has undefined results. This function actually deletes
- * the data from the database.
- *
- * @param $obj_type
- * The entity type of the object being deleted, such as 'node' or
- * 'user'.
- * @param $object
- * The object for which to delete field data.
- */
-function field_sql_storage_field_storage_delete_revision($obj_type, $object) {
- list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
- $etid = _field_sql_storage_etid($obj_type);
-
- if (isset($vid)) {
- $instances = field_info_instances($bundle);
- foreach ($instances as $instance) {
- $field_name = $instance['field_name'];
- $revision_name = _field_sql_storage_revision_tablename($field_name);
- db_delete($revision_name)
- ->condition('etid', $etid)
- ->condition('entity_id', $id)
- ->condition('revision_id', $vid)
- ->execute();
- }
- }
-}
-
-function field_sql_storage_field_storage_delete_instance($field_name, $bundle) {
- // Mark all data associated with the field for deletion.
- $table_name = _field_sql_storage_tablename($field_name);
- $revision_name = _field_sql_storage_revision_tablename($field_name);
- db_update($table_name)
- ->fields(array('deleted' => 1))
- ->condition('bundle', $bundle)
- ->execute();
- db_update($revision_name)
- ->fields(array('deleted' => 1))
- ->condition('bundle', $bundle)
- ->execute();
-}
-
-function field_sql_storage_field_storage_rename_bundle($bundle_old, $bundle_new) {
- $instances = field_info_instances($bundle_old);
- foreach ($instances as $instance) {
- $table_name = _field_sql_storage_tablename($instance['field_name']);
- $revision_name = _field_sql_storage_revision_tablename($instance['field_name']);
- db_update($table_name)
- ->fields(array('bundle' => $bundle_new))
- ->condition('bundle', $bundle_old)
- ->execute();
- db_update($revision_name)
- ->fields(array('bundle' => $bundle_new))
- ->condition('bundle', $bundle_old)
- ->execute();
- }
+<?php
+// $Id$
+
+/**
+ * @file
+ * Default implementation of the field storage API.
+ */
+
+/**
+ * Implementation of hook_help().
+ */
+function field_sql_storage_help($path, $arg) {
+ switch ($path) {
+ case 'admin/help#field_sql_storage':
+ $output = '<p>' . t('The Field SQL Storage module stores Field API data in the database. It is the default field storage module, but other field storage modules may be available in the contributions repository.') . '</p>';
+ return $output;
+ }
+}
+
+/**
+ * Generate a table name for a field data table.
+ *
+ * @param $name
+ * The name of the field
+ * @return
+ * A string containing the generated name for the database table
+ */
+function _field_sql_storage_tablename($name) {
+ return 'field_data_' . $name;
+}
+
+/**
+ * Generate a table name for a field revision archive table.
+ *
+ * @param $name
+ * The name of the field
+ * @return
+ * A string containing the generated name for the database table
+ */
+function _field_sql_storage_revision_tablename($name) {
+ return 'field_data_revision_' . $name;
+}
+
+/**
+ * Generate a column name for a field data table.
+ *
+ * @param $name
+ * The name of the field
+ * @param $column
+ * The name of the column
+ * @return
+ * A string containing a generated column name for a field data
+ * table that is unique among all other fields.
+ */
+function _field_sql_storage_columnname($name, $column) {
+ return $name . '_' . $column;
+}
+
+/**
+ * Retrieve or assign an entity type id for an object type.
+ *
+ * @param $obj_type
+ * The object type, such as 'node' or 'user'.
+ * @return
+ * The entity type id.
+ *
+ * TODO: We need to decide on 'entity' or 'object'.
+ */
+function _field_sql_storage_etid($obj_type) {
+ $etid = variable_get('field_sql_storage_' . $obj_type . '_etid', NULL);
+ if (is_null($etid)) {
+ $etid = db_insert('field_config_entity_type')->fields(array('type' => $obj_type))->execute();
+ variable_set('field_sql_storage_' . $obj_type . '_etid', $etid);
+ }
+ return $etid;
+}
+
+/**
+ * Return the database schema for a field. This may contain one or
+ * more tables. Each table will contain the columns relevant for the
+ * specified field. Leave $field['columns'] empty to get only the
+ * base schema.
+ *
+ * @param $field
+ * The field structure for which to generate a database schema.
+ * @return
+ * One or more tables representing the schema for the field.
+ */
+function _field_sql_storage_schema($field) {
+ $current = array(
+ 'description' => 'Data storage for field ' . $field['field_name'],
+ 'fields' => array(
+ 'etid' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'description' => 'The entity type id this data is attached to',
+ ),
+ 'bundle' => array(
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'description' => 'The field instance bundle to which this row belongs, used when deleting a field instance',
+ ),
+ 'deleted' => array(
+ 'type' => 'int',
+ 'size' => 'tiny',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'A boolean indicating whether this data item has been deleted'
+ ),
+ 'entity_id' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'description' => 'The entity id this data is attached to',
+ ),
+ 'revision_id' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => FALSE,
+ 'description' => 'The entity revision id this data is attached to, or NULL if the entity type is not versioned',
+ ),
+ 'delta' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'description' => 'The sequence number for this data item, used for multi-value fields',
+ ),
+ ),
+ 'primary key' => array('etid', 'entity_id', 'deleted', 'delta'),
+ // TODO : index on 'bundle'
+ );
+
+ // Add field columns.
+ foreach ($field['columns'] as $column_name => $attributes) {
+ $current['fields'][_field_sql_storage_columnname($field['field_name'], $column_name)] = $attributes;
+ }
+
+ // Construct the revision table. The primary key includes
+ // revision_id but not entity_id so that multiple revision loads can
+ // use the IN operator.
+ $revision = $current;
+ $revision['description'] = 'Revision archive storage for field ' . $field['field_name'];
+ $revision['revision_id']['description'] = 'The entity revision id this data is attached to';
+ $revision['primary key'] = array('etid', 'revision_id', 'deleted', 'delta');
+
+ return array(
+ _field_sql_storage_tablename($field['field_name']) => $current,
+ _field_sql_storage_revision_tablename($field['field_name']) => $revision,
+ );
+}
+
+function field_sql_storage_field_storage_create_field($field) {
+ $schema = _field_sql_storage_schema($field);
+ foreach ($schema as $name => $table) {
+ db_create_table($ret, $name, $table);
+ }
+}
+
+function field_sql_storage_field_storage_delete_field($field_name) {
+ // Mark all data associated with the field for deletion.
+ $table = _field_sql_storage_tablename($field_name);
+ db_update($table)
+ ->fields(array('deleted' => 1))
+ ->execute();
+}
+
+/**
+ * Load field data for a set of objects from the database.
+ *
+ * @param $obj_type
+ * The entity type of objects being loaded, such as 'node' or
+ * 'user'.
+ * @param $objects
+ * The array of objects for which to load data.
+ * @param $age
+ * FIELD_LOAD_CURRENT to load the most recent revision for all
+ * fields, or FIELD_LOAD_REVISION to load the version indicated by
+ * each object.
+ * @return
+ * An array of field data for the objects, keyed by entity id, field
+ * name, and item delta number.
+ */
+function field_sql_storage_field_storage_load($obj_type, $objects, $age) {
+ $etid = _field_sql_storage_etid($obj_type);
+ $load_current = $age == FIELD_LOAD_CURRENT;
+
+ // Gather ids needed for each field.
+ $field_ids = array();
+ $delta_count = array();
+ foreach ($objects as $obj) {
+ list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $obj);
+ foreach (field_info_instances($bundle) as $instance) {
+ $field_ids[$instance['field_name']][] = $load_current ? $id : $vid;
+ $delta_count[$id][$instance['field_name']] = 0;
+ }
+ }
+
+ $additions = array();
+ foreach ($field_ids as $field_name => $ids) {
+ $field = field_info_field($field_name);
+ $table = $load_current ? _field_sql_storage_tablename($field_name) : _field_sql_storage_revision_tablename($field_name);
+
+ $results = db_select($table, 't')
+ ->fields('t')
+ ->condition('etid', $etid)
+ ->condition($load_current ? 'entity_id' : 'revision_id', $ids, 'IN')
+ ->condition('deleted', 0)
+ ->orderBy('delta')
+ ->execute();
+
+ foreach ($results as $row) {
+ if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || $delta_count[$row->entity_id][$field_name] < $field['cardinality']) {
+ $item = array();
+ // For each column declared by the field, populate the item
+ // from the prefixed database column.
+ foreach ($field['columns'] as $column => $attributes) {
+ $item[$column] = $row->{_field_sql_storage_columnname($field_name, $column)};
+ }
+
+ // Add the item to the field values for the entity.
+ $additions[$row->entity_id][$field_name][] = $item;
+ $delta_count[$row->entity_id][$field_name]++;
+ }
+ }
+ }
+ return $additions;
+}
+
+function field_sql_storage_field_storage_write($obj_type, $object, $update = FALSE) {
+ list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
+ $etid = _field_sql_storage_etid($obj_type);
+
+ $instances = field_info_instances($bundle);
+ foreach ($instances as $instance) {
+ $field_name = $instance['field_name'];
+ $table_name = _field_sql_storage_tablename($field_name);
+ $revision_name = _field_sql_storage_revision_tablename($field_name);
+ $field = field_read_field($field_name);
+
+ // Leave the field untouched if $object comes with no $field_name property.
+ // Empty the field if $object->$field_name is NULL or an empty array.
+
+ // Function property_exists() is slower, so we catch the more frequent cases
+ // where it's an empty array with the faster isset().
+ if (isset($object->$field_name) || property_exists($object, $field_name)) {
+ // Delete and insert, rather than update, in case a value was added.
+ if ($update) {
+ db_delete($table_name)->condition('etid', $etid)->condition('entity_id', $id)->execute();
+ if (isset($vid)) {
+ db_delete($revision_name)->condition('etid', $etid)->condition('entity_id', $id)->condition('revision_id', $vid)->execute();
+ }
+ }
+
+ if ($object->$field_name) {
+ // Prepare the multi-insert query.
+ $columns = array('etid', 'entity_id', 'revision_id', 'bundle', 'delta');
+ foreach ($field['columns'] as $column => $attributes) {
+ $columns[] = _field_sql_storage_columnname($field_name, $column);
+ }
+ $query = db_insert($table_name)->fields($columns);
+ if (isset($vid)) {
+ $revision_query = db_insert($revision_name)->fields($columns);
+ }
+
+ $delta_count = 0;
+ foreach ($object->$field_name as $delta => $item) {
+ $record = array(
+ 'etid' => $etid,
+ 'entity_id' => $id,
+ 'revision_id' => $vid,
+ 'bundle' => $bundle,
+ 'delta' => $delta,
+ );
+ foreach ($field['columns'] as $column => $attributes) {
+ $record[_field_sql_storage_columnname($field_name, $column)] = isset($item[$column]) ? $item[$column] : NULL;
+ }
+ $query->values($record);
+ if (isset($vid)) {
+ $revision_query->values($record);
+ }
+
+ if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED && ++$delta_count == $field['cardinality']) {
+ break;
+ }
+ }
+
+ // Execute the insert.
+ $query->execute();
+ if (isset($vid)) {
+ $revision_query->execute();
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Delete all field data for a single object. This function actually
+ * deletes the data from the database.
+ *
+ * @param $obj_type
+ * The entity type of the object being deleted, such as 'node' or
+ * 'user'.
+ * @param $object
+ * The object for which to delete field data.
+ */
+function field_sql_storage_field_storage_delete($obj_type, $object) {
+ list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
+ $etid = _field_sql_storage_etid($obj_type);
+
+ $instances = field_info_instances($bundle);
+ foreach ($instances as $instance) {
+ $field_name = $instance['field_name'];
+ $table_name = _field_sql_storage_tablename($field_name);
+ $revision_name = _field_sql_storage_revision_tablename($field_name);
+ db_delete($table_name)
+ ->condition('etid', $etid)
+ ->condition('entity_id', $id)
+ ->execute();
+ db_delete($revision_name)
+ ->condition('etid', $etid)
+ ->condition('entity_id', $id)
+ ->execute();
+ }
+}
+
+/**
+ * Delete field data for a single revision of a single object.
+ * Deleting the current (most recently written) revision is not
+ * allowed as has undefined results. This function actually deletes
+ * the data from the database.
+ *
+ * @param $obj_type
+ * The entity type of the object being deleted, such as 'node' or
+ * 'user'.
+ * @param $object
+ * The object for which to delete field data.
+ */
+function field_sql_storage_field_storage_delete_revision($obj_type, $object) {
+ list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
+ $etid = _field_sql_storage_etid($obj_type);
+
+ if (isset($vid)) {
+ $instances = field_info_instances($bundle);
+ foreach ($instances as $instance) {
+ $field_name = $instance['field_name'];
+ $revision_name = _field_sql_storage_revision_tablename($field_name);
+ db_delete($revision_name)
+ ->condition('etid', $etid)
+ ->condition('entity_id', $id)
+ ->condition('revision_id', $vid)
+ ->execute();
+ }
+ }
+}
+
+function field_sql_storage_field_storage_delete_instance($field_name, $bundle) {
+ // Mark all data associated with the field for deletion.
+ $table_name = _field_sql_storage_tablename($field_name);
+ $revision_name = _field_sql_storage_revision_tablename($field_name);
+ db_update($table_name)
+ ->fields(array('deleted' => 1))
+ ->condition('bundle', $bundle)
+ ->execute();
+ db_update($revision_name)
+ ->fields(array('deleted' => 1))
+ ->condition('bundle', $bundle)
+ ->execute();
+}
+
+function field_sql_storage_field_storage_rename_bundle($bundle_old, $bundle_new) {
+ $instances = field_info_instances($bundle_old);
+ foreach ($instances as $instance) {
+ $table_name = _field_sql_storage_tablename($instance['field_name']);
+ $revision_name = _field_sql_storage_revision_tablename($instance['field_name']);
+ db_update($table_name)
+ ->fields(array('bundle' => $bundle_new))
+ ->condition('bundle', $bundle_old)
+ ->execute();
+ db_update($revision_name)
+ ->fields(array('bundle' => $bundle_new))
+ ->condition('bundle', $bundle_old)
+ ->execute();
+ }
} \ No newline at end of file
diff --git a/modules/field/modules/list/list.info b/modules/field/modules/list/list.info
index cac83e831..f0770357a 100644
--- a/modules/field/modules/list/list.info
+++ b/modules/field/modules/list/list.info
@@ -3,5 +3,4 @@ name = List
description = Defines list field types. Use with Options to create selection lists.
package = Core - fields
core = 7.x
-
files[]=list.module
diff --git a/modules/field/modules/list/list.module b/modules/field/modules/list/list.module
index c02fdcc8d..2d8b3680a 100644
--- a/modules/field/modules/list/list.module
+++ b/modules/field/modules/list/list.module
@@ -1,188 +1,188 @@
-<?php
-// $Id$
-
-/**
- * @file
- * Defines list field types that can be used with the Options module.
- */
-
-/**
- * Implementation of hook_theme().
- */
-function list_theme() {
- return array(
- 'field_formatter_list_default' => array(
- 'arguments' => array('element' => NULL),
- ),
- 'field_formatter_list_key' => array(
- 'arguments' => array('element' => NULL),
- ),
- );
-}
-
-/**
- * Implementation of hook_field_info().
- */
-function list_field_info() {
- return array(
- 'list' => array(
- 'label' => t('List'),
- 'description' => t('This field stores numeric keys from key/value lists of allowed values where the key is a simple alias for the position of the value, i.e. 0|First option, 1|Second option, 2|Third option.'),
- 'settings' => array('allowed_values_function' => ''),
- 'default_widget' => 'options_select',
- 'default_formatter' => 'list_default',
- ),
- 'list_boolean' => array(
- 'label' => t('Boolean'),
- 'description' => t('This field stores simple on/off or yes/no options.'),
- 'settings' => array('allowed_values_function' => ''),
- 'default_widget' => 'options_select',
- 'default_formatter' => 'list_default',
- ),
- 'list_number' => array(
- 'label' => t('List (numeric)'),
- 'description' => t('This field stores keys from key/value lists of allowed numbers where the stored numeric key has significance and must be preserved, i.e. \'Lifetime in days\': 1|1 day, 7|1 week, 31|1 month.'),
- 'settings' => array('allowed_values_function' => ''),
- 'default_widget' => 'options_select',
- 'default_formatter' => 'list_default',
- ),
- 'list_text' => array(
- 'label' => t('List (text)'),
- 'description' => t('This field stores keys from key/value lists of allowed values where the stored key has significance and must be a varchar, i.e. \'US States\': IL|Illinois, IA|Iowa, IN|Indiana'),
- 'settings' => array('allowed_values_function' => ''),
- 'default_widget' => 'options_select',
- 'default_formatter' => 'list_default',
- ),
- );
-}
-
-/**
- * Implementation of hook_field_schema().
- */
-function list_field_columns($field) {
- switch ($field['type']) {
- case 'list_text':
- $columns = array(
- 'value' => array(
- 'type' => 'varchar',
- 'length' => 255,
- 'not null' => FALSE,
- ),
- );
- break;
- case 'list_number':
- $columns = array(
- 'value' => array(
- 'type' => 'float',
- 'unsigned' => TRUE,
- 'not null' => FALSE,
- ),
- );
- break;
- default:
- $columns = array(
- 'value' => array(
- 'type' => 'int',
- 'unsigned' => TRUE,
- 'not null' => FALSE,
- ),
- );
- break;
- }
- return $columns;
-}
-
-/**
- * Implementation of hook_field_validate().
- */
-function list_field_validate($obj_type, $object, $field, $instance, $items, $form) {
- $allowed_values = list_allowed_values($field);
- if (is_array($items)) {
- foreach ($items as $delta => $item) {
- $error_element = isset($item['_error_element']) ? $item['_error_element'] : '';
- if (is_array($item) && isset($item['_error_element'])) unset($item['_error_element']);
- if (!empty($item['value'])) {
- if (count($allowed_values) && !array_key_exists($item['value'], $allowed_values)) {
- form_set_error($error_element, t('%name: illegal value.', array('%name' => t($instance['label']))));
- }
- }
- }
- }
-}
-
-/**
- * Implementation of hook_field_is_empty().
- */
-function list_field_is_empty($item, $field) {
- if (empty($item['value']) && (string)$item['value'] !== '0') {
- return TRUE;
- }
- return FALSE;
-}
-
-/**
- * Implementation of hook_field_formatter_info().
- */
-function list_field_formatter_info() {
- return array(
- 'list_default' => array(
- 'label' => t('Default'),
- 'field types' => array('list', 'list_boolean', 'list_text', 'list_number'),
- 'behaviors' => array(
- 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
- ),
- ),
- 'list_key' => array(
- 'label' => t('Key'),
- 'field types' => array('list', 'list_boolean', 'list_text', 'list_number'),
- 'behaviors' => array(
- 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
- ),
- ),
- );
-}
-
-/**
- * Theme function for 'default' list field formatter.
- */
-function theme_field_formatter_list_default($element) {
- $field = field_info_field($element['#field_name']);
- if (($allowed_values = list_allowed_values($field)) && isset($allowed_values[$element['#item']['value']])) {
- return $allowed_values[$element['#item']['value']];
- }
- // If no match was found in allowed values, fall back to the key.
- return $element['#item']['safe'];
-}
-
-/**
- * Theme function for 'key' list field formatter.
- */
-function theme_field_formatter_list_key($element) {
- return $element['#item']['safe'];
-}
-
-/**
- * Create an array of the allowed values for this field.
- *
- * Call the allowed_values_function to retrieve the allowed
- * values array.
- *
- * TODO Rework this to create a method of selecting plugable allowed values lists.
- */
-function list_allowed_values($field) {
- static $allowed_values;
-
- if (isset($allowed_values[$field['field_name']])) {
- return $allowed_values[$field['field_name']];
- }
-
- $allowed_values[$field['field_name']] = array();
-
- if (isset($field['settings']['allowed_values_function'])) {
- $function = $field['settings']['allowed_values_function'];
- if (drupal_function_exists($function)) {
- $allowed_values[$field['field_name']] = $function($field);
- }
- }
- return $allowed_values[$field['field_name']];
-}
+<?php
+// $Id$
+
+/**
+ * @file
+ * Defines list field types that can be used with the Options module.
+ */
+
+/**
+ * Implementation of hook_theme().
+ */
+function list_theme() {
+ return array(
+ 'field_formatter_list_default' => array(
+ 'arguments' => array('element' => NULL),
+ ),
+ 'field_formatter_list_key' => array(
+ 'arguments' => array('element' => NULL),
+ ),
+ );
+}
+
+/**
+ * Implementation of hook_field_info().
+ */
+function list_field_info() {
+ return array(
+ 'list' => array(
+ 'label' => t('List'),
+ 'description' => t('This field stores numeric keys from key/value lists of allowed values where the key is a simple alias for the position of the value, i.e. 0|First option, 1|Second option, 2|Third option.'),
+ 'settings' => array('allowed_values_function' => ''),
+ 'default_widget' => 'options_select',
+ 'default_formatter' => 'list_default',
+ ),
+ 'list_boolean' => array(
+ 'label' => t('Boolean'),
+ 'description' => t('This field stores simple on/off or yes/no options.'),
+ 'settings' => array('allowed_values_function' => ''),
+ 'default_widget' => 'options_select',
+ 'default_formatter' => 'list_default',
+ ),
+ 'list_number' => array(
+ 'label' => t('List (numeric)'),
+ 'description' => t('This field stores keys from key/value lists of allowed numbers where the stored numeric key has significance and must be preserved, i.e. \'Lifetime in days\': 1|1 day, 7|1 week, 31|1 month.'),
+ 'settings' => array('allowed_values_function' => ''),
+ 'default_widget' => 'options_select',
+ 'default_formatter' => 'list_default',
+ ),
+ 'list_text' => array(
+ 'label' => t('List (text)'),
+ 'description' => t('This field stores keys from key/value lists of allowed values where the stored key has significance and must be a varchar, i.e. \'US States\': IL|Illinois, IA|Iowa, IN|Indiana'),
+ 'settings' => array('allowed_values_function' => ''),
+ 'default_widget' => 'options_select',
+ 'default_formatter' => 'list_default',
+ ),
+ );
+}
+
+/**
+ * Implementation of hook_field_schema().
+ */
+function list_field_columns($field) {
+ switch ($field['type']) {
+ case 'list_text':
+ $columns = array(
+ 'value' => array(
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => FALSE,
+ ),
+ );
+ break;
+ case 'list_number':
+ $columns = array(
+ 'value' => array(
+ 'type' => 'float',
+ 'unsigned' => TRUE,
+ 'not null' => FALSE,
+ ),
+ );
+ break;
+ default:
+ $columns = array(
+ 'value' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => FALSE,
+ ),
+ );
+ break;
+ }
+ return $columns;
+}
+
+/**
+ * Implementation of hook_field_validate().
+ */
+function list_field_validate($obj_type, $object, $field, $instance, $items, $form) {
+ $allowed_values = list_allowed_values($field);
+ if (is_array($items)) {
+ foreach ($items as $delta => $item) {
+ $error_element = isset($item['_error_element']) ? $item['_error_element'] : '';
+ if (is_array($item) && isset($item['_error_element'])) unset($item['_error_element']);
+ if (!empty($item['value'])) {
+ if (count($allowed_values) && !array_key_exists($item['value'], $allowed_values)) {
+ form_set_error($error_element, t('%name: illegal value.', array('%name' => t($instance['label']))));
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Implementation of hook_field_is_empty().
+ */
+function list_field_is_empty($item, $field) {
+ if (empty($item['value']) && (string)$item['value'] !== '0') {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * Implementation of hook_field_formatter_info().
+ */
+function list_field_formatter_info() {
+ return array(
+ 'list_default' => array(
+ 'label' => t('Default'),
+ 'field types' => array('list', 'list_boolean', 'list_text', 'list_number'),
+ 'behaviors' => array(
+ 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+ ),
+ ),
+ 'list_key' => array(
+ 'label' => t('Key'),
+ 'field types' => array('list', 'list_boolean', 'list_text', 'list_number'),
+ 'behaviors' => array(
+ 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+ ),
+ ),
+ );
+}
+
+/**
+ * Theme function for 'default' list field formatter.
+ */
+function theme_field_formatter_list_default($element) {
+ $field = field_info_field($element['#field_name']);
+ if (($allowed_values = list_allowed_values($field)) && isset($allowed_values[$element['#item']['value']])) {
+ return $allowed_values[$element['#item']['value']];
+ }
+ // If no match was found in allowed values, fall back to the key.
+ return $element['#item']['safe'];
+}
+
+/**
+ * Theme function for 'key' list field formatter.
+ */
+function theme_field_formatter_list_key($element) {
+ return $element['#item']['safe'];
+}
+
+/**
+ * Create an array of the allowed values for this field.
+ *
+ * Call the allowed_values_function to retrieve the allowed
+ * values array.
+ *
+ * TODO Rework this to create a method of selecting plugable allowed values lists.
+ */
+function list_allowed_values($field) {
+ static $allowed_values;
+
+ if (isset($allowed_values[$field['field_name']])) {
+ return $allowed_values[$field['field_name']];
+ }
+
+ $allowed_values[$field['field_name']] = array();
+
+ if (isset($field['settings']['allowed_values_function'])) {
+ $function = $field['settings']['allowed_values_function'];
+ if (drupal_function_exists($function)) {
+ $allowed_values[$field['field_name']] = $function($field);
+ }
+ }
+ return $allowed_values[$field['field_name']];
+}
diff --git a/modules/field/modules/number/number.info b/modules/field/modules/number/number.info
index 4b1563843..f74330730 100644
--- a/modules/field/modules/number/number.info
+++ b/modules/field/modules/number/number.info
@@ -3,5 +3,4 @@ name = Number
description = Defines numeric field types.
package = Core - fields
core = 7.x
-
files[]=number.module
diff --git a/modules/field/modules/number/number.module b/modules/field/modules/number/number.module
index ed894f7a6..2b3cdcac0 100644
--- a/modules/field/modules/number/number.module
+++ b/modules/field/modules/number/number.module
@@ -1,425 +1,425 @@
-<?php
-// $Id$
-
-/**
- * @file
- * Defines numeric field types.
- */
-
-/**
- * Implementation of hook_theme().
- */
-function number_theme() {
- return array(
- 'number' => array('arguments' => array('element' => NULL)),
- 'field_formatter_number_integer' => array('arguments' => array('element' => NULL), 'function' => 'theme_field_formatter_number'),
- 'field_formatter_number_decimal' => array('arguments' => array('element' => NULL), 'function' => 'theme_field_formatter_number'),
- 'field_formatter_number_unformatted' => array('arguments' => array('element' => NULL)),
- );
-}
-
-/**
- * Implementation of hook_field_info().
- */
-function number_field_info() {
- return array(
- 'number_integer' => array(
- 'label' => t('Integer'),
- 'description' => t('This field stores a number in the database as an integer.'),
- 'instance_settings' => array('min' => '', 'max' => '', 'prefix' => '', 'suffix' => ''),
- 'default_widget' => 'number',
- 'default_formatter' => 'number_integer',
- ),
- 'number_decimal' => array(
- 'label' => t('Decimal'),
- 'description' => t('This field stores a number in the database in a fixed decimal format.'),
- 'settings' => array('precision' => 10, 'scale' => 2, 'decimal' => ' .'),
- 'instance_settings' => array('min' => '', 'max' => '', 'prefix' => '', 'suffix' => ''),
- 'default_widget' => 'number',
- 'default_formatter' => 'number_integer',
- ),
- 'number_float' => array(
- 'label' => t('Float'),
- 'description' => t('This field stores a number in the database in a floating point format.'),
- 'instance_settings' => array('min' => '', 'max' => '', 'prefix' => '', 'suffix' => ''),
- 'default_widget' => 'number',
- 'default_formatter' => 'number_integer',
- ),
- );
-}
-
-function number_field_columns($field) {
- switch ($field['type']) {
- case 'number_integer' :
- $colums = array(
- 'value' => array(
- 'type' => 'int',
- 'not null' => FALSE
- ),
- );
- break;
-
- case 'number_float' :
- $colums = array(
- 'value' => array(
- 'type' => 'float',
- 'not null' => FALSE
- ),
- );
- break;
-
- case 'number_decimal' :
- $colums = array(
- 'value' => array(
- 'type' => 'numeric',
- 'precision' => $field['settings']['precision'],
- 'scale' => $field['settings']['scale'],
- 'not null' => FALSE
- ),
- );
- break;
- }
- return $colums;
-}
-
-/**
- * Implementation of hook_field_validate().
- */
-function number_field_validate($obj_type, $node, $field, $instance, &$items, $form) {
- if (is_array($items)) {
- foreach ($items as $delta => $item) {
- $error_element = isset($item['_error_element']) ? $item['_error_element'] : '';
- if (is_array($item) && isset($item['_error_element'])) unset($item['_error_element']);
- if ($item['value'] != '') {
- if (is_numeric($instance['settings']['min']) && $item['value'] < $instance['settings']['min']) {
- form_set_error($error_element, t('%name: the value may be no smaller than %min.', array('%name' => t($instance['label']), '%min' => $instance['settings']['min'])));
- }
- if (is_numeric($instance['settings']['max']) && $item['value'] > $instance['settings']['max']) {
- form_set_error($error_element, t('%name: the value may be no larger than %max.', array('%name' => t($instance['label']), '%max' => $instance['settings']['max'])));
- }
- }
- }
- }
-}
-
-/**
- * Implementation of hook_content_is_empty().
- */
-function number_field_is_empty($item, $field) {
- if (empty($item['value']) && (string)$item['value'] !== '0') {
- return TRUE;
- }
- return FALSE;
-}
-
-/**
- * Implementation of hook_field_formatter_info().
- */
-function number_field_formatter_info() {
- return array(
- 'number_integer' => array(
- 'label' => t('default'),
- 'field types' => array('number_integer'),
- 'settings' => array(
- 'thousand_separator' => ' ',
- 'decimal_separator' => '.',
- 'scale' => 0,
- 'prefix_suffix' => TRUE,
- ),
- 'behaviors' => array(
- 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
- ),
- ),
- 'number_decimal' => array(
- 'label' => t('default'),
- 'field types' => array('number_decimal', 'number_float'),
- 'settings' => array(
- 'thousand_separator' => ' ',
- 'decimal_separator' => '.',
- 'scale' => 2,
- 'prefix_suffix' => TRUE,
- ),
- 'behaviors' => array(
- 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
- ),
- ),
- 'number_unformatted' => array(
- 'label' => t('unformatted'),
- 'field types' => array('number_integer', 'number_decimal', 'number_float'),
- 'behaviors' => array(
- 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
- ),
- ),
- );
-}
-
-/**
- * Theme function for 'unformatted' number field formatter.
- */
-function theme_field_formatter_number_unformatted($element) {
- return $element['#item']['value'];
-}
-
-/**
- * Proxy theme function for number field formatters.
- */
-function theme_field_formatter_number($element) {
- $field = field_info_field($element['#field_name']);
- $instance = field_info_instance($element['#field_name'], $element['#bundle']);
- $value = $element['#item']['value'];
- $settings = $element['#settings'];
- $formatter_type = $element['#formatter'];
-
- if (empty($value) && $value !== '0') {
- return '';
- }
-
- $output = number_format($value, $settings['scale'], $settings['decimal_separator'], $settings['thousand_separator']);
-
- if ($settings['prefix_suffix']) {
- $prefixes = isset($instance['settings']['prefix']) ? explode('|', check_plain($instance['settings']['prefix'])) : array(0 => '');
- $suffixes = isset($instance['settings']['suffix']) ? explode('|', check_plain($instance['settings']['suffix'])) : array(0 => '');
- $prefix = (count($prefixes) > 1) ? format_plural($value, $prefixes[0], $prefixes[1]) : $prefixes[0];
- $suffix = (count($suffixes) > 1) ? format_plural($value, $suffixes[0], $suffixes[1]) : $suffixes[0];
- $output = $prefix . $output . $suffix;
- }
-
- return $output;
-}
-
-/**
- * Implementation of hook_field_widget_info().
- *
- * Here we indicate that the Field module will handle
- * multiple values for these widgets.
- *
- * Callbacks can be omitted if default handing is used.
- * They're included here just so this module can be used
- * as an example for custom modules that might do things
- * differently.
- */
-function number_field_widget_info() {
- return array(
- 'number' => array(
- 'label' => t('Text field'),
- 'field types' => array('number_integer', 'number_decimal', 'number_float'),
- 'behaviors' => array(
- 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
- 'default value' => FIELD_BEHAVIOR_DEFAULT,
- ),
- ),
- );
-}
-
-/**
- * Implementation of FAPI hook_elements().
- *
- * Any FAPI callbacks needed for individual widgets can be declared here,
- * and the element will be passed to those callbacks for processing.
- *
- * Drupal will automatically theme the element using a theme with
- * the same name as the hook_elements key.
- *
- * Includes a regex to check for valid values as an additional parameter
- * the validator can use. The regex can be overridden if necessary.
- */
-function number_elements() {
- return array(
- 'number' => array(
- '#input' => TRUE,
- '#columns' => array('value'), '#delta' => 0,
- '#process' => array('number_process'),
- ),
- );
-}
-
-/**
- * Implementation of hook_field_widget().
- *
- * Attach a single form element to the form. It will be built out and
- * validated in the callback(s) listed in hook_elements. We build it
- * out in the callbacks rather than here in hook_widget so it can be
- * plugged into any module that can provide it with valid
- * $field information.
- *
- * Field module will set the weight, field name and delta values
- * for each form element.
- *
- * If there are multiple values for this field, the Field module will
- * call this function as many times as needed.
- *
- * @param $form
- * the entire form array, $form['#node'] holds node information
- * @param $form_state
- * the form_state, $form_state['values'] holds the form values.
- * @param $field
- * The field structure.
- * @param $instance
- * the field instance array
- * @param $delta
- * the order of this item in the array of subelements (0, 1, 2, etc)
- *
- * @return
- * the form item for a single element for this field
- */
-function number_field_widget(&$form, &$form_state, $field, $instance, $items, $delta = 0) {
- $element = array(
- '#type' => $instance['widget']['type'],
- '#default_value' => isset($items[$delta]) ? $items[$delta] : NULL,
- );
- return $element;
-}
-
-/**
- * Process an individual element.
- *
- * Build the form element. When creating a form using FAPI #process,
- * note that $element['#value'] is already set.
- *
- * The $field and $instance arrays are in $form['#fields'][$element['#field_name']].
- */
-function number_process($element, $edit, $form_state, $form) {
- $field_name = $element['#field_name'];
- $field = field_info_field($element['#field_name']);
- $instance = field_info_instance($element['#field_name'], $element['#bundle']);
- $field_key = $element['#columns'][0];
-
- $value = isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : '';
- if ($field['type'] == 'number_decimal') {
- $value = str_replace('.', $field['settings']['decimal'], $value);
- }
-
- $element[$field_key] = array(
- '#type' => 'textfield',
- '#default_value' => $value,
- // Need to allow a slightly larger size that the field length to allow
- // for some configurations where all characters won't fit in input field.
- '#size' => $field['type'] == 'number_decimal' ? $field['settings']['precision'] + 2 : 12,
- '#maxlength' => $field['type'] == 'number_decimal' ? $field['settings']['precision'] : 10,
- '#attributes' => array('class' => 'number'),
- // The following values were set by the Field module and need
- // to be passed down to the nested element.
- '#title' => $element['#title'],
- '#description' => $element['#description'],
- '#required' => $element['#required'],
- '#field_name' => $element['#field_name'],
- '#bundle' => $element['#bundle'],
- '#delta' => $element['#delta'],
- '#columns' => $element['#columns'],
- );
-
- if (!empty($instance['settings']['prefix'])) {
- $prefixes = explode('|', $instance['settings']['prefix']);
- $element[$field_key]['#field_prefix'] = array_pop($prefixes);
- }
- if (!empty($instance['settings']['suffix'])) {
- $suffixes = explode('|', $instance['settings']['suffix']);
- $element[$field_key]['#field_suffix'] = array_pop($suffixes);
- }
-
- // Make sure we don't wipe out element validation added elsewhere.
- if (empty($element['#element_validate'])) {
- $element['#element_validate'] = array();
- }
- switch ($field['type']) {
- case 'number_float':
- $element['#element_validate'][] = 'number_float_validate';
- break;
- case 'number_integer':
- $element['#element_validate'][] = 'number_integer_validate';
- break;
- case 'number_decimal':
- $element['#element_validate'][] = 'number_decimal_validate';
- break;
- }
-
- // Used so that hook_field('validate') knows where to flag an error.
- $element['_error_element'] = array(
- '#type' => 'value',
- '#value' => implode('][', array_merge($element['#parents'], array($field_key))),
- );
-
- return $element;
-}
-
-/**
- * FAPI validation of an individual float element.
- */
-function number_float_validate($element, &$form_state) {
- $field = field_info_field($element['#field_name']);
- $instance = field_info_instance($element['#field_name'], $element['#bundle']);
- $field_key = $element['#columns'][0];
- $value = $element['#value'][$field_key];
-
- if (($element[$field_key]['#required'] || !empty($value))) {
- $start = $value;
- $value = preg_replace('@[^-0-9\.]@', '', $value);
- if ($start != $value) {
- $error_field = implode('][', $element['#parents']) . '][' . $field_key;
- form_set_error($error_field, t('Only numbers and decimals are allowed in %field.', array('%field' => t($instance['label']))));
- }
- else {
- form_set_value($element[$field_key], $value, $form_state);
- }
- }
-}
-
-/**
- * FAPI validation of an individual integer element.
- */
-function number_integer_validate($element, &$form_state) {
- $field = field_info_field($element['#field_name']);
- $instance = field_info_instance($element['#field_name'], $element['#bundle']);
- $field_key = $element['#columns'][0];
- $value = $element['#value'][$field_key];
-
- if (($element[$field_key]['#required'] || !empty($value))) {
- $start = $value;
- $value = preg_replace('@[^-0-9]@', '', $value);
- if ($start != $value) {
- $error_field = implode('][', $element['#parents']) . '][' . $field_key;
- form_set_error($error_field, t('Only numbers are allowed in %field.', array('%field' => t($instance['label']))));
- }
- else {
- form_set_value($element[$field_key], $value, $form_state);
- }
- }
-}
-
-/**
- * FAPI validation of an individual decimal element.
- */
-function number_decimal_validate($element, &$form_state) {
- $field = field_info_field($element['#field_name']);
- $instance = field_info_instance($element['#field_name'], $element['#bundle']);
- $field_key = $element['#columns'][0];
- $value = $element['#value'][$field_key];
-
- if (($element[$field_key]['#required'] || !empty($value))) {
- $start = $value;
- $value = preg_replace('@[^-0-9\\' . $field['settings']['decimal'] . ']@', '', $value);
- if ($start != $value) {
- $error_field = implode('][', $element['#parents']) . '][' . $field_key;
- form_set_error($error_field, t('Only numbers and the decimal character (%decimal) are allowed in %field.', array('%decimal' => $field['settings']['decimal'], '%field' => t($instance['label']))));
- }
- else {
- $value = str_replace($field['settings']['decimal'], ' .', $value);
- $value = round($value, $field['settings']['scale']);
- form_set_value($element[$field_key], $value, $form_state);
- }
- }
-}
-
-/**
- * FAPI theme for an individual number element.
- *
- * The textfield is already rendered by the textfield
- * theme and the HTML output lives in $element['#children'].
- * Override this theme to make custom changes to the output.
- *
- * $element['#field_name'] contains the field name
- * $element['#delta] is the position of this element in the group
- */
-function theme_number($element) {
- return $element['#children'];
+<?php
+// $Id$
+
+/**
+ * @file
+ * Defines numeric field types.
+ */
+
+/**
+ * Implementation of hook_theme().
+ */
+function number_theme() {
+ return array(
+ 'number' => array('arguments' => array('element' => NULL)),
+ 'field_formatter_number_integer' => array('arguments' => array('element' => NULL), 'function' => 'theme_field_formatter_number'),
+ 'field_formatter_number_decimal' => array('arguments' => array('element' => NULL), 'function' => 'theme_field_formatter_number'),
+ 'field_formatter_number_unformatted' => array('arguments' => array('element' => NULL)),
+ );
+}
+
+/**
+ * Implementation of hook_field_info().
+ */
+function number_field_info() {
+ return array(
+ 'number_integer' => array(
+ 'label' => t('Integer'),
+ 'description' => t('This field stores a number in the database as an integer.'),
+ 'instance_settings' => array('min' => '', 'max' => '', 'prefix' => '', 'suffix' => ''),
+ 'default_widget' => 'number',
+ 'default_formatter' => 'number_integer',
+ ),
+ 'number_decimal' => array(
+ 'label' => t('Decimal'),
+ 'description' => t('This field stores a number in the database in a fixed decimal format.'),
+ 'settings' => array('precision' => 10, 'scale' => 2, 'decimal' => ' .'),
+ 'instance_settings' => array('min' => '', 'max' => '', 'prefix' => '', 'suffix' => ''),
+ 'default_widget' => 'number',
+ 'default_formatter' => 'number_integer',
+ ),
+ 'number_float' => array(
+ 'label' => t('Float'),
+ 'description' => t('This field stores a number in the database in a floating point format.'),
+ 'instance_settings' => array('min' => '', 'max' => '', 'prefix' => '', 'suffix' => ''),
+ 'default_widget' => 'number',
+ 'default_formatter' => 'number_integer',
+ ),
+ );
+}
+
+function number_field_columns($field) {
+ switch ($field['type']) {
+ case 'number_integer' :
+ $colums = array(
+ 'value' => array(
+ 'type' => 'int',
+ 'not null' => FALSE
+ ),
+ );
+ break;
+
+ case 'number_float' :
+ $colums = array(
+ 'value' => array(
+ 'type' => 'float',
+ 'not null' => FALSE
+ ),
+ );
+ break;
+
+ case 'number_decimal' :
+ $colums = array(
+ 'value' => array(
+ 'type' => 'numeric',
+ 'precision' => $field['settings']['precision'],
+ 'scale' => $field['settings']['scale'],
+ 'not null' => FALSE
+ ),
+ );
+ break;
+ }
+ return $colums;
+}
+
+/**
+ * Implementation of hook_field_validate().
+ */
+function number_field_validate($obj_type, $node, $field, $instance, &$items, $form) {
+ if (is_array($items)) {
+ foreach ($items as $delta => $item) {
+ $error_element = isset($item['_error_element']) ? $item['_error_element'] : '';
+ if (is_array($item) && isset($item['_error_element'])) unset($item['_error_element']);
+ if ($item['value'] != '') {
+ if (is_numeric($instance['settings']['min']) && $item['value'] < $instance['settings']['min']) {
+ form_set_error($error_element, t('%name: the value may be no smaller than %min.', array('%name' => t($instance['label']), '%min' => $instance['settings']['min'])));
+ }
+ if (is_numeric($instance['settings']['max']) && $item['value'] > $instance['settings']['max']) {
+ form_set_error($error_element, t('%name: the value may be no larger than %max.', array('%name' => t($instance['label']), '%max' => $instance['settings']['max'])));
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Implementation of hook_content_is_empty().
+ */
+function number_field_is_empty($item, $field) {
+ if (empty($item['value']) && (string)$item['value'] !== '0') {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * Implementation of hook_field_formatter_info().
+ */
+function number_field_formatter_info() {
+ return array(
+ 'number_integer' => array(
+ 'label' => t('default'),
+ 'field types' => array('number_integer'),
+ 'settings' => array(
+ 'thousand_separator' => ' ',
+ 'decimal_separator' => '.',
+ 'scale' => 0,
+ 'prefix_suffix' => TRUE,
+ ),
+ 'behaviors' => array(
+ 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+ ),
+ ),
+ 'number_decimal' => array(
+ 'label' => t('default'),
+ 'field types' => array('number_decimal', 'number_float'),
+ 'settings' => array(
+ 'thousand_separator' => ' ',
+ 'decimal_separator' => '.',
+ 'scale' => 2,
+ 'prefix_suffix' => TRUE,
+ ),
+ 'behaviors' => array(
+ 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+ ),
+ ),
+ 'number_unformatted' => array(
+ 'label' => t('unformatted'),
+ 'field types' => array('number_integer', 'number_decimal', 'number_float'),
+ 'behaviors' => array(
+ 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+ ),
+ ),
+ );
+}
+
+/**
+ * Theme function for 'unformatted' number field formatter.
+ */
+function theme_field_formatter_number_unformatted($element) {
+ return $element['#item']['value'];
+}
+
+/**
+ * Proxy theme function for number field formatters.
+ */
+function theme_field_formatter_number($element) {
+ $field = field_info_field($element['#field_name']);
+ $instance = field_info_instance($element['#field_name'], $element['#bundle']);
+ $value = $element['#item']['value'];
+ $settings = $element['#settings'];
+ $formatter_type = $element['#formatter'];
+
+ if (empty($value) && $value !== '0') {
+ return '';
+ }
+
+ $output = number_format($value, $settings['scale'], $settings['decimal_separator'], $settings['thousand_separator']);
+
+ if ($settings['prefix_suffix']) {
+ $prefixes = isset($instance['settings']['prefix']) ? explode('|', check_plain($instance['settings']['prefix'])) : array(0 => '');
+ $suffixes = isset($instance['settings']['suffix']) ? explode('|', check_plain($instance['settings']['suffix'])) : array(0 => '');
+ $prefix = (count($prefixes) > 1) ? format_plural($value, $prefixes[0], $prefixes[1]) : $prefixes[0];
+ $suffix = (count($suffixes) > 1) ? format_plural($value, $suffixes[0], $suffixes[1]) : $suffixes[0];
+ $output = $prefix . $output . $suffix;
+ }
+
+ return $output;
+}
+
+/**
+ * Implementation of hook_field_widget_info().
+ *
+ * Here we indicate that the Field module will handle
+ * multiple values for these widgets.
+ *
+ * Callbacks can be omitted if default handing is used.
+ * They're included here just so this module can be used
+ * as an example for custom modules that might do things
+ * differently.
+ */
+function number_field_widget_info() {
+ return array(
+ 'number' => array(
+ 'label' => t('Text field'),
+ 'field types' => array('number_integer', 'number_decimal', 'number_float'),
+ 'behaviors' => array(
+ 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+ 'default value' => FIELD_BEHAVIOR_DEFAULT,
+ ),
+ ),
+ );
+}
+
+/**
+ * Implementation of FAPI hook_elements().
+ *
+ * Any FAPI callbacks needed for individual widgets can be declared here,
+ * and the element will be passed to those callbacks for processing.
+ *
+ * Drupal will automatically theme the element using a theme with
+ * the same name as the hook_elements key.
+ *
+ * Includes a regex to check for valid values as an additional parameter
+ * the validator can use. The regex can be overridden if necessary.
+ */
+function number_elements() {
+ return array(
+ 'number' => array(
+ '#input' => TRUE,
+ '#columns' => array('value'), '#delta' => 0,
+ '#process' => array('number_process'),
+ ),
+ );
+}
+
+/**
+ * Implementation of hook_field_widget().
+ *
+ * Attach a single form element to the form. It will be built out and
+ * validated in the callback(s) listed in hook_elements. We build it
+ * out in the callbacks rather than here in hook_widget so it can be
+ * plugged into any module that can provide it with valid
+ * $field information.
+ *
+ * Field module will set the weight, field name and delta values
+ * for each form element.
+ *
+ * If there are multiple values for this field, the Field module will
+ * call this function as many times as needed.
+ *
+ * @param $form
+ * the entire form array, $form['#node'] holds node information
+ * @param $form_state
+ * the form_state, $form_state['values'] holds the form values.
+ * @param $field
+ * The field structure.
+ * @param $instance
+ * the field instance array
+ * @param $delta
+ * the order of this item in the array of subelements (0, 1, 2, etc)
+ *
+ * @return
+ * the form item for a single element for this field
+ */
+function number_field_widget(&$form, &$form_state, $field, $instance, $items, $delta = 0) {
+ $element = array(
+ '#type' => $instance['widget']['type'],
+ '#default_value' => isset($items[$delta]) ? $items[$delta] : NULL,
+ );
+ return $element;
+}
+
+/**
+ * Process an individual element.
+ *
+ * Build the form element. When creating a form using FAPI #process,
+ * note that $element['#value'] is already set.
+ *
+ * The $field and $instance arrays are in $form['#fields'][$element['#field_name']].
+ */
+function number_process($element, $edit, $form_state, $form) {
+ $field_name = $element['#field_name'];
+ $field = field_info_field($element['#field_name']);
+ $instance = field_info_instance($element['#field_name'], $element['#bundle']);
+ $field_key = $element['#columns'][0];
+
+ $value = isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : '';
+ if ($field['type'] == 'number_decimal') {
+ $value = str_replace('.', $field['settings']['decimal'], $value);
+ }
+
+ $element[$field_key] = array(
+ '#type' => 'textfield',
+ '#default_value' => $value,
+ // Need to allow a slightly larger size that the field length to allow
+ // for some configurations where all characters won't fit in input field.
+ '#size' => $field['type'] == 'number_decimal' ? $field['settings']['precision'] + 2 : 12,
+ '#maxlength' => $field['type'] == 'number_decimal' ? $field['settings']['precision'] : 10,
+ '#attributes' => array('class' => 'number'),
+ // The following values were set by the Field module and need
+ // to be passed down to the nested element.
+ '#title' => $element['#title'],
+ '#description' => $element['#description'],
+ '#required' => $element['#required'],
+ '#field_name' => $element['#field_name'],
+ '#bundle' => $element['#bundle'],
+ '#delta' => $element['#delta'],
+ '#columns' => $element['#columns'],
+ );
+
+ if (!empty($instance['settings']['prefix'])) {
+ $prefixes = explode('|', $instance['settings']['prefix']);
+ $element[$field_key]['#field_prefix'] = array_pop($prefixes);
+ }
+ if (!empty($instance['settings']['suffix'])) {
+ $suffixes = explode('|', $instance['settings']['suffix']);
+ $element[$field_key]['#field_suffix'] = array_pop($suffixes);
+ }
+
+ // Make sure we don't wipe out element validation added elsewhere.
+ if (empty($element['#element_validate'])) {
+ $element['#element_validate'] = array();
+ }
+ switch ($field['type']) {
+ case 'number_float':
+ $element['#element_validate'][] = 'number_float_validate';
+ break;
+ case 'number_integer':
+ $element['#element_validate'][] = 'number_integer_validate';
+ break;
+ case 'number_decimal':
+ $element['#element_validate'][] = 'number_decimal_validate';
+ break;
+ }
+
+ // Used so that hook_field('validate') knows where to flag an error.
+ $element['_error_element'] = array(
+ '#type' => 'value',
+ '#value' => implode('][', array_merge($element['#parents'], array($field_key))),
+ );
+
+ return $element;
+}
+
+/**
+ * FAPI validation of an individual float element.
+ */
+function number_float_validate($element, &$form_state) {
+ $field = field_info_field($element['#field_name']);
+ $instance = field_info_instance($element['#field_name'], $element['#bundle']);
+ $field_key = $element['#columns'][0];
+ $value = $element['#value'][$field_key];
+
+ if (($element[$field_key]['#required'] || !empty($value))) {
+ $start = $value;
+ $value = preg_replace('@[^-0-9\.]@', '', $value);
+ if ($start != $value) {
+ $error_field = implode('][', $element['#parents']) . '][' . $field_key;
+ form_set_error($error_field, t('Only numbers and decimals are allowed in %field.', array('%field' => t($instance['label']))));
+ }
+ else {
+ form_set_value($element[$field_key], $value, $form_state);
+ }
+ }
+}
+
+/**
+ * FAPI validation of an individual integer element.
+ */
+function number_integer_validate($element, &$form_state) {
+ $field = field_info_field($element['#field_name']);
+ $instance = field_info_instance($element['#field_name'], $element['#bundle']);
+ $field_key = $element['#columns'][0];
+ $value = $element['#value'][$field_key];
+
+ if (($element[$field_key]['#required'] || !empty($value))) {
+ $start = $value;
+ $value = preg_replace('@[^-0-9]@', '', $value);
+ if ($start != $value) {
+ $error_field = implode('][', $element['#parents']) . '][' . $field_key;
+ form_set_error($error_field, t('Only numbers are allowed in %field.', array('%field' => t($instance['label']))));
+ }
+ else {
+ form_set_value($element[$field_key], $value, $form_state);
+ }
+ }
+}
+
+/**
+ * FAPI validation of an individual decimal element.
+ */
+function number_decimal_validate($element, &$form_state) {
+ $field = field_info_field($element['#field_name']);
+ $instance = field_info_instance($element['#field_name'], $element['#bundle']);
+ $field_key = $element['#columns'][0];
+ $value = $element['#value'][$field_key];
+
+ if (($element[$field_key]['#required'] || !empty($value))) {
+ $start = $value;
+ $value = preg_replace('@[^-0-9\\' . $field['settings']['decimal'] . ']@', '', $value);
+ if ($start != $value) {
+ $error_field = implode('][', $element['#parents']) . '][' . $field_key;
+ form_set_error($error_field, t('Only numbers and the decimal character (%decimal) are allowed in %field.', array('%decimal' => $field['settings']['decimal'], '%field' => t($instance['label']))));
+ }
+ else {
+ $value = str_replace($field['settings']['decimal'], ' .', $value);
+ $value = round($value, $field['settings']['scale']);
+ form_set_value($element[$field_key], $value, $form_state);
+ }
+ }
+}
+
+/**
+ * FAPI theme for an individual number element.
+ *
+ * The textfield is already rendered by the textfield
+ * theme and the HTML output lives in $element['#children'].
+ * Override this theme to make custom changes to the output.
+ *
+ * $element['#field_name'] contains the field name
+ * $element['#delta] is the position of this element in the group
+ */
+function theme_number($element) {
+ return $element['#children'];
} \ No newline at end of file
diff --git a/modules/field/modules/options/options.module b/modules/field/modules/options/options.module
index ddc5252f8..a88c431eb 100644
--- a/modules/field/modules/options/options.module
+++ b/modules/field/modules/options/options.module
@@ -1,422 +1,422 @@
-<?php
-// $Id$
-
-/**
- * @file
- * Defines selection, check box and radio button widgets for text and numeric fields.
- */
-/**
- * Implementation of hook_theme().
- */
-function options_theme() {
- return array(
- 'options_select' => array(
- 'arguments' => array('element' => NULL),
- ),
- 'options_buttons' => array(
- 'arguments' => array('element' => NULL),
- ),
- 'options_onoff' => array(
- 'arguments' => array('element' => NULL),
- ),
- 'options_none' => array(
- 'arguments' => array('widget_type' => NULL, 'field_name' => NULL, 'node_type' => NULL),
- ),
- );
-}
-
-/**
- * Implementation of hook_field_widget_info().
- *
- * We need custom handling of multiple values because we need
- * to combine them into a options list rather than display
- * cardinality elements. We will use the field module's default
- * handling for default values.
- *
- * Callbacks can be omitted if default handing is used.
- * They're included here just so this module can be used
- * as an example for custom modules that might do things
- * differently.
- */
-function options_field_widget_info() {
-
- return array(
- 'options_select' => array(
- 'label' => t('Select list'),
- 'field types' => array('list', 'list_boolean', 'list_text', 'list_number'),
- 'behaviors' => array(
- 'multiple values' => FIELD_BEHAVIOR_CUSTOM,
- 'default value' => FIELD_BEHAVIOR_DEFAULT,
- ),
- ),
- 'options_buttons' => array(
- 'label' => t('Check boxes/radio buttons'),
- 'field types' => array('list', 'list_boolean', 'list_text', 'list_number'),
- 'behaviors' => array(
- 'multiple values' => FIELD_BEHAVIOR_CUSTOM,
- 'default value' => FIELD_BEHAVIOR_DEFAULT,
- ),
- ),
- 'options_onoff' => array(
- 'label' => t('Single on/off checkbox'),
- 'field types' => array('list_boolean'),
- 'behaviors' => array(
- 'multiple values' => FIELD_BEHAVIOR_CUSTOM,
- 'default value' => FIELD_BEHAVIOR_DEFAULT,
- ),
- ),
- );
-}
-
-/**
- * Implementation of FAPI hook_elements().
- *
- * Any FAPI callbacks needed for individual widgets can be declared here,
- * and the element will be passed to those callbacks for processing.
- *
- * Drupal will automatically theme the element using a theme with
- * the same name as the hook_elements key.
- */
-function options_elements() {
- return array(
- 'options_select' => array(
- '#input' => TRUE,
- '#columns' => array('value'), '#delta' => 0,
- '#process' => array('options_select_process'),
- ),
- 'options_buttons' => array(
- '#input' => TRUE,
- '#columns' => array('value'), '#delta' => 0,
- '#process' => array('options_buttons_process'),
- ),
- 'options_onoff' => array(
- '#input' => TRUE,
- '#columns' => array('value'), '#delta' => 0,
- '#process' => array('options_onoff_process'),
- ),
- );
-}
-
-/**
- * Implementation of hook_field_widget().
- */
-function options_field_widget(&$form, &$form_state, $field, $instance, $items, $delta = NULL) {
- $element = array(
- '#type' => $instance['widget']['type'],
- '#default_value' => !empty($items) ? $items : array(),
- );
- return $element;
-}
-
-/**
- * Process an individual element.
- *
- * Build the form element. When creating a form using FAPI #process,
- * note that $element['#value'] is already set.
- *
- * The $field and $instance arrays are in $form['#fields'][$element['#field_name']].
- */
-function options_buttons_process($element, $edit, &$form_state, $form) {
- $field = $form['#fields'][$element['#field_name']]['field'];
- $instance = $form['#fields'][$element['#field_name']]['instance'];
- $field_key = $element['#columns'][0];
-
- // See if this element is in the database format or the transformed format,
- // and transform it if necessary.
- if (is_array($element['#value']) && !array_key_exists($field_key, $element['#value'])) {
- $element['#value'] = options_data2form($element, $element['#default_value'], $field);
- }
- $options = options_options($field, $instance);
- $multiple = isset($element['#multiple']) ? $element['#multiple'] : $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED;
-
- $value = array();
- foreach ($element['#value'][$field_key] as $key) {
- // Multiple (checkboxes) need the default value in the form of an array.
- if ($multiple) {
- $value[$key] = 1;
- }
- // Non-multiple (radios) need single default value.
- else {
- $value = $key;
- break;
- }
- }
-
- $element[$field_key] = array(
- '#type' => $multiple ? 'checkboxes' : 'radios',
- '#title' => $element['#title'],
- '#description' => $element['#description'],
- '#required' => isset($element['#required']) ? $element['#required'] : $instance['required'],
- '#multiple' => $multiple,
- '#options' => $options,
- '#default_value' => $value,
- );
-
- // Set #element_validate in a way that it will not wipe out other
- // validation functions already set by other modules.
- if (empty($element['#element_validate'])) {
- $element['#element_validate'] = array();
- }
- array_unshift($element['#element_validate'], 'options_validate');
-
- // Make sure field info will be available to the validator which
- // does not get the values in $form.
- $form_state['#fields'][$element['#field_name']] = $form['#fields'][$element['#field_name']];
- return $element;
-}
-
-/**
- * Process an individual element.
- *
- * Build the form element. When creating a form using FAPI #process,
- * note that $element['#value'] is already set.
- *
- * The $field and $instance arrays are in $form['#fields'][$element['#field_name']].
- */
-function options_select_process($element, $edit, &$form_state, $form) {
- $field = $form['#fields'][$element['#field_name']]['field'];
- $instance = $form['#fields'][$element['#field_name']]['instance'];
- $field_key = $element['#columns'][0];
-
- // See if this element is in the database format or the transformed format,
- // and transform it if necessary.
- if (is_array($element['#value']) && !array_key_exists($field_key, $element['#value'])) {
- $element['#value'] = options_data2form($element, $element['#default_value'], $field);
- }
-
- $options = options_options($field, $instance);
- $element[$field_key] = array(
- '#type' => 'select',
- '#title' => $element['#title'],
- '#description' => $element['#description'],
- '#required' => isset($element['#required']) ? $element['#required'] : $instance['required'],
- '#multiple' => isset($element['#multiple']) ? $element['#multiple'] : $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED,
- '#options' => $options,
- '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL,
- );
-
- // Set #element_validate in a way that it will not wipe out other
- // validation functions already set by other modules.
- if (empty($element['#element_validate'])) {
- $element['#element_validate'] = array();
- }
- array_unshift($element['#element_validate'], 'options_validate');
-
- // Make sure field info will be available to the validator which
- // does not get the values in $form.
- $form_state['#fields'][$element['#field_name']] = $form['#fields'][$element['#field_name']];
- return $element;
-}
-
-/**
- * Process an individual element.
- *
- * Build the form element. When creating a form using FAPI #process,
- * note that $element['#value'] is already set.
- */
-function options_onoff_process($element, $edit, &$form_state, $form) {
- $field = $form['#fields'][$element['#field_name']]['field'];
- $instance = $form['#fields'][$element['#field_name']]['instance'];
- $field_key = $element['#columns'][0];
-
- // See if this element is in the database format or the transformed format,
- // and transform it if necessary.
- if (is_array($element['#value']) && !array_key_exists($field_key, $element['#value'])) {
- $element['#value'] = options_data2form($element, $element['#default_value'], $field);
- }
- $options = options_options($field, $instance);
- $keys = array_keys($options);
- $on_value = (!empty($keys) && isset($keys[1])) ? $keys[1] : NULL;
- $element[$field_key] = array(
- '#type' => 'checkbox',
- '#title' => isset($options[$on_value]) ? $options[$on_value] : '',
- '#description' => $element['#description'],
- '#default_value' => isset($element['#value'][$field_key][0]) ? $element['#value'][$field_key][0] == $on_value : FALSE,
- '#return_value' => $on_value,
- );
-
- // Set #element_validate in a way that it will not wipe out other
- // validation functions already set by other modules.
- if (empty($element['#element_validate'])) {
- $element['#element_validate'] = array();
- }
- array_unshift($element['#element_validate'], 'options_validate');
-
- // Make sure field info will be available to the validator which
- // does not get the values in $form.
- $form_state['#fields'][$element['#field_name']] = $form['#fields'][$element['#field_name']];
- return $element;
-}
-
-/**
- * FAPI function to validate options element.
- */
-function options_validate($element, &$form_state) {
- // Transpose selections from field => delta to delta => field,
- // turning cardinality selected options into cardinality parent elements.
- // Immediate parent is the delta, need to get back to parent's parent
- // to create cardinality elements.
- $field = $form_state['#fields'][$element['#field_name']]['field'];
- $items = options_form2data($element, $field);
- form_set_value($element, $items, $form_state);
-
- // Check we don't exceed the allowed number of values.
- if ($field['cardinality'] >= 2) {
- // Filter out 'none' value (if present, will always be in key 0)
- $field_key = $element['#columns'][0];
- if (isset($items[0][$field_key]) && $items[0][$field_key] === '') {
- unset($items[0]);
- }
- if (count($items) > $field['cardinality']) {
- $field_key = $element['#columns'][0];
- form_error($element[$field_key], t('%name: this field cannot hold more that @count values.', array('%name' => t($field['widget']['label']), '@count' => $field['cardinality'])));
- }
- }
-}
-
-/**
- * Helper function to transpose the values as stored in the database
- * to the format the widget needs. Can be called anywhere this
- * transformation is needed.
- */
-function options_data2form($element, $items, $field) {
- $field_key = $element['#columns'][0];
- $field = field_info_field($element['#field_name']);
- $instance = field_info_instance($element['#field_name'], $element['#bundle']);
- $options = options_options($field, $instance);
-
- $items_transposed = options_transpose_array_rows_cols($items);
- $values = (isset($items_transposed[$field_key]) && is_array($items_transposed[$field_key])) ? $items_transposed[$field_key] : array();
- $keys = array();
- foreach ($values as $value) {
- $key = array_search($value, array_keys($options));
- if (isset($key)) {
- $keys[] = $value;
- }
- }
- if ($field['cardinality'] || $element['#type'] == 'options_onoff') {
- return array($field_key => $keys);
- }
- else {
- return !empty($keys) ? array($field_key => $value) : array();
- }
-}
-
-/**
- * Helper function to transpose the values returned by submitting the widget
- * to the format to be stored in the field. Can be called anywhere this
- * transformation is needed.
- */
-function options_form2data($element, $field) {
- $field_key = $element['#columns'][0];
- $field = field_info_field($element['#field_name']);
- $instance = field_info_instance($element['#field_name'], $element['#bundle']);
- $items = (array) $element[$field_key]['#value'];
- $options = options_options($field, $instance);
-
- $values = array_values($items);
-
- if ($element['#type'] == 'options_onoff' && ($values[0] === 0)) {
- $keys = array_keys($options);
- $values = array(array_key_exists(0, $keys) ? $keys[0] : NULL);
- }
-
- if (empty($values)) {
- $values[] = NULL;
- }
- $result = options_transpose_array_rows_cols(array($field_key => $values));
- return $result;
-}
-
-/**
- * Manipulate a 2D array to reverse rows and columns.
- *
- * The default data storage for fields is delta first, column names second.
- * This is sometimes inconvenient for field modules, so this function can be
- * used to present the data in an alternate format.
- *
- * @param $array
- * The array to be transposed. It must be at least two-dimensional, and
- * the subarrays must all have the same keys or behavior is undefined.
- * @return
- * The transposed array.
- */
-function options_transpose_array_rows_cols($array) {
- $result = array();
- if (is_array($array)) {
- foreach ($array as $key1 => $value1) {
- if (is_array($value1)) {
- foreach ($value1 as $key2 => $value2) {
- if (!isset($result[$key2])) {
- $result[$key2] = array();
- }
- $result[$key2][$key1] = $value2;
- }
- }
- }
- }
- return $result;
-}
-
-/**
- * Helper function for finding the allowed values list for a field.
- *
- * See if there is a module hook for the option values.
- * Otherwise, try list_allowed_values() for an options list.
- */
-function options_options($field, $instance) {
- $function = $field['module'] . '_allowed_values';
- $options = function_exists($function) ? $function($field) : (array) list_allowed_values($field);
- // Add an empty choice for :
- // - non required radios
- // - non required selects
- if (!$instance['required']) {
- if ((in_array($instance['widget']['type'], array('options_buttons', 'node_reference_buttons', 'user_reference_buttons')) && !$field['cardinality'])
- || (in_array($instance['widget']['type'], array('options_select', 'node_reference_select', 'user_reference_select')))) {
- $options = array('' => theme('options_none', $instance)) + $options;
- }
- }
- return $options;
-}
-
-/**
- * Theme the label for the empty value for options that are not required.
- * The default theme will display N/A for a radio list and blank for a select.
- */
-function theme_options_none($instance) {
- switch ($instance['widget']['type']) {
- case 'options_buttons':
- case 'node_reference_buttons':
- case 'user_reference_buttons':
- return t('N/A');
- case 'options_select':
- case 'node_reference_select':
- case 'user_reference_select':
- return t('- None -');
- default :
- return '';
- }
-}
-
-/**
- * FAPI themes for options.
- *
- * The select, checkboxes or radios are already rendered by the
- * select, checkboxes, or radios themes and the HTML output
- * lives in $element['#children']. Override this theme to
- * make custom changes to the output.
- *
- * $element['#field_name'] contains the field name
- * $element['#delta] is the position of this element in the group
- */
-function theme_options_select($element) {
- return $element['#children'];
-}
-
-function theme_options_onoff($element) {
- return $element['#children'];
-}
-
-function theme_options_buttons($element) {
- return $element['#children'];
+<?php
+// $Id$
+
+/**
+ * @file
+ * Defines selection, check box and radio button widgets for text and numeric fields.
+ */
+/**
+ * Implementation of hook_theme().
+ */
+function options_theme() {
+ return array(
+ 'options_select' => array(
+ 'arguments' => array('element' => NULL),
+ ),
+ 'options_buttons' => array(
+ 'arguments' => array('element' => NULL),
+ ),
+ 'options_onoff' => array(
+ 'arguments' => array('element' => NULL),
+ ),
+ 'options_none' => array(
+ 'arguments' => array('widget_type' => NULL, 'field_name' => NULL, 'node_type' => NULL),
+ ),
+ );
+}
+
+/**
+ * Implementation of hook_field_widget_info().
+ *
+ * We need custom handling of multiple values because we need
+ * to combine them into a options list rather than display
+ * cardinality elements. We will use the field module's default
+ * handling for default values.
+ *
+ * Callbacks can be omitted if default handing is used.
+ * They're included here just so this module can be used
+ * as an example for custom modules that might do things
+ * differently.
+ */
+function options_field_widget_info() {
+
+ return array(
+ 'options_select' => array(
+ 'label' => t('Select list'),
+ 'field types' => array('list', 'list_boolean', 'list_text', 'list_number'),
+ 'behaviors' => array(
+ 'multiple values' => FIELD_BEHAVIOR_CUSTOM,
+ 'default value' => FIELD_BEHAVIOR_DEFAULT,
+ ),
+ ),
+ 'options_buttons' => array(
+ 'label' => t('Check boxes/radio buttons'),
+ 'field types' => array('list', 'list_boolean', 'list_text', 'list_number'),
+ 'behaviors' => array(
+ 'multiple values' => FIELD_BEHAVIOR_CUSTOM,
+ 'default value' => FIELD_BEHAVIOR_DEFAULT,
+ ),
+ ),
+ 'options_onoff' => array(
+ 'label' => t('Single on/off checkbox'),
+ 'field types' => array('list_boolean'),
+ 'behaviors' => array(
+ 'multiple values' => FIELD_BEHAVIOR_CUSTOM,
+ 'default value' => FIELD_BEHAVIOR_DEFAULT,
+ ),
+ ),
+ );
+}
+
+/**
+ * Implementation of FAPI hook_elements().
+ *
+ * Any FAPI callbacks needed for individual widgets can be declared here,
+ * and the element will be passed to those callbacks for processing.
+ *
+ * Drupal will automatically theme the element using a theme with
+ * the same name as the hook_elements key.
+ */
+function options_elements() {
+ return array(
+ 'options_select' => array(
+ '#input' => TRUE,
+ '#columns' => array('value'), '#delta' => 0,
+ '#process' => array('options_select_process'),
+ ),
+ 'options_buttons' => array(
+ '#input' => TRUE,
+ '#columns' => array('value'), '#delta' => 0,
+ '#process' => array('options_buttons_process'),
+ ),
+ 'options_onoff' => array(
+ '#input' => TRUE,
+ '#columns' => array('value'), '#delta' => 0,
+ '#process' => array('options_onoff_process'),
+ ),
+ );
+}
+
+/**
+ * Implementation of hook_field_widget().
+ */
+function options_field_widget(&$form, &$form_state, $field, $instance, $items, $delta = NULL) {
+ $element = array(
+ '#type' => $instance['widget']['type'],
+ '#default_value' => !empty($items) ? $items : array(),
+ );
+ return $element;
+}
+
+/**
+ * Process an individual element.
+ *
+ * Build the form element. When creating a form using FAPI #process,
+ * note that $element['#value'] is already set.
+ *
+ * The $field and $instance arrays are in $form['#fields'][$element['#field_name']].
+ */
+function options_buttons_process($element, $edit, &$form_state, $form) {
+ $field = $form['#fields'][$element['#field_name']]['field'];
+ $instance = $form['#fields'][$element['#field_name']]['instance'];
+ $field_key = $element['#columns'][0];
+
+ // See if this element is in the database format or the transformed format,
+ // and transform it if necessary.
+ if (is_array($element['#value']) && !array_key_exists($field_key, $element['#value'])) {
+ $element['#value'] = options_data2form($element, $element['#default_value'], $field);
+ }
+ $options = options_options($field, $instance);
+ $multiple = isset($element['#multiple']) ? $element['#multiple'] : $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED;
+
+ $value = array();
+ foreach ($element['#value'][$field_key] as $key) {
+ // Multiple (checkboxes) need the default value in the form of an array.
+ if ($multiple) {
+ $value[$key] = 1;
+ }
+ // Non-multiple (radios) need single default value.
+ else {
+ $value = $key;
+ break;
+ }
+ }
+
+ $element[$field_key] = array(
+ '#type' => $multiple ? 'checkboxes' : 'radios',
+ '#title' => $element['#title'],
+ '#description' => $element['#description'],
+ '#required' => isset($element['#required']) ? $element['#required'] : $instance['required'],
+ '#multiple' => $multiple,
+ '#options' => $options,
+ '#default_value' => $value,
+ );
+
+ // Set #element_validate in a way that it will not wipe out other
+ // validation functions already set by other modules.
+ if (empty($element['#element_validate'])) {
+ $element['#element_validate'] = array();
+ }
+ array_unshift($element['#element_validate'], 'options_validate');
+
+ // Make sure field info will be available to the validator which
+ // does not get the values in $form.
+ $form_state['#fields'][$element['#field_name']] = $form['#fields'][$element['#field_name']];
+ return $element;
+}
+
+/**
+ * Process an individual element.
+ *
+ * Build the form element. When creating a form using FAPI #process,
+ * note that $element['#value'] is already set.
+ *
+ * The $field and $instance arrays are in $form['#fields'][$element['#field_name']].
+ */
+function options_select_process($element, $edit, &$form_state, $form) {
+ $field = $form['#fields'][$element['#field_name']]['field'];
+ $instance = $form['#fields'][$element['#field_name']]['instance'];
+ $field_key = $element['#columns'][0];
+
+ // See if this element is in the database format or the transformed format,
+ // and transform it if necessary.
+ if (is_array($element['#value']) && !array_key_exists($field_key, $element['#value'])) {
+ $element['#value'] = options_data2form($element, $element['#default_value'], $field);
+ }
+
+ $options = options_options($field, $instance);
+ $element[$field_key] = array(
+ '#type' => 'select',
+ '#title' => $element['#title'],
+ '#description' => $element['#description'],
+ '#required' => isset($element['#required']) ? $element['#required'] : $instance['required'],
+ '#multiple' => isset($element['#multiple']) ? $element['#multiple'] : $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED,
+ '#options' => $options,
+ '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL,
+ );
+
+ // Set #element_validate in a way that it will not wipe out other
+ // validation functions already set by other modules.
+ if (empty($element['#element_validate'])) {
+ $element['#element_validate'] = array();
+ }
+ array_unshift($element['#element_validate'], 'options_validate');
+
+ // Make sure field info will be available to the validator which
+ // does not get the values in $form.
+ $form_state['#fields'][$element['#field_name']] = $form['#fields'][$element['#field_name']];
+ return $element;
+}
+
+/**
+ * Process an individual element.
+ *
+ * Build the form element. When creating a form using FAPI #process,
+ * note that $element['#value'] is already set.
+ */
+function options_onoff_process($element, $edit, &$form_state, $form) {
+ $field = $form['#fields'][$element['#field_name']]['field'];
+ $instance = $form['#fields'][$element['#field_name']]['instance'];
+ $field_key = $element['#columns'][0];
+
+ // See if this element is in the database format or the transformed format,
+ // and transform it if necessary.
+ if (is_array($element['#value']) && !array_key_exists($field_key, $element['#value'])) {
+ $element['#value'] = options_data2form($element, $element['#default_value'], $field);
+ }
+ $options = options_options($field, $instance);
+ $keys = array_keys($options);
+ $on_value = (!empty($keys) && isset($keys[1])) ? $keys[1] : NULL;
+ $element[$field_key] = array(
+ '#type' => 'checkbox',
+ '#title' => isset($options[$on_value]) ? $options[$on_value] : '',
+ '#description' => $element['#description'],
+ '#default_value' => isset($element['#value'][$field_key][0]) ? $element['#value'][$field_key][0] == $on_value : FALSE,
+ '#return_value' => $on_value,
+ );
+
+ // Set #element_validate in a way that it will not wipe out other
+ // validation functions already set by other modules.
+ if (empty($element['#element_validate'])) {
+ $element['#element_validate'] = array();
+ }
+ array_unshift($element['#element_validate'], 'options_validate');
+
+ // Make sure field info will be available to the validator which
+ // does not get the values in $form.
+ $form_state['#fields'][$element['#field_name']] = $form['#fields'][$element['#field_name']];
+ return $element;
+}
+
+/**
+ * FAPI function to validate options element.
+ */
+function options_validate($element, &$form_state) {
+ // Transpose selections from field => delta to delta => field,
+ // turning cardinality selected options into cardinality parent elements.
+ // Immediate parent is the delta, need to get back to parent's parent
+ // to create cardinality elements.
+ $field = $form_state['#fields'][$element['#field_name']]['field'];
+ $items = options_form2data($element, $field);
+ form_set_value($element, $items, $form_state);
+
+ // Check we don't exceed the allowed number of values.
+ if ($field['cardinality'] >= 2) {
+ // Filter out 'none' value (if present, will always be in key 0)
+ $field_key = $element['#columns'][0];
+ if (isset($items[0][$field_key]) && $items[0][$field_key] === '') {
+ unset($items[0]);
+ }
+ if (count($items) > $field['cardinality']) {
+ $field_key = $element['#columns'][0];
+ form_error($element[$field_key], t('%name: this field cannot hold more that @count values.', array('%name' => t($field['widget']['label']), '@count' => $field['cardinality'])));
+ }
+ }
+}
+
+/**
+ * Helper function to transpose the values as stored in the database
+ * to the format the widget needs. Can be called anywhere this
+ * transformation is needed.
+ */
+function options_data2form($element, $items, $field) {
+ $field_key = $element['#columns'][0];
+ $field = field_info_field($element['#field_name']);
+ $instance = field_info_instance($element['#field_name'], $element['#bundle']);
+ $options = options_options($field, $instance);
+
+ $items_transposed = options_transpose_array_rows_cols($items);
+ $values = (isset($items_transposed[$field_key]) && is_array($items_transposed[$field_key])) ? $items_transposed[$field_key] : array();
+ $keys = array();
+ foreach ($values as $value) {
+ $key = array_search($value, array_keys($options));
+ if (isset($key)) {
+ $keys[] = $value;
+ }
+ }
+ if ($field['cardinality'] || $element['#type'] == 'options_onoff') {
+ return array($field_key => $keys);
+ }
+ else {
+ return !empty($keys) ? array($field_key => $value) : array();
+ }
+}
+
+/**
+ * Helper function to transpose the values returned by submitting the widget
+ * to the format to be stored in the field. Can be called anywhere this
+ * transformation is needed.
+ */
+function options_form2data($element, $field) {
+ $field_key = $element['#columns'][0];
+ $field = field_info_field($element['#field_name']);
+ $instance = field_info_instance($element['#field_name'], $element['#bundle']);
+ $items = (array) $element[$field_key]['#value'];
+ $options = options_options($field, $instance);
+
+ $values = array_values($items);
+
+ if ($element['#type'] == 'options_onoff' && ($values[0] === 0)) {
+ $keys = array_keys($options);
+ $values = array(array_key_exists(0, $keys) ? $keys[0] : NULL);
+ }
+
+ if (empty($values)) {
+ $values[] = NULL;
+ }
+ $result = options_transpose_array_rows_cols(array($field_key => $values));
+ return $result;
+}
+
+/**
+ * Manipulate a 2D array to reverse rows and columns.
+ *
+ * The default data storage for fields is delta first, column names second.
+ * This is sometimes inconvenient for field modules, so this function can be
+ * used to present the data in an alternate format.
+ *
+ * @param $array
+ * The array to be transposed. It must be at least two-dimensional, and
+ * the subarrays must all have the same keys or behavior is undefined.
+ * @return
+ * The transposed array.
+ */
+function options_transpose_array_rows_cols($array) {
+ $result = array();
+ if (is_array($array)) {
+ foreach ($array as $key1 => $value1) {
+ if (is_array($value1)) {
+ foreach ($value1 as $key2 => $value2) {
+ if (!isset($result[$key2])) {
+ $result[$key2] = array();
+ }
+ $result[$key2][$key1] = $value2;
+ }
+ }
+ }
+ }
+ return $result;
+}
+
+/**
+ * Helper function for finding the allowed values list for a field.
+ *
+ * See if there is a module hook for the option values.
+ * Otherwise, try list_allowed_values() for an options list.
+ */
+function options_options($field, $instance) {
+ $function = $field['module'] . '_allowed_values';
+ $options = function_exists($function) ? $function($field) : (array) list_allowed_values($field);
+ // Add an empty choice for :
+ // - non required radios
+ // - non required selects
+ if (!$instance['required']) {
+ if ((in_array($instance['widget']['type'], array('options_buttons', 'node_reference_buttons', 'user_reference_buttons')) && !$field['cardinality'])
+ || (in_array($instance['widget']['type'], array('options_select', 'node_reference_select', 'user_reference_select')))) {
+ $options = array('' => theme('options_none', $instance)) + $options;
+ }
+ }
+ return $options;
+}
+
+/**
+ * Theme the label for the empty value for options that are not required.
+ * The default theme will display N/A for a radio list and blank for a select.
+ */
+function theme_options_none($instance) {
+ switch ($instance['widget']['type']) {
+ case 'options_buttons':
+ case 'node_reference_buttons':
+ case 'user_reference_buttons':
+ return t('N/A');
+ case 'options_select':
+ case 'node_reference_select':
+ case 'user_reference_select':
+ return t('- None -');
+ default :
+ return '';
+ }
+}
+
+/**
+ * FAPI themes for options.
+ *
+ * The select, checkboxes or radios are already rendered by the
+ * select, checkboxes, or radios themes and the HTML output
+ * lives in $element['#children']. Override this theme to
+ * make custom changes to the output.
+ *
+ * $element['#field_name'] contains the field name
+ * $element['#delta] is the position of this element in the group
+ */
+function theme_options_select($element) {
+ return $element['#children'];
+}
+
+function theme_options_onoff($element) {
+ return $element['#children'];
+}
+
+function theme_options_buttons($element) {
+ return $element['#children'];
} \ No newline at end of file
diff --git a/modules/field/modules/text/text.info b/modules/field/modules/text/text.info
index c13bed962..e478e2813 100644
--- a/modules/field/modules/text/text.info
+++ b/modules/field/modules/text/text.info
@@ -3,5 +3,4 @@ name = Text
description = Defines simple text field types.
package = Core - fields
core = 7.x
-
files[]=text.module
diff --git a/modules/field/modules/text/text.module b/modules/field/modules/text/text.module
index dda162a22..7cd127bbd 100644
--- a/modules/field/modules/text/text.module
+++ b/modules/field/modules/text/text.module
@@ -1,400 +1,400 @@
-<?php
-// $Id$
-
-/**
- * @file
- * Defines simple text field types.
- */
-
-/**
- * Implementation of hook_theme().
- */
-function text_theme() {
- return array(
- 'text_textarea' => array(
- 'arguments' => array('element' => NULL),
- ),
- 'text_textfield' => array(
- 'arguments' => array('element' => NULL),
- ),
- 'field_formatter_text_default' => array(
- 'arguments' => array('element' => NULL),
- ),
- 'field_formatter_text_plain' => array(
- 'arguments' => array('element' => NULL),
- ),
- 'field_formatter_text_trimmed' => array(
- 'arguments' => array('element' => NULL),
- ),
- );
-}
-
-/**
- * Implementation of hook_field_info().
- */
-function text_field_info() {
- return array(
- 'text' => array(
- 'label' => t('Text'),
- 'description' => t('This field stores varchar text in the database.'),
- 'settings' => array('max_length' => 255),
- 'instance_settings' => array('text_processing' => 0),
- 'default_widget' => 'text_textfield',
- 'default_formatter' => 'text_default',
- ),
- 'text_long' => array(
- 'label' => t('Long text'),
- 'description' => t('This field stores long text in the database.'),
- 'instance_settings' => array('text_processing' => 0),
- 'default_widget' => 'text_textarea',
- 'default_formatter' => 'text_default',
- ),
- );
-}
-
-/**
- * Implementation of hook_field_schema().
- */
-function text_field_columns($field) {
- if ($field['type'] == 'text_long') {
- $columns = array(
- 'value' => array(
- 'type' => 'text',
- 'size' => 'big',
- 'not null' => FALSE,
- ),
- );
- }
- else {
- $columns = array(
- 'value' => array(
- 'type' => 'varchar',
- 'length' => $field['settings']['max_length'],
- 'not null' => FALSE,
- ),
- );
- }
- $columns += array(
- 'format' => array(
- 'type' => 'int',
- 'unsigned' => TRUE,
- 'not null' => FALSE,
- ),
- );
- return $columns;
-}
-
-/**
- * Implementation of hook_field_validate().
- */
-function text_field_validate($obj_type, $object, $field, $instance, $items, $form) {
- if (is_array($items)) {
- foreach ($items as $delta => $item) {
- $error_element = isset($item['_error_element']) ? $item['_error_element'] : '';
- if (is_array($item) && isset($item['_error_element'])) unset($item['_error_element']);
- if (!empty($item['value'])) {
- if (!empty($field['settings']['max_length']) && drupal_strlen($item['value']) > $field['settings']['max_length']) {
- form_set_error($error_element, t('%name: the value may not be longer than %max characters.', array('%name' => $instance['label'], '%max' => $field['settings']['max_length'])));
- }
- }
- }
- }
-}
-
-function text_field_sanitize($obj_type, $object, $field, $instance, &$items) {
- global $language;
- foreach ($items as $delta => $item) {
- // TODO D7 : this code is really node-related.
- if (!empty($instance['settings']['text_processing'])) {
- $check = is_null($object) || (isset($object->build_mode) && $object->build_mode == NODE_BUILD_PREVIEW);
- $text = isset($item['value']) ? check_markup($item['value'], $item['format'], isset($object->language) ? $object->language : $language, $check) : '';
- }
- else {
- $text = check_plain($item['value']);
- }
- $items[$delta]['safe'] = $text;
- }
-}
-
-/**
- * Implementation of hook_field_is_empty().
- */
-function text_field_is_empty($item, $field) {
- if (empty($item['value']) && (string)$item['value'] !== '0') {
- return TRUE;
- }
- return FALSE;
-}
-
-/**
- * Implementation of hook_field_formatter_info().
- */
-function text_field_formatter_info() {
- return array(
- 'text_default' => array(
- 'label' => t('Default'),
- 'field types' => array('text', 'text_long'),
- 'behaviors' => array(
- 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
- ),
- ),
- 'text_plain' => array(
- 'label' => t('Plain text'),
- 'field types' => array('text', 'text_long'),
- 'behaviors' => array(
- 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
- ),
- ),
- 'text_trimmed' => array(
- 'label' => t('Trimmed'),
- 'field types' => array('text', 'text_long'),
- 'behaviors' => array(
- 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
- ),
- ),
- );
-}
-
-/**
- * Theme function for 'default' text field formatter.
- */
-function theme_field_formatter_text_default($element) {
- return $element['#item']['safe'];
-}
-
-/**
- * Theme function for 'plain' text field formatter.
- */
-function theme_field_formatter_text_plain($element) {
- return strip_tags($element['#item']['safe']);
-}
-
-/**
- * Theme function for 'trimmed' text field formatter.
- */
-function theme_field_formatter_text_trimmed($element) {
- $field = field_info_field($element['#field_name']);
- $instance = field_info_instance($element['#field_name'], $element['#bundle']);
- return $instance['settings']['text_processing'] ? $element['#item']['format'] : NULL;
-}
-
-/**
- * Implementation of hook_field_widget_info().
- *
- * Here we indicate that the field module will handle
- * the default value and multiple values for these widgets.
- *
- * Callbacks can be omitted if default handing is used.
- * They're included here just so this module can be used
- * as an example for custom modules that might do things
- * differently.
- */
-function text_field_widget_info() {
- return array(
- 'text_textfield' => array(
- 'label' => t('Text field'),
- 'field types' => array('text'),
- 'settings' => array('size' => 60),
- 'behaviors' => array(
- 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
- 'default value' => FIELD_BEHAVIOR_DEFAULT,
- ),
- ),
- 'text_textarea' => array(
- 'label' => t('Text area (multiple rows)'),
- 'field types' => array('text_long'),
- 'settings' => array('rows' => 5),
- 'behaviors' => array(
- 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
- 'default value' => FIELD_BEHAVIOR_DEFAULT,
- ),
- ),
- );
-}
-
-/**
- * Implementation of FAPI hook_elements().
- *
- * Any FAPI callbacks needed for individual widgets can be declared here,
- * and the element will be passed to those callbacks for processing.
- *
- * Drupal will automatically theme the element using a theme with
- * the same name as the hook_elements key.
- *
- * Autocomplete_path is not used by text_field_widget but other
- * widgets can use it (see nodereference and userreference).
- */
-function text_elements() {
- return array(
- 'text_textfield' => array(
- '#input' => TRUE,
- '#columns' => array('value'), '#delta' => 0,
- '#process' => array('text_textfield_process'),
- '#autocomplete_path' => FALSE,
- ),
- 'text_textarea' => array(
- '#input' => TRUE,
- '#columns' => array('value', 'format'), '#delta' => 0,
- '#process' => array('text_textarea_process'),
- '#filter_value' => FILTER_FORMAT_DEFAULT,
- ),
- );
-}
-
-/**
- * Implementation of hook_field_widget().
- *
- * Attach a single form element to the form. It will be built out and
- * validated in the callback(s) listed in hook_elements. We build it
- * out in the callbacks rather than here in hook_field_widget so it can be
- * plugged into any module that can provide it with valid
- * $field information.
- *
- * Field module will set the weight, field name and delta values
- * for each form element.
- *
- * If there are multiple values for this field, the field module will
- * call this function as many times as needed.
- *
- * @param $form
- * the entire form array, $form['#node'] holds node information
- * @param $form_state
- * the form_state, $form_state['values'][$field['field_name']]
- * holds the field's form values.
- * @param $field
- * The field structure.
- * @param $instance
- * the field instance array
- * @param $items
- * array of default values for this field
- * @param $delta
- * the order of this item in the array of subelements (0, 1, 2, etc)
- *
- * @return
- * the form item for a single element for this field
- */
-function text_field_widget(&$form, &$form_state, $field, $instance, $items, $delta = 0) {
- $element = array(
- '#type' => $instance['widget']['type'],
- '#default_value' => isset($items[$delta]) ? $items[$delta] : '',
- );
- return $element;
-}
-
-/**
- * Process an individual element.
- *
- * Build the form element. When creating a form using FAPI #process,
- * note that $element['#value'] is already set.
- *
- * The $field and $instance arrays are in $form['#fields'][$element['#field_name']].
- *
- * TODO: For widgets to be actual FAPI 'elements', reusable outside of a
- * 'field' context, they shoudn't rely on $field and $instance. The bits of
- * information needed to adjust the behavior of the 'element' should be
- * extracted in hook_field_widget() above.
- */
-function text_textfield_process($element, $edit, $form_state, $form) {
- $field = $form['#fields'][$element['#field_name']]['field'];
- $instance = $form['#fields'][$element['#field_name']]['instance'];
- $field_key = $element['#columns'][0];
- $delta = $element['#delta'];
-
- $element[$field_key] = array(
- '#type' => 'textfield',
- '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL,
- '#autocomplete_path' => $element['#autocomplete_path'],
- '#size' => $instance['widget']['settings']['size'],
- '#attributes' => array('class' => 'text'),
- // The following values were set by the field module and need
- // to be passed down to the nested element.
- '#title' => $element['#title'],
- '#description' => $element['#description'],
- '#required' => $element['#required'],
- '#field_name' => $element['#field_name'],
- '#bundle' => $element['#bundle'],
- '#delta' => $element['#delta'],
- '#columns' => $element['#columns'],
- );
-
- $element[$field_key]['#maxlength'] = !empty($field['settings']['max_length']) ? $field['settings']['max_length'] : NULL;
-
- if (!empty($instance['settings']['text_processing'])) {
- $filter_key = $element['#columns'][1];
- $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : FILTER_FORMAT_DEFAULT;
- $parents = array_merge($element['#parents'] , array($filter_key));
- $element[$filter_key] = filter_form($format, 1, $parents);
- }
-
- // Used so that hook_field('validate') knows where to flag an error.
- // TODO: rework that. See http://groups.drupal.org/node/18019.
- $element['_error_element'] = array(
- '#type' => 'value',
- '#value' => implode('][', array_merge($element['#parents'], array($field_key))),
- );
-
- return $element;
-}
-
-/**
- * Process an individual element.
- *
- * Build the form element. When creating a form using FAPI #process,
- * note that $element['#value'] is already set.
- *
- * The $field and $instance arrays are in $form['#fields'][$element['#field_name']].
- */
-function text_textarea_process($element, $edit, $form_state, $form) {
- $field = $form['#fields'][$element['#field_name']]['field'];
- $instance = $form['#fields'][$element['#field_name']]['instance'];
- $field_key = $element['#columns'][0];
- $delta = $element['#delta'];
- $element[$field_key] = array(
- '#type' => 'textarea',
- '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL,
- '#rows' => $instance['widget']['settings']['rows'],
- '#weight' => 0,
- // The following values were set by the field module and need
- // to be passed down to the nested element.
- '#title' => $element['#title'],
- '#description' => $element['#description'],
- '#required' => $element['#required'],
- '#field_name' => $element['#field_name'],
- '#bundle' => $element['#bundle'],
- '#delta' => $element['#delta'],
- '#columns' => $element['#columns'],
- );
-
- if (!empty($instance['settings']['text_processing'])) {
- $filter_key = (count($element['#columns']) == 2) ? $element['#columns'][1] : 'format';
- $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : FILTER_FORMAT_DEFAULT;
- $parents = array_merge($element['#parents'] , array($filter_key));
- $element[$filter_key] = filter_form($format, 1, $parents);
- }
-
- // Used so that hook_field('validate') knows where to flag an error.
- $element['_error_element'] = array(
- '#type' => 'value',
- '#value' => implode('][', array_merge($element['#parents'], array($field_key))),
- );
- return $element;
-}
-
-/**
- * FAPI theme for an individual text elements.
- *
- * The textfield or textarea is already rendered by the
- * textfield or textarea themes and the html output
- * lives in $element['#children']. Override this theme to
- * make custom changes to the output.
- *
- * $element['#field_name'] contains the field name
- * $element['#delta] is the position of this element in the group
- */
-function theme_text_textfield($element) {
- return $element['#children'];
-}
-
-function theme_text_textarea($element) {
- return $element['#children'];
+<?php
+// $Id$
+
+/**
+ * @file
+ * Defines simple text field types.
+ */
+
+/**
+ * Implementation of hook_theme().
+ */
+function text_theme() {
+ return array(
+ 'text_textarea' => array(
+ 'arguments' => array('element' => NULL),
+ ),
+ 'text_textfield' => array(
+ 'arguments' => array('element' => NULL),
+ ),
+ 'field_formatter_text_default' => array(
+ 'arguments' => array('element' => NULL),
+ ),
+ 'field_formatter_text_plain' => array(
+ 'arguments' => array('element' => NULL),
+ ),
+ 'field_formatter_text_trimmed' => array(
+ 'arguments' => array('element' => NULL),
+ ),
+ );
+}
+
+/**
+ * Implementation of hook_field_info().
+ */
+function text_field_info() {
+ return array(
+ 'text' => array(
+ 'label' => t('Text'),
+ 'description' => t('This field stores varchar text in the database.'),
+ 'settings' => array('max_length' => 255),
+ 'instance_settings' => array('text_processing' => 0),
+ 'default_widget' => 'text_textfield',
+ 'default_formatter' => 'text_default',
+ ),
+ 'text_long' => array(
+ 'label' => t('Long text'),
+ 'description' => t('This field stores long text in the database.'),
+ 'instance_settings' => array('text_processing' => 0),
+ 'default_widget' => 'text_textarea',
+ 'default_formatter' => 'text_default',
+ ),
+ );
+}
+
+/**
+ * Implementation of hook_field_schema().
+ */
+function text_field_columns($field) {
+ if ($field['type'] == 'text_long') {
+ $columns = array(
+ 'value' => array(
+ 'type' => 'text',
+ 'size' => 'big',
+ 'not null' => FALSE,
+ ),
+ );
+ }
+ else {
+ $columns = array(
+ 'value' => array(
+ 'type' => 'varchar',
+ 'length' => $field['settings']['max_length'],
+ 'not null' => FALSE,
+ ),
+ );
+ }
+ $columns += array(
+ 'format' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => FALSE,
+ ),
+ );
+ return $columns;
+}
+
+/**
+ * Implementation of hook_field_validate().
+ */
+function text_field_validate($obj_type, $object, $field, $instance, $items, $form) {
+ if (is_array($items)) {
+ foreach ($items as $delta => $item) {
+ $error_element = isset($item['_error_element']) ? $item['_error_element'] : '';
+ if (is_array($item) && isset($item['_error_element'])) unset($item['_error_element']);
+ if (!empty($item['value'])) {
+ if (!empty($field['settings']['max_length']) && drupal_strlen($item['value']) > $field['settings']['max_length']) {
+ form_set_error($error_element, t('%name: the value may not be longer than %max characters.', array('%name' => $instance['label'], '%max' => $field['settings']['max_length'])));
+ }
+ }
+ }
+ }
+}
+
+function text_field_sanitize($obj_type, $object, $field, $instance, &$items) {
+ global $language;
+ foreach ($items as $delta => $item) {
+ // TODO D7 : this code is really node-related.
+ if (!empty($instance['settings']['text_processing'])) {
+ $check = is_null($object) || (isset($object->build_mode) && $object->build_mode == NODE_BUILD_PREVIEW);
+ $text = isset($item['value']) ? check_markup($item['value'], $item['format'], isset($object->language) ? $object->language : $language, $check) : '';
+ }
+ else {
+ $text = check_plain($item['value']);
+ }
+ $items[$delta]['safe'] = $text;
+ }
+}
+
+/**
+ * Implementation of hook_field_is_empty().
+ */
+function text_field_is_empty($item, $field) {
+ if (empty($item['value']) && (string)$item['value'] !== '0') {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * Implementation of hook_field_formatter_info().
+ */
+function text_field_formatter_info() {
+ return array(
+ 'text_default' => array(
+ 'label' => t('Default'),
+ 'field types' => array('text', 'text_long'),
+ 'behaviors' => array(
+ 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+ ),
+ ),
+ 'text_plain' => array(
+ 'label' => t('Plain text'),
+ 'field types' => array('text', 'text_long'),
+ 'behaviors' => array(
+ 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+ ),
+ ),
+ 'text_trimmed' => array(
+ 'label' => t('Trimmed'),
+ 'field types' => array('text', 'text_long'),
+ 'behaviors' => array(
+ 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+ ),
+ ),
+ );
+}
+
+/**
+ * Theme function for 'default' text field formatter.
+ */
+function theme_field_formatter_text_default($element) {
+ return $element['#item']['safe'];
+}
+
+/**
+ * Theme function for 'plain' text field formatter.
+ */
+function theme_field_formatter_text_plain($element) {
+ return strip_tags($element['#item']['safe']);
+}
+
+/**
+ * Theme function for 'trimmed' text field formatter.
+ */
+function theme_field_formatter_text_trimmed($element) {
+ $field = field_info_field($element['#field_name']);
+ $instance = field_info_instance($element['#field_name'], $element['#bundle']);
+ return $instance['settings']['text_processing'] ? $element['#item']['format'] : NULL;
+}
+
+/**
+ * Implementation of hook_field_widget_info().
+ *
+ * Here we indicate that the field module will handle
+ * the default value and multiple values for these widgets.
+ *
+ * Callbacks can be omitted if default handing is used.
+ * They're included here just so this module can be used
+ * as an example for custom modules that might do things
+ * differently.
+ */
+function text_field_widget_info() {
+ return array(
+ 'text_textfield' => array(
+ 'label' => t('Text field'),
+ 'field types' => array('text'),
+ 'settings' => array('size' => 60),
+ 'behaviors' => array(
+ 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+ 'default value' => FIELD_BEHAVIOR_DEFAULT,
+ ),
+ ),
+ 'text_textarea' => array(
+ 'label' => t('Text area (multiple rows)'),
+ 'field types' => array('text_long'),
+ 'settings' => array('rows' => 5),
+ 'behaviors' => array(
+ 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+ 'default value' => FIELD_BEHAVIOR_DEFAULT,
+ ),
+ ),
+ );
+}
+
+/**
+ * Implementation of FAPI hook_elements().
+ *
+ * Any FAPI callbacks needed for individual widgets can be declared here,
+ * and the element will be passed to those callbacks for processing.
+ *
+ * Drupal will automatically theme the element using a theme with
+ * the same name as the hook_elements key.
+ *
+ * Autocomplete_path is not used by text_field_widget but other
+ * widgets can use it (see nodereference and userreference).
+ */
+function text_elements() {
+ return array(
+ 'text_textfield' => array(
+ '#input' => TRUE,
+ '#columns' => array('value'), '#delta' => 0,
+ '#process' => array('text_textfield_process'),
+ '#autocomplete_path' => FALSE,
+ ),
+ 'text_textarea' => array(
+ '#input' => TRUE,
+ '#columns' => array('value', 'format'), '#delta' => 0,
+ '#process' => array('text_textarea_process'),
+ '#filter_value' => FILTER_FORMAT_DEFAULT,
+ ),
+ );
+}
+
+/**
+ * Implementation of hook_field_widget().
+ *
+ * Attach a single form element to the form. It will be built out and
+ * validated in the callback(s) listed in hook_elements. We build it
+ * out in the callbacks rather than here in hook_field_widget so it can be
+ * plugged into any module that can provide it with valid
+ * $field information.
+ *
+ * Field module will set the weight, field name and delta values
+ * for each form element.
+ *
+ * If there are multiple values for this field, the field module will
+ * call this function as many times as needed.
+ *
+ * @param $form
+ * the entire form array, $form['#node'] holds node information
+ * @param $form_state
+ * the form_state, $form_state['values'][$field['field_name']]
+ * holds the field's form values.
+ * @param $field
+ * The field structure.
+ * @param $instance
+ * the field instance array
+ * @param $items
+ * array of default values for this field
+ * @param $delta
+ * the order of this item in the array of subelements (0, 1, 2, etc)
+ *
+ * @return
+ * the form item for a single element for this field
+ */
+function text_field_widget(&$form, &$form_state, $field, $instance, $items, $delta = 0) {
+ $element = array(
+ '#type' => $instance['widget']['type'],
+ '#default_value' => isset($items[$delta]) ? $items[$delta] : '',
+ );
+ return $element;
+}
+
+/**
+ * Process an individual element.
+ *
+ * Build the form element. When creating a form using FAPI #process,
+ * note that $element['#value'] is already set.
+ *
+ * The $field and $instance arrays are in $form['#fields'][$element['#field_name']].
+ *
+ * TODO: For widgets to be actual FAPI 'elements', reusable outside of a
+ * 'field' context, they shoudn't rely on $field and $instance. The bits of
+ * information needed to adjust the behavior of the 'element' should be
+ * extracted in hook_field_widget() above.
+ */
+function text_textfield_process($element, $edit, $form_state, $form) {
+ $field = $form['#fields'][$element['#field_name']]['field'];
+ $instance = $form['#fields'][$element['#field_name']]['instance'];
+ $field_key = $element['#columns'][0];
+ $delta = $element['#delta'];
+
+ $element[$field_key] = array(
+ '#type' => 'textfield',
+ '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL,
+ '#autocomplete_path' => $element['#autocomplete_path'],
+ '#size' => $instance['widget']['settings']['size'],
+ '#attributes' => array('class' => 'text'),
+ // The following values were set by the field module and need
+ // to be passed down to the nested element.
+ '#title' => $element['#title'],
+ '#description' => $element['#description'],
+ '#required' => $element['#required'],
+ '#field_name' => $element['#field_name'],
+ '#bundle' => $element['#bundle'],
+ '#delta' => $element['#delta'],
+ '#columns' => $element['#columns'],
+ );
+
+ $element[$field_key]['#maxlength'] = !empty($field['settings']['max_length']) ? $field['settings']['max_length'] : NULL;
+
+ if (!empty($instance['settings']['text_processing'])) {
+ $filter_key = $element['#columns'][1];
+ $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : FILTER_FORMAT_DEFAULT;
+ $parents = array_merge($element['#parents'] , array($filter_key));
+ $element[$filter_key] = filter_form($format, 1, $parents);
+ }
+
+ // Used so that hook_field('validate') knows where to flag an error.
+ // TODO: rework that. See http://groups.drupal.org/node/18019.
+ $element['_error_element'] = array(
+ '#type' => 'value',
+ '#value' => implode('][', array_merge($element['#parents'], array($field_key))),
+ );
+
+ return $element;
+}
+
+/**
+ * Process an individual element.
+ *
+ * Build the form element. When creating a form using FAPI #process,
+ * note that $element['#value'] is already set.
+ *
+ * The $field and $instance arrays are in $form['#fields'][$element['#field_name']].
+ */
+function text_textarea_process($element, $edit, $form_state, $form) {
+ $field = $form['#fields'][$element['#field_name']]['field'];
+ $instance = $form['#fields'][$element['#field_name']]['instance'];
+ $field_key = $element['#columns'][0];
+ $delta = $element['#delta'];
+ $element[$field_key] = array(
+ '#type' => 'textarea',
+ '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL,
+ '#rows' => $instance['widget']['settings']['rows'],
+ '#weight' => 0,
+ // The following values were set by the field module and need
+ // to be passed down to the nested element.
+ '#title' => $element['#title'],
+ '#description' => $element['#description'],
+ '#required' => $element['#required'],
+ '#field_name' => $element['#field_name'],
+ '#bundle' => $element['#bundle'],
+ '#delta' => $element['#delta'],
+ '#columns' => $element['#columns'],
+ );
+
+ if (!empty($instance['settings']['text_processing'])) {
+ $filter_key = (count($element['#columns']) == 2) ? $element['#columns'][1] : 'format';
+ $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : FILTER_FORMAT_DEFAULT;
+ $parents = array_merge($element['#parents'] , array($filter_key));
+ $element[$filter_key] = filter_form($format, 1, $parents);
+ }
+
+ // Used so that hook_field('validate') knows where to flag an error.
+ $element['_error_element'] = array(
+ '#type' => 'value',
+ '#value' => implode('][', array_merge($element['#parents'], array($field_key))),
+ );
+ return $element;
+}
+
+/**
+ * FAPI theme for an individual text elements.
+ *
+ * The textfield or textarea is already rendered by the
+ * textfield or textarea themes and the html output
+ * lives in $element['#children']. Override this theme to
+ * make custom changes to the output.
+ *
+ * $element['#field_name'] contains the field name
+ * $element['#delta] is the position of this element in the group
+ */
+function theme_text_textfield($element) {
+ return $element['#children'];
+}
+
+function theme_text_textarea($element) {
+ return $element['#children'];
} \ No newline at end of file
diff --git a/modules/field/sample_code.php b/modules/field/sample_code.php
index fbbc2eb76..b93474b4d 100644
--- a/modules/field/sample_code.php
+++ b/modules/field/sample_code.php
@@ -1,103 +1,103 @@
-<?php
-
-$field = array(
- 'field_name' => 'field_single',
- 'type' => 'text',
-);
-field_create_field($field);
-
-$instance = array(
- 'field_name' => 'field_single',
- 'bundle' => 'article',
- 'label' => 'Single',
- 'widget' => array(
- 'type' => 'text_textfield',
- ),
- 'display' => array(
- 'full' => array(
- 'label' => 'above',
- 'type' => 'text_default',
- 'exclude' => 0,
- ),
- ),
-);
-field_create_instance($instance);
-
-$instance['bundle'] = 'user';
-field_create_instance($instance);
-
-
-$field = array(
- 'field_name' => 'field_multiple',
- 'cardinality' => FIELD_CARDINALITY_UNLIMITED,
- 'type' => 'text',
-);
-field_create_field($field);
-
-$instance = array(
- 'field_name' => 'field_multiple',
- 'bundle' => 'article',
- 'label' => 'Multiple',
- 'widget' => array(
- 'type' => 'text_textfield',
- ),
- 'display' => array(
- 'full' => array(
- 'label' => 'above',
- 'type' => 'text_default',
- 'exclude' => 0,
- ),
- ),
-);
-field_create_instance($instance);
-
-$instance['bundle'] = 'user';
-field_create_instance($instance);
-
-
-// Number
-$field = array(
- 'field_name' => 'field_integer',
- 'type' => 'number_integer',
-);
-field_create_field($field);
-$instance = array(
- 'field_name' => 'field_integer',
- 'bundle' => 'article',
- 'label' => 'Integer',
- 'widget' => array(
- 'type' => 'number',
- ),
- 'display' => array(
- 'full' => array(
- 'label' => 'above',
- 'type' => 'number_integer',
- 'exclude' => 0,
- ),
- ),
-);
-field_create_instance($instance);
-
-$field = array(
- 'field_name' => 'field_decimal',
- 'type' => 'number_decimal',
-);
-field_create_field($field);
-$instance = array(
- 'field_name' => 'field_decimal',
- 'bundle' => 'article',
- 'label' => 'Decimal',
- 'widget' => array(
- 'type' => 'number',
- ),
- 'display' => array(
- 'full' => array(
- 'label' => 'above',
- 'type' => 'number_decimal',
- 'exclude' => 0,
- ),
- ),
-);
-field_create_instance($instance);
-
-
+<?php
+
+$field = array(
+ 'field_name' => 'field_single',
+ 'type' => 'text',
+);
+field_create_field($field);
+
+$instance = array(
+ 'field_name' => 'field_single',
+ 'bundle' => 'article',
+ 'label' => 'Single',
+ 'widget' => array(
+ 'type' => 'text_textfield',
+ ),
+ 'display' => array(
+ 'full' => array(
+ 'label' => 'above',
+ 'type' => 'text_default',
+ 'exclude' => 0,
+ ),
+ ),
+);
+field_create_instance($instance);
+
+$instance['bundle'] = 'user';
+field_create_instance($instance);
+
+
+$field = array(
+ 'field_name' => 'field_multiple',
+ 'cardinality' => FIELD_CARDINALITY_UNLIMITED,
+ 'type' => 'text',
+);
+field_create_field($field);
+
+$instance = array(
+ 'field_name' => 'field_multiple',
+ 'bundle' => 'article',
+ 'label' => 'Multiple',
+ 'widget' => array(
+ 'type' => 'text_textfield',
+ ),
+ 'display' => array(
+ 'full' => array(
+ 'label' => 'above',
+ 'type' => 'text_default',
+ 'exclude' => 0,
+ ),
+ ),
+);
+field_create_instance($instance);
+
+$instance['bundle'] = 'user';
+field_create_instance($instance);
+
+
+// Number
+$field = array(
+ 'field_name' => 'field_integer',
+ 'type' => 'number_integer',
+);
+field_create_field($field);
+$instance = array(
+ 'field_name' => 'field_integer',
+ 'bundle' => 'article',
+ 'label' => 'Integer',
+ 'widget' => array(
+ 'type' => 'number',
+ ),
+ 'display' => array(
+ 'full' => array(
+ 'label' => 'above',
+ 'type' => 'number_integer',
+ 'exclude' => 0,
+ ),
+ ),
+);
+field_create_instance($instance);
+
+$field = array(
+ 'field_name' => 'field_decimal',
+ 'type' => 'number_decimal',
+);
+field_create_field($field);
+$instance = array(
+ 'field_name' => 'field_decimal',
+ 'bundle' => 'article',
+ 'label' => 'Decimal',
+ 'widget' => array(
+ 'type' => 'number',
+ ),
+ 'display' => array(
+ 'full' => array(
+ 'label' => 'above',
+ 'type' => 'number_decimal',
+ 'exclude' => 0,
+ ),
+ ),
+);
+field_create_instance($instance);
+
+
diff --git a/modules/field/theme/field.css b/modules/field/theme/field.css
index 57eb97105..aa9a4921c 100644
--- a/modules/field/theme/field.css
+++ b/modules/field/theme/field.css
@@ -1,3 +1,5 @@
+/* $Id$ */
+
/* Node display */
.field .field-label,
.field .field-label-inline,
@@ -27,4 +29,4 @@
.node-form .number {
display:inline;
width:auto;
-} \ No newline at end of file
+}
diff --git a/modules/node/node.pages.inc b/modules/node/node.pages.inc
index 03866ec8b..cb5a70d84 100644
--- a/modules/node/node.pages.inc
+++ b/modules/node/node.pages.inc
@@ -257,10 +257,10 @@ function node_form(&$form_state, $node) {
);
}
$form['#validate'][] = 'node_form_validate';
- $form['#theme'] = array($node->type . '_node_form', 'node_form');
-
- $form['#builder_function'] = 'node_form_submit_build_node';
- field_attach_form('node', $node, $form, $form_state);
+ $form['#theme'] = array($node->type . '_node_form', 'node_form');
+
+ $form['#builder_function'] = 'node_form_submit_build_node';
+ field_attach_form('node', $node, $form, $form_state);
return $form;
}
@@ -468,10 +468,10 @@ function node_form_submit_build_node($form, &$form_state) {
// Unset any button-level handlers, execute all the form-level submit
// functions to process the form values into an updated node.
unset($form_state['submit_handlers']);
- form_execute_handlers('submit', $form, $form_state);
- $node = node_submit($form_state['values']);
-
- field_attach_submit('node', $node, $form, $form_state);
+ form_execute_handlers('submit', $form, $form_state);
+ $node = node_submit($form_state['values']);
+
+ field_attach_submit('node', $node, $form, $form_state);
$form_state['node'] = (array)$node;
$form_state['rebuild'] = TRUE;
diff --git a/modules/node/node.tpl.php b/modules/node/node.tpl.php
index 3e950f11d..a5bda033a 100644
--- a/modules/node/node.tpl.php
+++ b/modules/node/node.tpl.php
@@ -20,7 +20,7 @@
* - $terms: the themed list of taxonomy term links output from theme_links().
* - $submitted: themed submission information output from
* theme_node_submitted().
- * TODO D7 : document $FIELD_NAME_rendered variables.
+ * TODO D7 : document $FIELD_NAME_rendered variables.
*
* Other variables:
* - $node: Full node object. Contains data that may not be safe.
diff --git a/modules/simpletest/tests/field_test.install b/modules/simpletest/tests/field_test.install
index c706ebfe4..d64886b5d 100644
--- a/modules/simpletest/tests/field_test.install
+++ b/modules/simpletest/tests/field_test.install
@@ -2,59 +2,59 @@
// $Id$
/**
- * Implementation of hook_schema().
+ * Implementation of hook_schema().
*/
function field_test_schema() {
$schema['test_entity'] = array(
- 'description' => 'The base table for test_entities.',
- 'fields' => array(
- 'ftid' => array(
- 'description' => 'The primary identifier for a test_entity.',
- 'type' => 'serial',
- 'unsigned' => TRUE,
- 'not null' => TRUE,
- ),
- 'ftvid' => array(
- 'description' => 'The current {test_entity_revision}.ftvid version identifier.',
- 'type' => 'int',
- 'unsigned' => TRUE,
- 'not null' => TRUE,
- 'default' => 0,
- ),
- 'fttype' => array(
- 'description' => 'The type of this test_entity.',
- 'type' => 'varchar',
- 'length' => 32,
- 'not null' => TRUE,
- 'default' => '',
+ 'description' => 'The base table for test_entities.',
+ 'fields' => array(
+ 'ftid' => array(
+ 'description' => 'The primary identifier for a test_entity.',
+ 'type' => 'serial',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ),
+ 'ftvid' => array(
+ 'description' => 'The current {test_entity_revision}.ftvid version identifier.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'fttype' => array(
+ 'description' => 'The type of this test_entity.',
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => TRUE,
+ 'default' => '',
),
),
- 'unique keys' => array(
- 'ftvid' => array('ftvid'),
- ),
+ 'unique keys' => array(
+ 'ftvid' => array('ftvid'),
+ ),
'primary key' => array('ftid'),
- );
- $schema['test_entity_revision'] = array(
- 'description' => 'Stores information about each saved version of a {test_entity}.',
- 'fields' => array(
- 'ftid' => array(
- 'description' => 'The {test_entity} this version belongs to.',
- 'type' => 'int',
- 'unsigned' => TRUE,
- 'not null' => TRUE,
- 'default' => 0,
- ),
- 'ftvid' => array(
- 'description' => 'The primary identifier for this version.',
- 'type' => 'serial',
- 'unsigned' => TRUE,
- 'not null' => TRUE,
- ),
- ),
- 'indexes' => array(
- 'nid' => array('ftid'),
- ),
- 'primary key' => array('ftvid'),
+ );
+ $schema['test_entity_revision'] = array(
+ 'description' => 'Stores information about each saved version of a {test_entity}.',
+ 'fields' => array(
+ 'ftid' => array(
+ 'description' => 'The {test_entity} this version belongs to.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'ftvid' => array(
+ 'description' => 'The primary identifier for this version.',
+ 'type' => 'serial',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ),
+ ),
+ 'indexes' => array(
+ 'nid' => array('ftid'),
+ ),
+ 'primary key' => array('ftvid'),
);
return $schema;
diff --git a/modules/simpletest/tests/field_test.module b/modules/simpletest/tests/field_test.module
index 230eb7a54..3772ad920 100644
--- a/modules/simpletest/tests/field_test.module
+++ b/modules/simpletest/tests/field_test.module
@@ -3,299 +3,299 @@
define('FIELD_TEST_ELEMENT_ID', 1);
define('FIELD_TEST_BUNDLE', 'test_bundle');
-
-/**
- * Implementation of hook_perm().
- */
-function field_test_perm() {
- $perms = array(
- 'access field_test content' => array(
- 'title' => t('Access field_test content'),
- 'description' => t('View published field_test content.'),
- ),
- 'administer field_test content' => array(
- 'title' => t('Administer field_test content'),
- 'description' => t('Manage field_test content'),
- ),
- );
- return $perms;
-}
-
-/**
- * Implementation of hook_menu().
- */
-function field_test_menu() {
- $items = array();
- $info = field_test_fieldable_info();
-
- foreach (array_keys($info['test_entity']['bundles']) as $bundle) {
- $bundle_url_str = str_replace('_', '-', $bundle);
- $items['test-entity/add/' . $bundle_url_str] = array(
- 'title' => "Add $bundle test_entity",
- 'page callback' => 'field_test_entity_add',
- 'page arguments' => array(2),
- 'access arguments' => array('administer field_test content'),
- 'type' => MENU_NORMAL_ITEM,
- );
- }
- $items['test-entity/%field_test_entity/edit'] = array(
- 'title' => 'Edit test entity',
- 'page callback' => 'field_test_entity_edit',
- 'page arguments' => array(1),
- 'access arguments' => array('administer field_test content'),
- 'type' => MENU_NORMAL_ITEM,
- );
-
- return $items;
-}
-
-
-/**
- *
- * 'Field attach' API.
- *
- */
-
-
-/**
- * Define a test fieldable entity.
- */
-function field_test_fieldable_info() {
- $bundles = variable_get('field_test_bundles', array('test_bundle' => 'Test Bundle'));
- return array(
- 'test_entity' => array(
- 'name' => t('Test Entity'),
- 'id key' => 'ftid',
- 'revision key' => 'ftvid',
- 'cacheable' => FALSE,
- 'bundle key' => 'fttype',
- 'bundles' => $bundles,
- ),
- // This entity type doesn't get form handling for now...
- 'test_cacheable_entity' => array(
- 'name' => t('Test Entity, cacheable'),
- 'id key' => 'ftid',
- 'revision key' => 'ftvid',
- 'cacheable' => TRUE,
- 'bundle key' => 'fttype',
- 'bundles' => $bundles,
- ),
- );
-}
-
-function field_test_create_bundle($bundle, $text) {
- $bundles = variable_get('field_test_bundles', array('field_text_bundle' => 'Test Bundle'));
- $bundles += array($bundle => $text);
- variable_set('field_test_bundles', $bundles);
-
- field_attach_create_bundle($bundle);
-}
-
-function field_test_rename_bundle($bundle_old, $bundle_new) {
- $bundles = variable_get('field_test_bundles', array('field_text_bundle' => 'Test Bundle'));
- $bundles[$bundle_new] = $bundles[$bundle_old];
- unset($bundles[$bundle_old]);
- variable_set('field_test_bundles', $bundles);
-
- field_attach_rename_bundle($bundle_old, $bundle_new);
-}
-
-function field_test_delete_bundle($bundle) {
- $bundles = variable_get('field_test_bundles', array('field_text_bundle' => 'Test Bundle'));
- unset($bundles[$bundle]);
- variable_set('field_test_bundles', $bundles);
-
- field_attach_delete_bundle($bundle);
-}
-
-/**
- * Implementation of hook_field_build_modes().
- */
-function field_test_field_build_modes($obj_type) {
- $modes = array();
- if ($obj_type == 'test_entity' || $obj_type == 'test_cacheable_entity') {
- $modes = array(
- 'full' => t('Full node'),
- 'teaser' => t('Teaser'),
- );
- }
- return $modes;
-}
-
-/**
- * Helper function to create a basic 'test entity' structure.
- *
- * TODO : do we stil need this now that we can actualy load and save test_entities ?
- */
-function field_test_create_stub_entity($id = 1, $vid = 1, $bundle = FIELD_TEST_BUNDLE) {
- $entity = new stdClass();
- $entity->ftid = $id;
- $entity->ftvid = $vid;
- $entity->fttype = $bundle;
-
- return $entity;
-}
-
-function field_test_entity_load($ftid, $ftvid = NULL) {
- // Load basic strucure.
- $query = db_select('test_entity', 'fte', array())
- ->fields('fte')
- ->condition('ftid', $ftid);
- if ($ftvid) {
- $query->condition('ftvid', $ftvid);
- }
- $entities = $query->execute()->fetchAllAssoc('ftid');
-
- // Attach fields.
- if ($ftvid) {
- field_attach_load_revision('test_entity', $entities);
- }
- else {
- field_attach_load('test_entity', $entities);
- }
-
- return $entities[$ftid];
-}
-
-function field_test_entity_save(&$entity) {
- field_attach_presave('test_entity', $entity);
-
- $entity->is_new = FALSE;
- if (empty($entity->ftid)) {
- // Insert a new test_entity.
- $entity->is_new = TRUE;
- }
- elseif (!empty($entity->revision)) {
- $entity->old_ftvid = $entity->ftvid;
- }
-
- $update_entity = TRUE;
- if ($entity->is_new) {
- drupal_write_record('test_entity', $entity);
- drupal_write_record('test_entity_revision', $entity);
- $op = 'insert';
- }
- else {
- drupal_write_record('test_entity', $entity, 'ftid');
- if (!empty($entity->revision)) {
- drupal_write_record('test_entity_revision', $entity);
- }
- else {
- drupal_write_record('test_entity_revision', $entity, 'ftvid');
- $update_entity = FALSE;
- }
- $op = 'update';
- }
- if ($update_entity) {
- db_update('test_entity')
- ->fields(array('ftvid' => $entity->ftvid))
- ->condition('ftid', $entity->ftid)
- ->execute();
- }
-
- // Save fields.
- $function = "field_attach_$op";
- $function('test_entity', $entity);
-}
-
-function field_test_entity_add($fttype) {
- $fttype = str_replace('-', '_', $fttype);
- $entity = (object)array('fttype' => $fttype);
- drupal_set_title(t('Create test_entity @bundle', array('@bundle' => $fttype)), PASS_THROUGH);
- return drupal_get_form('field_test_entity_form', $entity);
-}
-
-function field_test_entity_edit($entity) {
- drupal_set_title(t('test_entity @ftid revision @ftvid', array('@ftid' => $entity->ftid, '@ftvid' => $entity->ftvid)), PASS_THROUGH);
- return drupal_get_form('field_test_entity_form', $entity);
-}
-
-/**
- * Form to set the value of fields attached to our entity.
- */
-function field_test_entity_form(&$form_state, $entity) {
- $form = array();
-
- if (isset($form_state['test_entity'])) {
- $entity = $form_state['test_entity'] + (array)$entity;
- }
- $entity = (object)$entity;
-
- foreach (array('ftid', 'ftvid', 'fttype') as $key) {
- $form[$key] = array(
- '#type' => 'value',
- '#value' => isset($entity->$key) ? $entity->$key : NULL,
- );
- }
-
- // Add field widgets.
- $form['#builder_function'] = 'field_test_entity_form_submit_builder';
- field_attach_form('test_entity', $entity, $form, $form_state);
-
- $form['revision'] = array(
- '#access' => user_access('administer field_test content'),
- '#type' => 'checkbox',
- '#title' => t('Create new revision'),
- '#default_value' => FALSE,
- '#weight' => 100,
- );
- $form['submit'] = array(
- '#type' => 'submit',
- '#value' => t('Save'),
- '#weight' => 101,
- );
-
- return $form;
-}
-
-/**
- * Validate handler for field_test_set_field_values().
- */
-function field_test_entity_form_validate($form, &$form_state) {
- $entity = (object)$form_state['values'];
- field_attach_validate('test_entity', $entity, $form);
-}
-
-/**
- * Submit handler for field_test_set_field_values().
- */
-function field_test_entity_form_submit($form, &$form_state) {
- $entity = field_test_entity_form_submit_builder($form, $form_state);
- $insert = empty($entity->ftid);
- field_test_entity_save($entity);
-
- $message = $insert ? t('test_entity @id has been created.', array('@id' => $entity->ftid)) : t('test_entity @id has been updated.', array('@id' => $entity->ftid));
- drupal_set_message($message);
-
- if ($entity->ftid) {
- unset($form_state['rebuild']);
- $form_state['redirect'] = 'test-entity/' . $entity->ftid . '/edit';
- }
- else {
- // Error on save.
- drupal_set_message(t('The entity could not be saved.'), 'error');
- }
-
-}
-
-/**
- * Build a test_entity by processing submitted form values and prepare for a form rebuild.
- */
-function field_test_entity_form_submit_builder($form, &$form_state) {
- $entity = field_test_create_stub_entity($form_state['values']['ftid'], $form_state['values']['ftvid'], $form_state['values']['fttype']);
- field_attach_submit('test_entity', $entity, $form, $form_state);
-
- $form_state['test_entity'] = (array)$entity;
- $form_state['rebuild'] = TRUE;
-
- return $entity;
-}
-
-/**
- *
- * 'Field type' API.
- *
- */
+
+/**
+ * Implementation of hook_perm().
+ */
+function field_test_perm() {
+ $perms = array(
+ 'access field_test content' => array(
+ 'title' => t('Access field_test content'),
+ 'description' => t('View published field_test content.'),
+ ),
+ 'administer field_test content' => array(
+ 'title' => t('Administer field_test content'),
+ 'description' => t('Manage field_test content'),
+ ),
+ );
+ return $perms;
+}
+
+/**
+ * Implementation of hook_menu().
+ */
+function field_test_menu() {
+ $items = array();
+ $info = field_test_fieldable_info();
+
+ foreach (array_keys($info['test_entity']['bundles']) as $bundle) {
+ $bundle_url_str = str_replace('_', '-', $bundle);
+ $items['test-entity/add/' . $bundle_url_str] = array(
+ 'title' => "Add $bundle test_entity",
+ 'page callback' => 'field_test_entity_add',
+ 'page arguments' => array(2),
+ 'access arguments' => array('administer field_test content'),
+ 'type' => MENU_NORMAL_ITEM,
+ );
+ }
+ $items['test-entity/%field_test_entity/edit'] = array(
+ 'title' => 'Edit test entity',
+ 'page callback' => 'field_test_entity_edit',
+ 'page arguments' => array(1),
+ 'access arguments' => array('administer field_test content'),
+ 'type' => MENU_NORMAL_ITEM,
+ );
+
+ return $items;
+}
+
+
+/**
+ *
+ * 'Field attach' API.
+ *
+ */
+
+
+/**
+ * Define a test fieldable entity.
+ */
+function field_test_fieldable_info() {
+ $bundles = variable_get('field_test_bundles', array('test_bundle' => 'Test Bundle'));
+ return array(
+ 'test_entity' => array(
+ 'name' => t('Test Entity'),
+ 'id key' => 'ftid',
+ 'revision key' => 'ftvid',
+ 'cacheable' => FALSE,
+ 'bundle key' => 'fttype',
+ 'bundles' => $bundles,
+ ),
+ // This entity type doesn't get form handling for now...
+ 'test_cacheable_entity' => array(
+ 'name' => t('Test Entity, cacheable'),
+ 'id key' => 'ftid',
+ 'revision key' => 'ftvid',
+ 'cacheable' => TRUE,
+ 'bundle key' => 'fttype',
+ 'bundles' => $bundles,
+ ),
+ );
+}
+
+function field_test_create_bundle($bundle, $text) {
+ $bundles = variable_get('field_test_bundles', array('field_text_bundle' => 'Test Bundle'));
+ $bundles += array($bundle => $text);
+ variable_set('field_test_bundles', $bundles);
+
+ field_attach_create_bundle($bundle);
+}
+
+function field_test_rename_bundle($bundle_old, $bundle_new) {
+ $bundles = variable_get('field_test_bundles', array('field_text_bundle' => 'Test Bundle'));
+ $bundles[$bundle_new] = $bundles[$bundle_old];
+ unset($bundles[$bundle_old]);
+ variable_set('field_test_bundles', $bundles);
+
+ field_attach_rename_bundle($bundle_old, $bundle_new);
+}
+
+function field_test_delete_bundle($bundle) {
+ $bundles = variable_get('field_test_bundles', array('field_text_bundle' => 'Test Bundle'));
+ unset($bundles[$bundle]);
+ variable_set('field_test_bundles', $bundles);
+
+ field_attach_delete_bundle($bundle);
+}
+
+/**
+ * Implementation of hook_field_build_modes().
+ */
+function field_test_field_build_modes($obj_type) {
+ $modes = array();
+ if ($obj_type == 'test_entity' || $obj_type == 'test_cacheable_entity') {
+ $modes = array(
+ 'full' => t('Full node'),
+ 'teaser' => t('Teaser'),
+ );
+ }
+ return $modes;
+}
+
+/**
+ * Helper function to create a basic 'test entity' structure.
+ *
+ * TODO : do we stil need this now that we can actualy load and save test_entities ?
+ */
+function field_test_create_stub_entity($id = 1, $vid = 1, $bundle = FIELD_TEST_BUNDLE) {
+ $entity = new stdClass();
+ $entity->ftid = $id;
+ $entity->ftvid = $vid;
+ $entity->fttype = $bundle;
+
+ return $entity;
+}
+
+function field_test_entity_load($ftid, $ftvid = NULL) {
+ // Load basic strucure.
+ $query = db_select('test_entity', 'fte', array())
+ ->fields('fte')
+ ->condition('ftid', $ftid);
+ if ($ftvid) {
+ $query->condition('ftvid', $ftvid);
+ }
+ $entities = $query->execute()->fetchAllAssoc('ftid');
+
+ // Attach fields.
+ if ($ftvid) {
+ field_attach_load_revision('test_entity', $entities);
+ }
+ else {
+ field_attach_load('test_entity', $entities);
+ }
+
+ return $entities[$ftid];
+}
+
+function field_test_entity_save(&$entity) {
+ field_attach_presave('test_entity', $entity);
+
+ $entity->is_new = FALSE;
+ if (empty($entity->ftid)) {
+ // Insert a new test_entity.
+ $entity->is_new = TRUE;
+ }
+ elseif (!empty($entity->revision)) {
+ $entity->old_ftvid = $entity->ftvid;
+ }
+
+ $update_entity = TRUE;
+ if ($entity->is_new) {
+ drupal_write_record('test_entity', $entity);
+ drupal_write_record('test_entity_revision', $entity);
+ $op = 'insert';
+ }
+ else {
+ drupal_write_record('test_entity', $entity, 'ftid');
+ if (!empty($entity->revision)) {
+ drupal_write_record('test_entity_revision', $entity);
+ }
+ else {
+ drupal_write_record('test_entity_revision', $entity, 'ftvid');
+ $update_entity = FALSE;
+ }
+ $op = 'update';
+ }
+ if ($update_entity) {
+ db_update('test_entity')
+ ->fields(array('ftvid' => $entity->ftvid))
+ ->condition('ftid', $entity->ftid)
+ ->execute();
+ }
+
+ // Save fields.
+ $function = "field_attach_$op";
+ $function('test_entity', $entity);
+}
+
+function field_test_entity_add($fttype) {
+ $fttype = str_replace('-', '_', $fttype);
+ $entity = (object)array('fttype' => $fttype);
+ drupal_set_title(t('Create test_entity @bundle', array('@bundle' => $fttype)), PASS_THROUGH);
+ return drupal_get_form('field_test_entity_form', $entity);
+}
+
+function field_test_entity_edit($entity) {
+ drupal_set_title(t('test_entity @ftid revision @ftvid', array('@ftid' => $entity->ftid, '@ftvid' => $entity->ftvid)), PASS_THROUGH);
+ return drupal_get_form('field_test_entity_form', $entity);
+}
+
+/**
+ * Form to set the value of fields attached to our entity.
+ */
+function field_test_entity_form(&$form_state, $entity) {
+ $form = array();
+
+ if (isset($form_state['test_entity'])) {
+ $entity = $form_state['test_entity'] + (array)$entity;
+ }
+ $entity = (object)$entity;
+
+ foreach (array('ftid', 'ftvid', 'fttype') as $key) {
+ $form[$key] = array(
+ '#type' => 'value',
+ '#value' => isset($entity->$key) ? $entity->$key : NULL,
+ );
+ }
+
+ // Add field widgets.
+ $form['#builder_function'] = 'field_test_entity_form_submit_builder';
+ field_attach_form('test_entity', $entity, $form, $form_state);
+
+ $form['revision'] = array(
+ '#access' => user_access('administer field_test content'),
+ '#type' => 'checkbox',
+ '#title' => t('Create new revision'),
+ '#default_value' => FALSE,
+ '#weight' => 100,
+ );
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save'),
+ '#weight' => 101,
+ );
+
+ return $form;
+}
+
+/**
+ * Validate handler for field_test_set_field_values().
+ */
+function field_test_entity_form_validate($form, &$form_state) {
+ $entity = (object)$form_state['values'];
+ field_attach_validate('test_entity', $entity, $form);
+}
+
+/**
+ * Submit handler for field_test_set_field_values().
+ */
+function field_test_entity_form_submit($form, &$form_state) {
+ $entity = field_test_entity_form_submit_builder($form, $form_state);
+ $insert = empty($entity->ftid);
+ field_test_entity_save($entity);
+
+ $message = $insert ? t('test_entity @id has been created.', array('@id' => $entity->ftid)) : t('test_entity @id has been updated.', array('@id' => $entity->ftid));
+ drupal_set_message($message);
+
+ if ($entity->ftid) {
+ unset($form_state['rebuild']);
+ $form_state['redirect'] = 'test-entity/' . $entity->ftid . '/edit';
+ }
+ else {
+ // Error on save.
+ drupal_set_message(t('The entity could not be saved.'), 'error');
+ }
+
+}
+
+/**
+ * Build a test_entity by processing submitted form values and prepare for a form rebuild.
+ */
+function field_test_entity_form_submit_builder($form, &$form_state) {
+ $entity = field_test_create_stub_entity($form_state['values']['ftid'], $form_state['values']['ftvid'], $form_state['values']['fttype']);
+ field_attach_submit('test_entity', $entity, $form, $form_state);
+
+ $form_state['test_entity'] = (array)$entity;
+ $form_state['rebuild'] = TRUE;
+
+ return $entity;
+}
+
+/**
+ *
+ * 'Field type' API.
+ *
+ */
/**
* Implementation of hook_field_info().
@@ -327,11 +327,11 @@ function field_test_field_columns($field) {
return $columns;
}
-/**
- * Implementation of hook_instance_settings().
- */
-function field_test_field_instance_settings($field_type) {
- return array('test_instance_setting' => 'dummy test string');
+/**
+ * Implementation of hook_instance_settings().
+ */
+function field_test_field_instance_settings($field_type) {
+ return array('test_instance_setting' => 'dummy test string');
}
/**
@@ -395,8 +395,8 @@ function field_test_field_widget_info() {
'field types' => array('test_field'),
'settings' => array('test_widget_setting_multiple' => 'dummy test string'),
'behaviors' => array(
- 'multiple values' => FIELD_BEHAVIOR_CUSTOM,
- 'default value' => FIELD_BEHAVIOR_DEFAULT,
+ 'multiple values' => FIELD_BEHAVIOR_CUSTOM,
+ 'default value' => FIELD_BEHAVIOR_DEFAULT,
),
),
);
@@ -436,31 +436,31 @@ function field_test_field_widget_info() {
* the form item for a single element for this field
*/
function field_test_field_widget(&$form, &$form_state, $field, $instance, $items, $delta = 0) {
- $element = array(
- 'value' => array(
+ $element = array(
+ 'value' => array(
'#title' => $instance['label'],
'#type' => 'textfield',
- '#default_value' => isset($items[$delta]['value']) ? $items[$delta]['value'] : '',
- '#required' => $instance['required'],
+ '#default_value' => isset($items[$delta]['value']) ? $items[$delta]['value'] : '',
+ '#required' => $instance['required'],
),
);
return $element;
}
-/**
- * Implementation of hook_field_formatter_info().
- */
-function field_test_field_formatter_info() {
- return array(
- 'field_test_default' => array(
- 'label' => t('Default'),
- 'field types' => array('test_field'),
+/**
+ * Implementation of hook_field_formatter_info().
+ */
+function field_test_field_formatter_info() {
+ return array(
+ 'field_test_default' => array(
+ 'label' => t('Default'),
+ 'field types' => array('test_field'),
'settings' => array(
'test_formatter_setting' => 'dummy test string',
),
- 'behaviors' => array(
- 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
- ),
+ 'behaviors' => array(
+ 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+ ),
),
'field_test_multiple' => array(
'label' => t('Default'),
@@ -471,32 +471,32 @@ function field_test_field_formatter_info() {
'behaviors' => array(
'multiple values' => FIELD_BEHAVIOR_CUSTOM,
),
- ),
- );
-}
-
-/**
- * Implementation of hook_theme().
- */
-function field_test_theme() {
- return array(
- 'field_formatter_field_test_default' => array(
- 'arguments' => array('element' => NULL),
- ),
- 'field_formatter_field_test_multiple' => array(
- 'arguments' => array('element' => NULL),
- ),
- );
-}
-
-/**
- * Theme function for 'field_test_default' formatter.
- */
+ ),
+ );
+}
+
+/**
+ * Implementation of hook_theme().
+ */
+function field_test_theme() {
+ return array(
+ 'field_formatter_field_test_default' => array(
+ 'arguments' => array('element' => NULL),
+ ),
+ 'field_formatter_field_test_multiple' => array(
+ 'arguments' => array('element' => NULL),
+ ),
+ );
+}
+
+/**
+ * Theme function for 'field_test_default' formatter.
+ */
function theme_field_formatter_field_test_default($element) {
$value = $element['#item']['value'];
$settings = $element['#settings'];
-
- return $settings['test_formatter_setting'] . '|' . $value;
+
+ return $settings['test_formatter_setting'] . '|' . $value;
}
/**
diff --git a/modules/user/user-profile.tpl.php b/modules/user/user-profile.tpl.php
index e290f8c8b..809b4c0f7 100644
--- a/modules/user/user-profile.tpl.php
+++ b/modules/user/user-profile.tpl.php
@@ -38,7 +38,7 @@
* Available variables:
* - $user_profile: All user profile data. Ready for print.
* - $profile: Keyed array of profile categories and their items or other data
- * provided by modules.
+ * provided by modules.
* - TODO D7 : document $FIELD_NAME_rendered variables.
*
* @see template_preprocess_user_profile()