diff options
Diffstat (limited to 'modules/field')
43 files changed, 0 insertions, 18762 deletions
diff --git a/modules/field/field.api.php b/modules/field/field.api.php deleted file mode 100644 index 74eae62ab50..00000000000 --- a/modules/field/field.api.php +++ /dev/null @@ -1,2635 +0,0 @@ -<?php - -/** - * @ingroup field_fieldable_type - * @{ - */ - -/** - * Exposes "pseudo-field" components on fieldable entities. - * - * Field UI's "Manage fields" and "Manage display" pages let users re-order - * fields, but also non-field components. For nodes, these include the title, - * poll choices, and other elements exposed by modules through hook_form() or - * hook_form_alter(). - * - * Fieldable entities or modules that want to have their components supported - * should expose them using this hook. The user-defined settings (weight, - * visible) are automatically applied on rendered forms and displayed - * entities in a #pre_render callback added by field_attach_form() and - * field_attach_view(). - * - * @see _field_extra_fields_pre_render() - * @see hook_field_extra_fields_alter() - * - * @return - * A nested array of 'pseudo-field' components. Each list is nested within - * the following keys: entity type, bundle name, context (either 'form' or - * 'display'). The keys are the name of the elements as appearing in the - * renderable array (either the entity form or the displayed entity). The - * value is an associative array: - * - label: The human readable name of the component. - * - description: A short description of the component contents. - * - weight: The default weight of the element. - */ -function hook_field_extra_fields() { - $extra['node']['poll'] = array( - 'form' => array( - 'choice_wrapper' => array( - 'label' => t('Poll choices'), - 'description' => t('Poll choices'), - 'weight' => -4, - ), - 'settings' => array( - 'label' => t('Poll settings'), - 'description' => t('Poll module settings'), - 'weight' => -3, - ), - ), - 'display' => array( - 'poll_view_voting' => array( - 'label' => t('Poll vote'), - 'description' => t('Poll vote'), - 'weight' => 0, - ), - 'poll_view_results' => array( - 'label' => t('Poll results'), - 'description' => t('Poll results'), - 'weight' => 0, - ), - ) - ); - - return $extra; -} - -/** - * Alter "pseudo-field" components on fieldable entities. - * - * @param $info - * The associative array of 'pseudo-field' components. - * - * @see hook_field_extra_fields() - */ -function hook_field_extra_fields_alter(&$info) { - // Force node title to always be at the top of the list by default. - foreach (node_type_get_types() as $bundle) { - if (isset($info['node'][$bundle->type]['title'])) { - $info['node'][$bundle->type]['title']['weight'] = -20; - } - } -} - -/** - * @} End of "ingroup field_fieldable_type" - */ - -/** - * @defgroup field_types Field Types API - * @{ - * Define field types, widget types, display formatter types, storage types. - * - * The bulk of the Field Types API are related to field types. A field type - * represents a particular type of data (integer, string, date, etc.) that - * can be attached to a fieldable entity. hook_field_info() defines the basic - * properties of a field type, and a variety of other field hooks are called by - * the Field Attach API to perform field-type-specific actions. - * - * @see hook_field_info() - * @see hook_field_info_alter() - * @see hook_field_schema() - * @see hook_field_load() - * @see hook_field_validate() - * @see hook_field_presave() - * @see hook_field_insert() - * @see hook_field_update() - * @see hook_field_delete() - * @see hook_field_delete_revision() - * @see hook_field_prepare_view() - * @see hook_field_is_empty() - * - * The Field Types API also defines two kinds of pluggable handlers: widgets - * and formatters, which specify how the field appears in edit forms and in - * displayed entities. Widgets and formatters can be implemented by a field-type - * module for its own field types, or by a third-party module to extend the - * behavior of existing field types. - * - * @see hook_field_widget_info() - * @see hook_field_formatter_info() - * - * A third kind of pluggable handlers, storage backends, is defined by the - * @link field_storage Field Storage API @endlink. - */ - -/** - * Define Field API field types. - * - * @return - * An array whose keys are field type names and whose values are arrays - * describing the field type, with the following key/value pairs: - * - label: The human-readable name of the field type. - * - description: A short description for the field type. - * - settings: An array whose keys are the names of the settings available - * for the field type, and whose values are the default values for those - * settings. - * - instance_settings: An array whose keys are the names of the settings - * available for instances of the field type, and whose values are the - * default values for those settings. Instance-level settings can have - * different values on each field instance, and thus allow greater - * flexibility than field-level settings. It is recommended to put settings - * at the instance level whenever possible. Notable exceptions: settings - * acting on the schema definition, or settings that Views needs to use - * across field instances (for example, the list of allowed values). - * - default_widget: The machine name of the default widget to be used by - * instances of this field type, when no widget is specified in the - * instance definition. This widget must be available whenever the field - * type is available (i.e. provided by the field type module, or by a module - * the field type module depends on). - * - default_formatter: The machine name of the default formatter to be used - * by instances of this field type, when no formatter is specified in the - * instance definition. This formatter must be available whenever the field - * type is available (i.e. provided by the field type module, or by a module - * the field type module depends on). - * - no_ui: (optional) A boolean specifying that users should not be allowed - * to create fields and instances of this field type through the UI. Such - * fields can only be created programmatically with field_create_field() - * and field_create_instance(). Defaults to FALSE. - * - * @see hook_field_info_alter() - */ -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', - ), - 'text_long' => array( - 'label' => t('Long text'), - 'description' => t('This field stores long text in the database.'), - 'settings' => array('max_length' => ''), - 'instance_settings' => array('text_processing' => 0), - 'default_widget' => 'text_textarea', - 'default_formatter' => 'text_default', - ), - 'text_with_summary' => array( - 'label' => t('Long text and summary'), - 'description' => t('This field stores long text in the database along with optional summary text.'), - 'settings' => array('max_length' => ''), - 'instance_settings' => array('text_processing' => 1, 'display_summary' => 0), - 'default_widget' => 'text_textarea_with_summary', - 'default_formatter' => 'text_summary_or_trimmed', - ), - ); -} - -/** - * Perform alterations on Field API field types. - * - * @param $info - * Array of information on field types exposed by hook_field_info() - * implementations. - */ -function hook_field_info_alter(&$info) { - // Add a setting to all field types. - foreach ($info as $field_type => $field_type_info) { - $info[$field_type]['settings'] += array( - 'mymodule_additional_setting' => 'default value', - ); - } - - // Change the default widget for fields of type 'foo'. - if (isset($info['foo'])) { - $info['foo']['default widget'] = 'mymodule_widget'; - } -} - -/** - * Define the Field API schema for a field structure. - * - * This hook MUST be defined in .install for it to be detected during - * installation and upgrade. - * - * @param $field - * A field structure. - * - * @return - * An associative array with the following keys: - * - columns: An array of Schema API column specifications, keyed by column - * name. This specifies what comprises a value for a given field. For - * example, a value for a number field is simply 'value', while a value for - * a formatted text field is the combination of 'value' and 'format'. It is - * recommended to avoid having the column definitions depend on field - * settings when possible. No assumptions should be made on how storage - * engines internally use the original column name to structure their - * storage. - * - indexes: (optional) An array of Schema API indexes definitions. Only - * columns that appear in the 'columns' array are allowed. Those indexes - * will be used as default indexes. Callers of field_create_field() can - * specify additional indexes, or, at their own risk, modify the default - * indexes specified by the field-type module. Some storage engines might - * not support indexes. - * - foreign keys: (optional) An array of Schema API foreign keys - * definitions. - */ -function hook_field_schema($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' => 'varchar', - 'length' => 255, - 'not null' => FALSE, - ), - ); - return array( - 'columns' => $columns, - 'indexes' => array( - 'format' => array('format'), - ), - 'foreign keys' => array( - 'format' => array( - 'table' => 'filter_format', - 'columns' => array('format' => 'format'), - ), - ), - ); -} - -/** - * Define custom load behavior for this module's field types. - * - * Unlike most other field hooks, this hook operates on multiple entities. The - * $entities, $instances and $items parameters are arrays keyed by entity ID. - * For performance reasons, information for all available entity should be - * loaded in a single query where possible. - * - * Note that the changes made to the field values get cached by the field cache - * for subsequent loads. You should never use this hook to load fieldable - * entities, since this is likely to cause infinite recursions when - * hook_field_load() is run on those as well. Use - * hook_field_formatter_prepare_view() instead. - * - * Make changes or additions to field values by altering the $items parameter by - * reference. There is no return value. - * - * @param $entity_type - * The type of $entity. - * @param $entities - * Array of entities being loaded, keyed by entity ID. - * @param $field - * The field structure for the operation. - * @param $instances - * Array of instance structures for $field for each entity, keyed by entity - * ID. - * @param $langcode - * The language code associated with $items. - * @param $items - * Array of field values already loaded for the entities, keyed by entity ID. - * Store your changes in this parameter (passed by reference). - * @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 entity. - */ -function hook_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) { - // Sample code from text.module: precompute sanitized strings so they are - // stored in the field cache. - foreach ($entities as $id => $entity) { - foreach ($items[$id] as $delta => $item) { - // Only process items with a cacheable format, the rest will be handled - // by formatters if needed. - if (empty($instances[$id]['settings']['text_processing']) || filter_format_allowcache($item['format'])) { - $items[$id][$delta]['safe_value'] = isset($item['value']) ? _text_sanitize($instances[$id], $langcode, $item, 'value') : ''; - if ($field['type'] == 'text_with_summary') { - $items[$id][$delta]['safe_summary'] = isset($item['summary']) ? _text_sanitize($instances[$id], $langcode, $item, 'summary') : ''; - } - } - } - } -} - -/** - * Prepare field values prior to display. - * - * This hook is invoked before the field values are handed to formatters - * for display, and runs before the formatters' own - * hook_field_formatter_prepare_view(). - * - * Unlike most other field hooks, this hook operates on multiple entities. The - * $entities, $instances and $items parameters are arrays keyed by entity ID. - * For performance reasons, information for all available entities should be - * loaded in a single query where possible. - * - * Make changes or additions to field values by altering the $items parameter by - * reference. There is no return value. - * - * @param $entity_type - * The type of $entity. - * @param $entities - * Array of entities being displayed, keyed by entity ID. - * @param $field - * The field structure for the operation. - * @param $instances - * Array of instance structures for $field for each entity, keyed by entity - * ID. - * @param $langcode - * The language associated to $items. - * @param $items - * $entity->{$field['field_name']}, or an empty array if unset. - */ -function hook_field_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items) { - // Sample code from image.module: if there are no images specified at all, - // use the default image. - foreach ($entities as $id => $entity) { - if (empty($items[$id]) && $field['settings']['default_image']) { - if ($file = file_load($field['settings']['default_image'])) { - $items[$id][0] = (array) $file + array( - 'is_default' => TRUE, - 'alt' => '', - 'title' => '', - ); - } - } - } -} - -/** - * Validate this module's field data. - * - * If there are validation problems, add to the $errors array (passed by - * reference). There is no return value. - * - * @param $entity_type - * The type of $entity. - * @param $entity - * The entity for the operation. - * @param $field - * The field structure for the operation. - * @param $instance - * The instance structure for $field on $entity's bundle. - * @param $langcode - * The language associated with $items. - * @param $items - * $entity->{$field['field_name']}[$langcode], or an empty array if unset. - * @param $errors - * The array of errors (keyed by field name, language code, and delta) that - * have already been reported for the entity. The function should add its - * errors to this array. Each error is an associative array with the following - * keys and values: - * - error: An error code (should be a string prefixed with the module name). - * - message: The human readable message to be displayed. - */ -function hook_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) { - foreach ($items as $delta => $item) { - if (!empty($item['value'])) { - if (!empty($field['settings']['max_length']) && drupal_strlen($item['value']) > $field['settings']['max_length']) { - $errors[$field['field_name']][$langcode][$delta][] = array( - 'error' => 'text_max_length', - 'message' => t('%name: the value may not be longer than %max characters.', array('%name' => $instance['label'], '%max' => $field['settings']['max_length'])), - ); - } - } - } -} - -/** - * Define custom presave behavior for this module's field types. - * - * Make changes or additions to field values by altering the $items parameter by - * reference. There is no return value. - * - * @param $entity_type - * The type of $entity. - * @param $entity - * The entity for the operation. - * @param $field - * The field structure for the operation. - * @param $instance - * The instance structure for $field on $entity's bundle. - * @param $langcode - * The language associated with $items. - * @param $items - * $entity->{$field['field_name']}[$langcode], or an empty array if unset. - */ -function hook_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) { - if ($field['type'] == 'number_decimal') { - // Let PHP round the value to ensure consistent behavior across storage - // backends. - foreach ($items as $delta => $item) { - if (isset($item['value'])) { - $items[$delta]['value'] = round($item['value'], $field['settings']['scale']); - } - } - } -} - -/** - * Define custom insert behavior for this module's field types. - * - * Invoked from field_attach_insert(). - * - * @param $entity_type - * The type of $entity. - * @param $entity - * The entity for the operation. - * @param $field - * The field structure for the operation. - * @param $instance - * The instance structure for $field on $entity's bundle. - * @param $langcode - * The language associated with $items. - * @param $items - * $entity->{$field['field_name']}[$langcode], or an empty array if unset. - */ -function hook_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) { - if (variable_get('taxonomy_maintain_index_table', TRUE) && $field['storage']['type'] == 'field_sql_storage' && $entity_type == 'node' && $entity->status) { - $query = db_insert('taxonomy_index')->fields(array('nid', 'tid', 'sticky', 'created', )); - foreach ($items as $item) { - $query->values(array( - 'nid' => $entity->nid, - 'tid' => $item['tid'], - 'sticky' => $entity->sticky, - 'created' => $entity->created, - )); - } - $query->execute(); - } -} - -/** - * Define custom update behavior for this module's field types. - * - * Invoked from field_attach_update(). - * - * @param $entity_type - * The type of $entity. - * @param $entity - * The entity for the operation. - * @param $field - * The field structure for the operation. - * @param $instance - * The instance structure for $field on $entity's bundle. - * @param $langcode - * The language associated with $items. - * @param $items - * $entity->{$field['field_name']}[$langcode], or an empty array if unset. - */ -function hook_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) { - if (variable_get('taxonomy_maintain_index_table', TRUE) && $field['storage']['type'] == 'field_sql_storage' && $entity_type == 'node') { - $first_call = &drupal_static(__FUNCTION__, array()); - - // We don't maintain data for old revisions, so clear all previous values - // from the table. Since this hook runs once per field, per object, make - // sure we only wipe values once. - if (!isset($first_call[$entity->nid])) { - $first_call[$entity->nid] = FALSE; - db_delete('taxonomy_index')->condition('nid', $entity->nid)->execute(); - } - // Only save data to the table if the node is published. - if ($entity->status) { - $query = db_insert('taxonomy_index')->fields(array('nid', 'tid', 'sticky', 'created')); - foreach ($items as $item) { - $query->values(array( - 'nid' => $entity->nid, - 'tid' => $item['tid'], - 'sticky' => $entity->sticky, - 'created' => $entity->created, - )); - } - $query->execute(); - } - } -} - -/** - * Update the storage information for a field. - * - * This is invoked on the field's storage module from field_update_field(), - * before the new field information is saved to the database. The field storage - * module should update its storage tables to agree with the new field - * information. If there is a problem, the field storage module should throw an - * exception. - * - * @param $field - * The updated field structure to be saved. - * @param $prior_field - * The previously-saved field structure. - * @param $has_data - * TRUE if the field has data in storage currently. - */ -function hook_field_storage_update_field($field, $prior_field, $has_data) { - if (!$has_data) { - // There is no data. Re-create the tables completely. - $prior_schema = _field_sql_storage_schema($prior_field); - foreach ($prior_schema as $name => $table) { - db_drop_table($name, $table); - } - $schema = _field_sql_storage_schema($field); - foreach ($schema as $name => $table) { - db_create_table($name, $table); - } - } - else { - // There is data. See field_sql_storage_field_storage_update_field() for - // an example of what to do to modify the schema in place, preserving the - // old data as much as possible. - } - drupal_get_schema(NULL, TRUE); -} - -/** - * Define custom delete behavior for this module's field types. - * - * This hook is invoked just before the data is deleted from field storage - * in field_attach_delete(). - * - * @param $entity_type - * The type of $entity. - * @param $entity - * The entity for the operation. - * @param $field - * The field structure for the operation. - * @param $instance - * The instance structure for $field on $entity's bundle. - * @param $langcode - * The language associated with $items. - * @param $items - * $entity->{$field['field_name']}[$langcode], or an empty array if unset. - */ -function hook_field_delete($entity_type, $entity, $field, $instance, $langcode, &$items) { - list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); - foreach ($items as $delta => $item) { - // For hook_file_references(), remember that this is being deleted. - $item['file_field_name'] = $field['field_name']; - // Pass in the ID of the object that is being removed so all references can - // be counted in hook_file_references(). - $item['file_field_type'] = $entity_type; - $item['file_field_id'] = $id; - file_field_delete_file($item, $field); - } -} - -/** - * Define custom revision delete behavior for this module's field types. - * - * This hook is invoked just before the data is deleted from field storage - * in field_attach_delete_revision(), and will only be called for fieldable - * types that are versioned. - * - * @param $entity_type - * The type of $entity. - * @param $entity - * The entity for the operation. - * @param $field - * The field structure for the operation. - * @param $instance - * The instance structure for $field on $entity's bundle. - * @param $langcode - * The language associated with $items. - * @param $items - * $entity->{$field['field_name']}[$langcode], or an empty array if unset. - */ -function hook_field_delete_revision($entity_type, $entity, $field, $instance, $langcode, &$items) { - foreach ($items as $delta => $item) { - // For hook_file_references, remember that this file is being deleted. - $item['file_field_name'] = $field['field_name']; - if (file_field_delete_file($item, $field)) { - $items[$delta] = NULL; - } - } -} - -/** - * Define custom prepare_translation behavior for this module's field types. - * - * @param $entity_type - * The type of $entity. - * @param $entity - * The entity for the operation. - * @param $field - * The field structure for the operation. - * @param $instance - * The instance structure for $field on $entity's bundle. - * @param $langcode - * The language associated to $items. - * @param $items - * $entity->{$field['field_name']}[$langcode], or an empty array if unset. - * @param $source_entity - * The source entity from which field values are being copied. - * @param $source_langcode - * The source language from which field values are being copied. - */ -function hook_field_prepare_translation($entity_type, $entity, $field, $instance, $langcode, &$items, $source_entity, $source_langcode) { - // If the translating user is not permitted to use the assigned text format, - // we must not expose the source values. - $field_name = $field['field_name']; - $formats = filter_formats(); - $format_id = $source_entity->{$field_name}[$source_langcode][0]['format']; - if (!filter_access($formats[$format_id])) { - $items = array(); - } -} - -/** - * Define what constitutes an empty item for a field type. - * - * @param $item - * An item that may or may not be empty. - * @param $field - * The field to which $item belongs. - * - * @return - * TRUE if $field's type considers $item not to contain any data; - * FALSE otherwise. - */ -function hook_field_is_empty($item, $field) { - if (empty($item['value']) && (string) $item['value'] !== '0') { - return TRUE; - } - return FALSE; -} - -/** - * Expose Field API widget types. - * - * Widgets are Form API elements with additional processing capabilities. - * Widget hooks are typically called by the Field Attach API during the - * creation of the field form structure with field_attach_form(). - * - * @return - * An array describing the widget types implemented by the module. - * The keys are widget type names. To avoid name clashes, widget type - * names should be prefixed with the name of the module that exposes them. - * The values are arrays describing the widget type, with the following - * key/value pairs: - * - label: The human-readable name of the widget type. - * - description: A short description for the widget type. - * - field types: An array of field types the widget supports. - * - settings: An array whose keys are the names of the settings available - * for the widget type, and whose values are the default values for those - * settings. - * - behaviors: (optional) An array describing behaviors of the widget, with - * the following elements: - * - multiple values: One of the following constants: - * - FIELD_BEHAVIOR_DEFAULT: (default) If the widget allows the input of - * one single field value (most common case). The widget will be - * repeated for each value input. - * - FIELD_BEHAVIOR_CUSTOM: If one single copy of the widget can receive - * several field values. Examples: checkboxes, multiple select, - * comma-separated textfield. - * - default value: One of the following constants: - * - FIELD_BEHAVIOR_DEFAULT: (default) If the widget accepts default - * values. - * - FIELD_BEHAVIOR_NONE: if the widget does not support default values. - * - * @see hook_field_widget_info_alter() - * @see hook_field_widget_form() - * @see hook_field_widget_form_alter() - * @see hook_field_widget_WIDGET_TYPE_form_alter() - * @see hook_field_widget_error() - */ -function hook_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, - ), - ), - 'text_textarea_with_summary' => array( - 'label' => t('Text area with a summary'), - 'field types' => array('text_with_summary'), - 'settings' => array('rows' => 20, 'summary_rows' => 5), - 'behaviors' => array( - 'multiple values' => FIELD_BEHAVIOR_DEFAULT, - 'default value' => FIELD_BEHAVIOR_DEFAULT, - ), - ), - ); -} - -/** - * Perform alterations on Field API widget types. - * - * @param $info - * Array of informations on widget types exposed by hook_field_widget_info() - * implementations. - */ -function hook_field_widget_info_alter(&$info) { - // Add a setting to a widget type. - $info['text_textfield']['settings'] += array( - 'mymodule_additional_setting' => 'default value', - ); - - // Let a new field type re-use an existing widget. - $info['options_select']['field types'][] = 'my_field_type'; -} - -/** - * Return the form for a single field widget. - * - * Field widget form elements should be based on the passed-in $element, which - * contains the base form element properties derived from the field - * configuration. - * - * 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 - * invoke this hook as many times as needed. - * - * Note that, depending on the context in which the widget is being included - * (regular entity form, field configuration form, advanced search form...), - * the values for $field and $instance might be different from the "official" - * definitions returned by field_info_field() and field_info_instance(). - * Examples: mono-value widget even if the field is multi-valued, non-required - * widget even if the field is 'required'... - * - * Therefore, the FAPI element callbacks (such as #process, #element_validate, - * #value_callback...) used by the widget cannot use the field_info_field() - * or field_info_instance() functions to retrieve the $field or $instance - * definitions they should operate on. The field_widget_field() and - * field_widget_instance() functions should be used instead to fetch the - * current working definitions from $form_state, where Field API stores them. - * - * Alternatively, hook_field_widget_form() can extract the needed specific - * properties from $field and $instance and set them as ad-hoc - * $element['#custom'] properties, for later use by its element callbacks. - * - * Other modules may alter the form element provided by this function using - * hook_field_widget_form_alter(). - * - * @param $form - * The form structure where widgets are being attached to. This might be a - * full form structure, or a sub-element of a larger form. - * @param $form_state - * An associative array containing the current state of the form. - * @param $field - * The field structure. - * @param $instance - * The field instance. - * @param $langcode - * The language associated with $items. - * @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). - * @param $element - * A form element array containing basic properties for the widget: - * - #entity_type: The name of the entity the field is attached to. - * - #bundle: The name of the field bundle the field is contained in. - * - #field_name: The name of the field. - * - #language: The language the field is being edited in. - * - #field_parents: The 'parents' space for the field in the form. Most - * widgets can simply overlook this property. This identifies the - * location where the field values are placed within - * $form_state['values'], and is used to access processing information - * for the field through the field_form_get_state() and - * field_form_set_state() functions. - * - #columns: A list of field storage columns of the field. - * - #title: The sanitized element label for the field instance, ready for - * output. - * - #description: The sanitized element description for the field instance, - * ready for output. - * - #required: A Boolean indicating whether the element value is required; - * for required multiple value fields, only the first widget's values are - * required. - * - #delta: The order of this item in the array of subelements; see $delta - * above. - * - * @return - * The form elements for a single widget for this field. - * - * @see field_widget_field() - * @see field_widget_instance() - * @see hook_field_widget_form_alter() - * @see hook_field_widget_WIDGET_TYPE_form_alter() - */ -function hook_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { - $element += array( - '#type' => $instance['widget']['type'], - '#default_value' => isset($items[$delta]) ? $items[$delta] : '', - ); - return $element; -} - -/** - * Alter forms for field widgets provided by other modules. - * - * @param $element - * The field widget form element as constructed by hook_field_widget_form(). - * @param $form_state - * An associative array containing the current state of the form. - * @param $context - * An associative array containing the following key-value pairs, matching the - * arguments received by hook_field_widget_form(): - * - "form": The form structure where widgets are being attached to. This - * might be a full form structure, or a sub-element of a larger form. - * - "field": The field structure. - * - "instance": The field instance structure. - * - "langcode": The language associated with $items. - * - "items": Array of default values for this field. - * - "delta": The order of this item in the array of subelements (0, 1, 2, - * etc). - * - * @see hook_field_widget_form() - * @see hook_field_widget_WIDGET_TYPE_form_alter - */ -function hook_field_widget_form_alter(&$element, &$form_state, $context) { - // Add a css class to widget form elements for all fields of type mytype. - if ($context['field']['type'] == 'mytype') { - // Be sure not to overwrite existing attributes. - $element['#attributes']['class'][] = 'myclass'; - } -} - -/** - * Alter widget forms for a specific widget provided by another module. - * - * Modules can implement hook_field_widget_WIDGET_TYPE_form_alter() to modify a - * specific widget form, rather than using hook_field_widget_form_alter() and - * checking the widget type. - * - * @param $element - * The field widget form element as constructed by hook_field_widget_form(). - * @param $form_state - * An associative array containing the current state of the form. - * @param $context - * An associative array containing the following key-value pairs, matching the - * arguments received by hook_field_widget_form(): - * - "form": The form structure where widgets are being attached to. This - * might be a full form structure, or a sub-element of a larger form. - * - "field": The field structure. - * - "instance": The field instance structure. - * - "langcode": The language associated with $items. - * - "items": Array of default values for this field. - * - "delta": The order of this item in the array of subelements (0, 1, 2, - * etc). - * - * @see hook_field_widget_form() - * @see hook_field_widget_form_alter() - */ -function hook_field_widget_WIDGET_TYPE_form_alter(&$element, &$form_state, $context) { - // Code here will only act on widgets of type WIDGET_TYPE. For example, - // hook_field_widget_mymodule_autocomplete_form_alter() will only act on - // widgets of type 'mymodule_autocomplete'. - $element['#autocomplete_path'] = 'mymodule/autocomplete_path'; -} - -/** - * Flag a field-level validation error. - * - * @param $element - * An array containing the form element for the widget. The error needs to be - * flagged on the right sub-element, according to the widget's internal - * structure. - * @param $error - * An associative array with the following key-value pairs, as returned by - * hook_field_validate(): - * - error: the error code. Complex widgets might need to report different - * errors to different form elements inside the widget. - * - message: the human readable message to be displayed. - * @param $form - * The form structure where field elements are attached to. This might be a - * full form structure, or a sub-element of a larger form. - * @param $form_state - * An associative array containing the current state of the form. - */ -function hook_field_widget_error($element, $error, $form, &$form_state) { - form_error($element['value'], $error['message']); -} - -/** - * Expose Field API formatter types. - * - * Formatters handle the display of field values. Formatter hooks are typically - * called by the Field Attach API field_attach_prepare_view() and - * field_attach_view() functions. - * - * @return - * An array describing the formatter types implemented by the module. - * The keys are formatter type names. To avoid name clashes, formatter type - * names should be prefixed with the name of the module that exposes them. - * The values are arrays describing the formatter type, with the following - * key/value pairs: - * - label: The human-readable name of the formatter type. - * - description: A short description for the formatter type. - * - field types: An array of field types the formatter supports. - * - settings: An array whose keys are the names of the settings available - * for the formatter type, and whose values are the default values for - * those settings. - * - * @see hook_field_formatter_info_alter() - * @see hook_field_formatter_view() - * @see hook_field_formatter_prepare_view() - */ -function hook_field_formatter_info() { - return array( - 'text_default' => array( - 'label' => t('Default'), - 'field types' => array('text', 'text_long', 'text_with_summary'), - ), - 'text_plain' => array( - 'label' => t('Plain text'), - 'field types' => array('text', 'text_long', 'text_with_summary'), - ), - - // The text_trimmed formatter displays the trimmed version of the - // full element of the field. It is intended to be used with text - // and text_long fields. It also works with text_with_summary - // fields though the text_summary_or_trimmed formatter makes more - // sense for that field type. - 'text_trimmed' => array( - 'label' => t('Trimmed'), - 'field types' => array('text', 'text_long', 'text_with_summary'), - ), - - // The 'summary or trimmed' field formatter for text_with_summary - // fields displays returns the summary element of the field or, if - // the summary is empty, the trimmed version of the full element - // of the field. - 'text_summary_or_trimmed' => array( - 'label' => t('Summary or trimmed'), - 'field types' => array('text_with_summary'), - ), - ); -} - -/** - * Perform alterations on Field API formatter types. - * - * @param $info - * Array of informations on formatter types exposed by - * hook_field_field_formatter_info() implementations. - */ -function hook_field_formatter_info_alter(&$info) { - // Add a setting to a formatter type. - $info['text_default']['settings'] += array( - 'mymodule_additional_setting' => 'default value', - ); - - // Let a new field type re-use an existing formatter. - $info['text_default']['field types'][] = 'my_field_type'; -} - -/** - * Allow formatters to load information for field values being displayed. - * - * This should be used when a formatter needs to load additional information - * from the database in order to render a field, for example a reference field - * which displays properties of the referenced entities such as name or type. - * - * This hook is called after the field type's own hook_field_prepare_view(). - * - * Unlike most other field hooks, this hook operates on multiple entities. The - * $entities, $instances and $items parameters are arrays keyed by entity ID. - * For performance reasons, information for all available entities should be - * loaded in a single query where possible. - * - * @param $entity_type - * The type of $entity. - * @param $entities - * Array of entities being displayed, keyed by entity ID. - * @param $field - * The field structure for the operation. - * @param $instances - * Array of instance structures for $field for each entity, keyed by entity - * ID. - * @param $langcode - * The language the field values are to be shown in. If no language is - * provided the current language is used. - * @param $items - * Array of field values for the entities, keyed by entity ID. - * @param $displays - * Array of display settings to use for each entity, keyed by entity ID. - * - * @return - * Changes or additions to field values are done by altering the $items - * parameter by reference. - */ -function hook_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) { - $tids = array(); - - // Collect every possible term attached to any of the fieldable entities. - foreach ($entities as $id => $entity) { - foreach ($items[$id] as $delta => $item) { - // Force the array key to prevent duplicates. - $tids[$item['tid']] = $item['tid']; - } - } - - if ($tids) { - $terms = taxonomy_term_load_multiple($tids); - - // Iterate through the fieldable entities again to attach the loaded term - // data. - foreach ($entities as $id => $entity) { - $rekey = FALSE; - - foreach ($items[$id] as $delta => $item) { - // Check whether the taxonomy term field instance value could be loaded. - if (isset($terms[$item['tid']])) { - // Replace the instance value with the term data. - $items[$id][$delta]['taxonomy_term'] = $terms[$item['tid']]; - } - // Otherwise, unset the instance value, since the term does not exist. - else { - unset($items[$id][$delta]); - $rekey = TRUE; - } - } - - if ($rekey) { - // Rekey the items array. - $items[$id] = array_values($items[$id]); - } - } - } -} - -/** - * Build a renderable array for a field value. - * - * @param $entity_type - * The type of $entity. - * @param $entity - * The entity being displayed. - * @param $field - * The field structure. - * @param $instance - * The field instance. - * @param $langcode - * The language associated with $items. - * @param $items - * Array of values for this field. - * @param $display - * The display settings to use, as found in the 'display' entry of instance - * definitions. The array notably contains the following keys and values; - * - type: The name of the formatter to use. - * - settings: The array of formatter settings. - * - * @return - * A renderable array for the $items, as an array of child elements keyed - * by numeric indexes starting from 0. - */ -function hook_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) { - $element = array(); - $settings = $display['settings']; - - switch ($display['type']) { - case 'sample_field_formatter_simple': - // Common case: each value is displayed individually in a sub-element - // keyed by delta. The field.tpl.php template specifies the markup - // wrapping each value. - foreach ($items as $delta => $item) { - $element[$delta] = array('#markup' => $settings['some_setting'] . $item['value']); - } - break; - - case 'sample_field_formatter_themeable': - // More elaborate formatters can defer to a theme function for easier - // customization. - foreach ($items as $delta => $item) { - $element[$delta] = array( - '#theme' => 'mymodule_theme_sample_field_formatter_themeable', - '#data' => $item['value'], - '#some_setting' => $settings['some_setting'], - ); - } - break; - - case 'sample_field_formatter_combined': - // Some formatters might need to display all values within a single piece - // of markup. - $rows = array(); - foreach ($items as $delta => $item) { - $rows[] = array($delta, $item['value']); - } - $element[0] = array( - '#theme' => 'table', - '#header' => array(t('Delta'), t('Value')), - '#rows' => $rows, - ); - break; - } - - 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. - * Implementing modules should alter the $form or $form_state parameters. - * - * @param $entity_type - * The type of $entity; for example, 'node' or 'user'. - * @param $entity - * The entity for which an edit form is being built. - * @param $form - * The form structure where field elements are attached to. This might be a - * full form structure, or a sub-element of a larger form. The - * $form['#parents'] property can be used to identify the corresponding part - * of $form_state['values']. Hook implementations that need to act on the - * top-level properties of the global form (like #submit, #validate...) can - * add a #process callback to the array received in the $form parameter, and - * act on the $complete_form parameter in the process callback. - * @param $form_state - * An associative array containing the current state of the form. - * @param $langcode - * The language the field values are going to be entered in. If no language - * is provided the default site language will be used. - */ -function hook_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) { - // Add a checkbox allowing a given field to be emptied. - // See hook_field_attach_submit() for the corresponding processing code. - $form['empty_field_foo'] = array( - '#type' => 'checkbox', - '#title' => t("Empty the 'field_foo' field"), - ); -} - -/** - * Act on field_attach_load(). - * - * This hook is invoked after the field module has performed the operation. - * - * Unlike other field_attach hooks, this hook accounts for 'multiple loads'. - * Instead of the usual $entity parameter, it accepts an array of entities, - * indexed by entity ID. For performance reasons, information for all available - * entities should be loaded in a single query where possible. - * - * The changes made to the entities' field values get cached by the field cache - * for subsequent loads. - * - * See field_attach_load() for details and arguments. - */ -function hook_field_attach_load($entity_type, $entities, $age, $options) { - // @todo Needs function body. -} - -/** - * 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($entity_type, $entity, &$errors) { - // @todo Needs function body. -} - -/** - * Act on field_attach_submit(). - * - * This hook is invoked after the field module has performed the operation. - * - * @param $entity_type - * The type of $entity; for example, 'node' or 'user'. - * @param $entity - * The entity for which an edit form is being submitted. The incoming form - * values have been extracted as field values of the $entity object. - * @param $form - * The form structure where field elements are attached to. This might be a - * full form structure, or a sub-part of a larger form. The $form['#parents'] - * property can be used to identify the corresponding part of - * $form_state['values']. - * @param $form_state - * An associative array containing the current state of the form. - */ -function hook_field_attach_submit($entity_type, $entity, $form, &$form_state) { - // Sample case of an 'Empty the field' checkbox added on the form, allowing - // a given field to be emptied. - $values = drupal_array_get_nested_value($form_state['values'], $form['#parents']); - if (!empty($values['empty_field_foo'])) { - unset($entity->field_foo); - } -} - -/** - * 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($entity_type, $entity) { - // @todo Needs function body. -} - -/** - * 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($entity_type, $entity) { - // @todo Needs function body. -} - -/** - * 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($entity_type, $entity) { - // @todo Needs function body. -} - -/** - * Alter field_attach_preprocess() variables. - * - * This hook is invoked while preprocessing the field.tpl.php template file - * in field_attach_preprocess(). - * - * @param $variables - * The variables array is passed by reference and will be populated with field - * values. - * @param $context - * An associative array containing: - * - entity_type: The type of $entity; for example, 'node' or 'user'. - * - entity: The entity with fields to render. - * - element: The structured array containing the values ready for rendering. - */ -function hook_field_attach_preprocess_alter(&$variables, $context) { - // @todo Needs function body. -} - -/** - * 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($entity_type, $entity) { - // @todo Needs function body. -} - -/** - * 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($entity_type, $entity) { - // @todo Needs function body. -} - -/** - * Act on field_purge_data(). - * - * This hook is invoked in field_purge_data() and allows modules to act on - * purging data from a single field pseudo-entity. For example, if a module - * relates data in the field with its own data, it may purge its own data - * during this process as well. - * - * @param $entity_type - * The type of $entity; for example, 'node' or 'user'. - * @param $entity - * The pseudo-entity whose field data is being purged. - * @param $field - * The (possibly deleted) field whose data is being purged. - * @param $instance - * The deleted field instance whose data is being purged. - * - * @see @link field_purge Field API bulk data deletion @endlink - * @see field_purge_data() - */ -function hook_field_attach_purge($entity_type, $entity, $field, $instance) { - // find the corresponding data in mymodule and purge it - if ($entity_type == 'node' && $field->field_name == 'my_field_name') { - mymodule_remove_mydata($entity->nid); - } -} - -/** - * Perform alterations on field_attach_view() or field_view_field(). - * - * This hook is invoked after the field module has performed the operation. - * - * @param $output - * The structured content array tree for all of the entity's fields. - * @param $context - * An associative array containing: - * - entity_type: The type of $entity; for example, 'node' or 'user'. - * - entity: The entity with fields to render. - * - view_mode: View mode; for example, 'full' or 'teaser'. - * - display: Either a view mode string or an array of display settings. If - * this hook is being invoked from field_attach_view(), the 'display' - * element is set to the view mode string. If this hook is being invoked - * from field_view_field(), this element is set to the $display argument - * and the view_mode element is set to '_custom'. See field_view_field() - * for more information on what its $display argument contains. - * - language: The language code used for rendering. - */ -function hook_field_attach_view_alter(&$output, $context) { - // Append RDF term mappings on displayed taxonomy links. - foreach (element_children($output) as $field_name) { - $element = &$output[$field_name]; - if ($element['#field_type'] == 'taxonomy_term_reference' && $element['#formatter'] == 'taxonomy_term_reference_link') { - foreach ($element['#items'] as $delta => $item) { - $term = $item['taxonomy_term']; - if (!empty($term->rdf_mapping['rdftype'])) { - $element[$delta]['#options']['attributes']['typeof'] = $term->rdf_mapping['rdftype']; - } - if (!empty($term->rdf_mapping['name']['predicates'])) { - $element[$delta]['#options']['attributes']['property'] = $term->rdf_mapping['name']['predicates']; - } - } - } - } -} - -/** - * Perform alterations on field_attach_prepare_translation(). - * - * This hook is invoked after the field module has performed the operation. - * - * @param $entity - * The entity being prepared for translation. - * @param $context - * An associative array containing: - * - entity_type: The type of $entity; e.g. 'node' or 'user'. - * - langcode: The language the entity has to be translated in. - * - source_entity: The entity holding the field values to be translated. - * - source_langcode: The source language from which translate. - */ -function hook_field_attach_prepare_translation_alter(&$entity, $context) { - if ($context['entity_type'] == 'custom_entity_type') { - $entity->custom_field = $context['source_entity']->custom_field; - } -} - -/** - * Perform alterations on field_language() values. - * - * This hook is invoked to alter the array of display languages for the given - * entity. - * - * @param $display_language - * A reference to an array of language codes keyed by field name. - * @param $context - * An associative array containing: - * - entity_type: The type of the entity to be displayed. - * - entity: The entity with fields to render. - * - langcode: The language code $entity has to be displayed in. - */ -function hook_field_language_alter(&$display_language, $context) { - // Do not apply core language fallback rules if they are disabled or if Locale - // is not registered as a translation handler. - if (variable_get('locale_field_language_fallback', TRUE) && field_has_translation_handler($context['entity_type'], 'locale')) { - locale_field_language_fallback($display_language, $context['entity'], $context['language']); - } -} - -/** - * Alter field_available_languages() values. - * - * This hook is invoked from field_available_languages() to allow modules to - * alter the array of available languages for the given field. - * - * @param $languages - * A reference to an array of language codes to be made available. - * @param $context - * An associative array containing: - * - entity_type: The type of the entity the field is attached to. - * - field: A field data structure. - */ -function hook_field_available_languages_alter(&$languages, $context) { - // Add an unavailable language. - $languages[] = 'xx'; - - // Remove an available language. - $index = array_search('yy', $languages); - unset($languages[$index]); -} - -/** - * 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($entity_type, $bundle) { - // When a new bundle is created, the menu needs to be rebuilt to add the - // Field UI menu item tabs. - variable_set('menu_rebuild_needed', TRUE); -} - -/** - * 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_attach_rename_bundle($entity_type, $bundle_old, $bundle_new) { - // Update the extra weights variable with new information. - if ($bundle_old !== $bundle_new) { - $extra_weights = variable_get('field_extra_weights', array()); - if (isset($info[$entity_type][$bundle_old])) { - $extra_weights[$entity_type][$bundle_new] = $extra_weights[$entity_type][$bundle_old]; - unset($extra_weights[$entity_type][$bundle_old]); - variable_set('field_extra_weights', $extra_weights); - } - } -} - -/** - * Act on field_attach_delete_bundle. - * - * This hook is invoked after the field module has performed the operation. - * - * @param $entity_type - * The type of entity; for example, 'node' or 'user'. - * @param $bundle - * The bundle that was just deleted. - * @param $instances - * An array of all instances that existed for the bundle before it was - * deleted. - */ -function hook_field_attach_delete_bundle($entity_type, $bundle, $instances) { - // Remove the extra weights variable information for this bundle. - $extra_weights = variable_get('field_extra_weights', array()); - if (isset($extra_weights[$entity_type][$bundle])) { - unset($extra_weights[$entity_type][$bundle]); - variable_set('field_extra_weights', $extra_weights); - } -} - -/** - * @} End of "ingroup field_attach" - */ - -/********************************************************************** - * Field Storage API - **********************************************************************/ - -/** - * @ingroup field_storage - * @{ - */ - -/** - * Expose Field API storage backends. - * - * @return - * An array describing the storage backends implemented by the module. - * The keys are storage backend names. To avoid name clashes, storage backend - * names should be prefixed with the name of the module that exposes them. - * The values are arrays describing the storage backend, with the following - * key/value pairs: - * - label: The human-readable name of the storage backend. - * - description: A short description for the storage backend. - * - settings: An array whose keys are the names of the settings available - * for the storage backend, and whose values are the default values for - * those settings. - */ -function hook_field_storage_info() { - return array( - 'field_sql_storage' => array( - 'label' => t('Default SQL storage'), - 'description' => t('Stores fields in the local SQL database, using per-field tables.'), - 'settings' => array(), - ), - ); -} - -/** - * Perform alterations on Field API storage types. - * - * @param $info - * Array of informations on storage types exposed by - * hook_field_field_storage_info() implementations. - */ -function hook_field_storage_info_alter(&$info) { - // Add a setting to a storage type. - $info['field_sql_storage']['settings'] += array( - 'mymodule_additional_setting' => 'default value', - ); -} - -/** - * Reveal the internal details about the storage for a field. - * - * For example, an SQL storage module might return the Schema API structure for - * the table. A key/value storage module might return the server name, - * authentication credentials, and bin name. - * - * Field storage modules are not obligated to implement this hook. Modules - * that rely on these details must only use them for read operations. - * - * @param $field - * A field structure. - * - * @return - * An array of details. - * - The first dimension is a store type (sql, solr, etc). - * - The second dimension indicates the age of the values in the store - * FIELD_LOAD_CURRENT or FIELD_LOAD_REVISION. - * - Other dimensions are specific to the field storage module. - * - * @see hook_field_storage_details_alter() - */ -function hook_field_storage_details($field) { - $details = array(); - - // Add field columns. - foreach ((array) $field['columns'] as $column_name => $attributes) { - $real_name = _field_sql_storage_columnname($field['field_name'], $column_name); - $columns[$column_name] = $real_name; - } - return array( - 'sql' => array( - FIELD_LOAD_CURRENT => array( - _field_sql_storage_tablename($field) => $columns, - ), - FIELD_LOAD_REVISION => array( - _field_sql_storage_revision_tablename($field) => $columns, - ), - ), - ); -} - -/** - * Perform alterations on Field API storage details. - * - * @param $details - * An array of storage details for fields as exposed by - * hook_field_storage_details() implementations. - * @param $field - * A field structure. - * - * @see hook_field_storage_details() - */ -function hook_field_storage_details_alter(&$details, $field) { - if ($field['field_name'] == 'field_of_interest') { - $columns = array(); - foreach ((array) $field['columns'] as $column_name => $attributes) { - $columns[$column_name] = $column_name; - } - $details['drupal_variables'] = array( - FIELD_LOAD_CURRENT => array( - 'moon' => $columns, - ), - FIELD_LOAD_REVISION => array( - 'mars' => $columns, - ), - ); - } -} - -/** - * Load field data for a set of entities. - * - * This hook is invoked from field_attach_load() to ask the field storage - * module to load field data. - * - * Modules implementing this hook should load field values and add them to - * objects in $entities. Fields with no values should be added as empty - * arrays. - * - * @param $entity_type - * The type of entity, such as 'node' or 'user'. - * @param $entities - * The array of entity objects to add fields to, keyed by entity ID. - * @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 entity. - * @param $fields - * An array listing the fields to be loaded. The keys of the array are field - * IDs, and the values of the array are the entity IDs (or revision IDs, - * depending on the $age parameter) to add each field to. - * @param $options - * An associative array of additional options, with the following keys: - * - deleted: If TRUE, deleted fields should be loaded as well as - * non-deleted fields. If unset or FALSE, only non-deleted fields should be - * loaded. - */ -function hook_field_storage_load($entity_type, $entities, $age, $fields, $options) { - $field_info = field_info_field_by_ids(); - $load_current = $age == FIELD_LOAD_CURRENT; - - foreach ($fields as $field_id => $ids) { - $field = $field_info[$field_id]; - $field_name = $field['field_name']; - $table = $load_current ? _field_sql_storage_tablename($field) : _field_sql_storage_revision_tablename($field); - - $query = db_select($table, 't') - ->fields('t') - ->condition('entity_type', $entity_type) - ->condition($load_current ? 'entity_id' : 'revision_id', $ids, 'IN') - ->condition('language', field_available_languages($entity_type, $field), 'IN') - ->orderBy('delta'); - - if (empty($options['deleted'])) { - $query->condition('deleted', 0); - } - - $results = $query->execute(); - - $delta_count = array(); - foreach ($results as $row) { - if (!isset($delta_count[$row->entity_id][$row->language])) { - $delta_count[$row->entity_id][$row->language] = 0; - } - - if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || $delta_count[$row->entity_id][$row->language] < $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) { - $column_name = _field_sql_storage_columnname($field_name, $column); - $item[$column] = $row->$column_name; - } - - // Add the item to the field values for the entity. - $entities[$row->entity_id]->{$field_name}[$row->language][] = $item; - $delta_count[$row->entity_id][$row->language]++; - } - } - } -} - -/** - * Write field data for an entity. - * - * This hook is invoked from field_attach_insert() and field_attach_update(), - * to ask the field storage module to save field data. - * - * @param $entity_type - * The entity type of entity, such as 'node' or 'user'. - * @param $entity - * The entity on which to operate. - * @param $op - * FIELD_STORAGE_UPDATE when updating an existing entity, - * FIELD_STORAGE_INSERT when inserting a new entity. - * @param $fields - * An array listing the fields to be written. The keys and values of the - * array are field IDs. - */ -function hook_field_storage_write($entity_type, $entity, $op, $fields) { - list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); - if (!isset($vid)) { - $vid = $id; - } - - foreach ($fields as $field_id) { - $field = field_info_field_by_id($field_id); - $field_name = $field['field_name']; - $table_name = _field_sql_storage_tablename($field); - $revision_name = _field_sql_storage_revision_tablename($field); - - $all_languages = field_available_languages($entity_type, $field); - $field_languages = array_intersect($all_languages, array_keys((array) $entity->$field_name)); - - // Delete and insert, rather than update, in case a value was added. - if ($op == FIELD_STORAGE_UPDATE) { - // Delete languages present in the incoming $entity->$field_name. - // Delete all languages if $entity->$field_name is empty. - $languages = !empty($entity->$field_name) ? $field_languages : $all_languages; - if ($languages) { - db_delete($table_name) - ->condition('entity_type', $entity_type) - ->condition('entity_id', $id) - ->condition('language', $languages, 'IN') - ->execute(); - db_delete($revision_name) - ->condition('entity_type', $entity_type) - ->condition('entity_id', $id) - ->condition('revision_id', $vid) - ->condition('language', $languages, 'IN') - ->execute(); - } - } - - // Prepare the multi-insert query. - $do_insert = FALSE; - $columns = array('entity_type', 'entity_id', 'revision_id', 'bundle', 'delta', 'language'); - foreach ($field['columns'] as $column => $attributes) { - $columns[] = _field_sql_storage_columnname($field_name, $column); - } - $query = db_insert($table_name)->fields($columns); - $revision_query = db_insert($revision_name)->fields($columns); - - foreach ($field_languages as $langcode) { - $items = (array) $entity->{$field_name}[$langcode]; - $delta_count = 0; - foreach ($items as $delta => $item) { - // We now know we have someting to insert. - $do_insert = TRUE; - $record = array( - 'entity_type' => $entity_type, - 'entity_id' => $id, - 'revision_id' => $vid, - 'bundle' => $bundle, - 'delta' => $delta, - 'language' => $langcode, - ); - 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 query if we have values to insert. - if ($do_insert) { - $query->execute(); - $revision_query->execute(); - } - } -} - -/** - * Delete all field data for an entity. - * - * This hook is invoked from field_attach_delete() to ask the field storage - * module to delete field data. - * - * @param $entity_type - * The entity type of entity, such as 'node' or 'user'. - * @param $entity - * The entity on which to operate. - * @param $fields - * An array listing the fields to delete. The keys and values of the - * array are field IDs. - */ -function hook_field_storage_delete($entity_type, $entity, $fields) { - list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); - - foreach (field_info_instances($entity_type, $bundle) as $instance) { - if (isset($fields[$instance['field_id']])) { - $field = field_info_field_by_id($instance['field_id']); - field_sql_storage_field_storage_purge($entity_type, $entity, $field, $instance); - } - } -} - -/** - * Delete a single revision of field data for an entity. - * - * This hook is invoked from field_attach_delete_revision() to ask the field - * storage module to delete field revision data. - * - * Deleting the current (most recently written) revision is not - * allowed as has undefined results. - * - * @param $entity_type - * The entity type of entity, such as 'node' or 'user'. - * @param $entity - * The entity on which to operate. The revision to delete is - * indicated by the entity's revision ID property, as identified by - * hook_fieldable_info() for $entity_type. - * @param $fields - * An array listing the fields to delete. The keys and values of the - * array are field IDs. - */ -function hook_field_storage_delete_revision($entity_type, $entity, $fields) { - list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); - - if (isset($vid)) { - foreach ($fields as $field_id) { - $field = field_info_field_by_id($field_id); - $revision_name = _field_sql_storage_revision_tablename($field); - db_delete($revision_name) - ->condition('entity_type', $entity_type) - ->condition('entity_id', $id) - ->condition('revision_id', $vid) - ->execute(); - } - } -} - -/** - * Execute an EntityFieldQuery. - * - * This hook is called to find the entities having certain entity and field - * conditions and sort them in the given field order. If the field storage - * engine also handles property sorts and orders, it should unset those - * properties in the called object to signal that those have been handled. - * - * @param EntityFieldQuery $query - * An EntityFieldQuery. - * - * @return - * See EntityFieldQuery::execute() for the return values. - */ -function hook_field_storage_query($query) { - $groups = array(); - if ($query->age == FIELD_LOAD_CURRENT) { - $tablename_function = '_field_sql_storage_tablename'; - $id_key = 'entity_id'; - } - else { - $tablename_function = '_field_sql_storage_revision_tablename'; - $id_key = 'revision_id'; - } - $table_aliases = array(); - // Add tables for the fields used. - foreach ($query->fields as $key => $field) { - $tablename = $tablename_function($field); - // Every field needs a new table. - $table_alias = $tablename . $key; - $table_aliases[$key] = $table_alias; - if ($key) { - $select_query->join($tablename, $table_alias, "$table_alias.entity_type = $field_base_table.entity_type AND $table_alias.$id_key = $field_base_table.$id_key"); - } - else { - $select_query = db_select($tablename, $table_alias); - $select_query->addTag('entity_field_access'); - $select_query->addMetaData('base_table', $tablename); - $select_query->fields($table_alias, array('entity_type', 'entity_id', 'revision_id', 'bundle')); - $field_base_table = $table_alias; - } - if ($field['cardinality'] != 1) { - $select_query->distinct(); - } - } - - // Add field conditions. - foreach ($query->fieldConditions as $key => $condition) { - $table_alias = $table_aliases[$key]; - $field = $condition['field']; - // Add the specified condition. - $sql_field = "$table_alias." . _field_sql_storage_columnname($field['field_name'], $condition['column']); - $query->addCondition($select_query, $sql_field, $condition); - // Add delta / language group conditions. - foreach (array('delta', 'language') as $column) { - if (isset($condition[$column . '_group'])) { - $group_name = $condition[$column . '_group']; - if (!isset($groups[$column][$group_name])) { - $groups[$column][$group_name] = $table_alias; - } - else { - $select_query->where("$table_alias.$column = " . $groups[$column][$group_name] . ".$column"); - } - } - } - } - - if (isset($query->deleted)) { - $select_query->condition("$field_base_table.deleted", (int) $query->deleted); - } - - // Is there a need to sort the query by property? - $has_property_order = FALSE; - foreach ($query->order as $order) { - if ($order['type'] == 'property') { - $has_property_order = TRUE; - } - } - - if ($query->propertyConditions || $has_property_order) { - if (empty($query->entityConditions['entity_type']['value'])) { - throw new EntityFieldQueryException('Property conditions and orders must have an entity type defined.'); - } - $entity_type = $query->entityConditions['entity_type']['value']; - $entity_base_table = _field_sql_storage_query_join_entity($select_query, $entity_type, $field_base_table); - $query->entityConditions['entity_type']['operator'] = '='; - foreach ($query->propertyConditions as $property_condition) { - $query->addCondition($select_query, "$entity_base_table." . $property_condition['column'], $property_condition); - } - } - foreach ($query->entityConditions as $key => $condition) { - $query->addCondition($select_query, "$field_base_table.$key", $condition); - } - - // Order the query. - foreach ($query->order as $order) { - if ($order['type'] == 'entity') { - $key = $order['specifier']; - $select_query->orderBy("$field_base_table.$key", $order['direction']); - } - elseif ($order['type'] == 'field') { - $specifier = $order['specifier']; - $field = $specifier['field']; - $table_alias = $table_aliases[$specifier['index']]; - $sql_field = "$table_alias." . _field_sql_storage_columnname($field['field_name'], $specifier['column']); - $select_query->orderBy($sql_field, $order['direction']); - } - elseif ($order['type'] == 'property') { - $select_query->orderBy("$entity_base_table." . $order['specifier'], $order['direction']); - } - } - - return $query->finishQuery($select_query, $id_key); -} - -/** - * Act on creation of a new field. - * - * This hook is invoked from field_create_field() to ask the field storage - * module to save field information and prepare for storing field instances. - * If there is a problem, the field storage module should throw an exception. - * - * @param $field - * The field structure being created. - */ -function hook_field_storage_create_field($field) { - $schema = _field_sql_storage_schema($field); - foreach ($schema as $name => $table) { - db_create_table($name, $table); - } - drupal_get_schema(NULL, TRUE); -} - -/** - * Act on deletion of a field. - * - * This hook is invoked from field_delete_field() to ask the field storage - * module to mark all information stored in the field for deletion. - * - * @param $field - * The field being deleted. - */ -function hook_field_storage_delete_field($field) { - // Mark all data associated with the field for deletion. - $field['deleted'] = 0; - $table = _field_sql_storage_tablename($field); - $revision_table = _field_sql_storage_revision_tablename($field); - db_update($table) - ->fields(array('deleted' => 1)) - ->execute(); - - // Move the table to a unique name while the table contents are being deleted. - $field['deleted'] = 1; - $new_table = _field_sql_storage_tablename($field); - $revision_new_table = _field_sql_storage_revision_tablename($field); - db_rename_table($table, $new_table); - db_rename_table($revision_table, $revision_new_table); - drupal_get_schema(NULL, TRUE); -} - -/** - * Act on deletion of a field instance. - * - * This hook is invoked from field_delete_instance() to ask the field storage - * module to mark all information stored for the field instance for deletion. - * - * @param $instance - * The instance being deleted. - */ -function hook_field_storage_delete_instance($instance) { - $field = field_info_field($instance['field_name']); - $table_name = _field_sql_storage_tablename($field); - $revision_name = _field_sql_storage_revision_tablename($field); - db_update($table_name) - ->fields(array('deleted' => 1)) - ->condition('entity_type', $instance['entity_type']) - ->condition('bundle', $instance['bundle']) - ->execute(); - db_update($revision_name) - ->fields(array('deleted' => 1)) - ->condition('entity_type', $instance['entity_type']) - ->condition('bundle', $instance['bundle']) - ->execute(); -} - -/** - * Act before the storage backends load field data. - * - * This hook allows modules to load data before the Field Storage API, - * optionally preventing the field storage module from doing so. - * - * This lets 3rd party modules override, mirror, shard, or otherwise store a - * subset of fields in a different way than the current storage engine. - * Possible use cases include per-bundle storage, per-combo-field storage, etc. - * - * Modules implementing this hook should load field values and add them to - * objects in $entities. Fields with no values should be added as empty - * arrays. In addition, fields loaded should be added as keys to $skip_fields. - * - * @param $entity_type - * The type of entity, such as 'node' or 'user'. - * @param $entities - * The array of entity objects to add fields to, keyed by entity ID. - * @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 entity. - * @param $skip_fields - * An array keyed by field IDs whose data has already been loaded and - * therefore should not be loaded again. Add a key to this array to indicate - * that your module has already loaded a field. - * @param $options - * An associative array of additional options, with the following keys: - * - field_id: The field ID that should be loaded. If unset, all fields - * should be loaded. - * - deleted: If TRUE, deleted fields should be loaded as well as - * non-deleted fields. If unset or FALSE, only non-deleted fields should be - * loaded. - */ -function hook_field_storage_pre_load($entity_type, $entities, $age, &$skip_fields, $options) { - // @todo Needs function body. -} - -/** - * Act before the storage backends insert field data. - * - * This hook allows modules to store data before the Field Storage API, - * optionally preventing the field storage module from doing so. - * - * @param $entity_type - * The type of $entity; for example, 'node' or 'user'. - * @param $entity - * The entity with fields to save. - * @param $skip_fields - * An array keyed by field IDs whose data has already been written and - * therefore should not be written again. The values associated with these - * keys are not specified. - * @return - * Saved field IDs are set set as keys in $skip_fields. - */ -function hook_field_storage_pre_insert($entity_type, $entity, &$skip_fields) { - if ($entity_type == 'node' && $entity->status && _forum_node_check_node_type($entity)) { - $query = db_insert('forum_index')->fields(array('nid', 'title', 'tid', 'sticky', 'created', 'comment_count', 'last_comment_timestamp')); - foreach ($entity->taxonomy_forums as $language) { - foreach ($language as $delta) { - $query->values(array( - 'nid' => $entity->nid, - 'title' => $entity->title, - 'tid' => $delta['value'], - 'sticky' => $entity->sticky, - 'created' => $entity->created, - 'comment_count' => 0, - 'last_comment_timestamp' => $entity->created, - )); - } - } - $query->execute(); - } -} - -/** - * Act before the storage backends update field data. - * - * This hook allows modules to store data before the Field Storage API, - * optionally preventing the field storage module from doing so. - * - * @param $entity_type - * The type of $entity; for example, 'node' or 'user'. - * @param $entity - * The entity with fields to save. - * @param $skip_fields - * An array keyed by field IDs whose data has already been written and - * therefore should not be written again. The values associated with these - * keys are not specified. - * @return - * Saved field IDs are set set as keys in $skip_fields. - */ -function hook_field_storage_pre_update($entity_type, $entity, &$skip_fields) { - $first_call = &drupal_static(__FUNCTION__, array()); - - if ($entity_type == 'node' && $entity->status && _forum_node_check_node_type($entity)) { - // We don't maintain data for old revisions, so clear all previous values - // from the table. Since this hook runs once per field, per entity, make - // sure we only wipe values once. - if (!isset($first_call[$entity->nid])) { - $first_call[$entity->nid] = FALSE; - db_delete('forum_index')->condition('nid', $entity->nid)->execute(); - } - // Only save data to the table if the node is published. - if ($entity->status) { - $query = db_insert('forum_index')->fields(array('nid', 'title', 'tid', 'sticky', 'created', 'comment_count', 'last_comment_timestamp')); - foreach ($entity->taxonomy_forums as $language) { - foreach ($language as $delta) { - $query->values(array( - 'nid' => $entity->nid, - 'title' => $entity->title, - 'tid' => $delta['value'], - 'sticky' => $entity->sticky, - 'created' => $entity->created, - 'comment_count' => 0, - 'last_comment_timestamp' => $entity->created, - )); - } - } - $query->execute(); - // The logic for determining last_comment_count is fairly complex, so - // call _forum_update_forum_index() too. - _forum_update_forum_index($entity->nid); - } - } -} - -/** - * Returns the maximum weight for the entity components handled by the module. - * - * Field API takes care of fields and 'extra_fields'. This hook is intended for - * third-party modules adding other entity components (e.g. field_group). - * - * @param $entity_type - * The type of entity; e.g. 'node' or 'user'. - * @param $bundle - * The bundle name. - * @param $context - * The context for which the maximum weight is requested. Either 'form', or - * the name of a view mode. - * @return - * The maximum weight of the entity's components, or NULL if no components - * were found. - */ -function hook_field_info_max_weight($entity_type, $bundle, $context) { - $weights = array(); - - foreach (my_module_entity_additions($entity_type, $bundle, $context) as $addition) { - $weights[] = $addition['weight']; - } - - return $weights ? max($weights) : NULL; -} - -/** - * Alters the display settings of a field before it gets displayed. - * - * Note that instead of hook_field_display_alter(), which is called for all - * fields on all entity types, hook_field_display_ENTITY_TYPE_alter() may be - * used to alter display settings for fields on a specific entity type only. - * - * This hook is called once per field per displayed entity. If the result of the - * hook involves reading from the database, it is highly recommended to - * statically cache the information. - * - * @param $display - * The display settings that will be used to display the field values, as - * found in the 'display' key of $instance definitions. - * @param $context - * An associative array containing: - * - entity_type: The entity type; e.g., 'node' or 'user'. - * - field: The field being rendered. - * - instance: The instance being rendered. - * - entity: The entity being rendered. - * - view_mode: The view mode, e.g. 'full', 'teaser'... - * - * @see hook_field_display_ENTITY_TYPE_alter() - */ -function hook_field_display_alter(&$display, $context) { - // Leave field labels out of the search index. - // Note: The check against $context['entity_type'] == 'node' could be avoided - // by using hook_field_display_node_alter() instead of - // hook_field_display_alter(), resulting in less function calls when - // rendering non-node entities. - if ($context['entity_type'] == 'node' && $context['view_mode'] == 'search_index') { - $display['label'] = 'hidden'; - } -} - -/** - * Alters the display settings of a field on a given entity type before it gets displayed. - * - * Modules can implement hook_field_display_ENTITY_TYPE_alter() to alter display - * settings for fields on a specific entity type, rather than implementing - * hook_field_display_alter(). - * - * This hook is called once per field per displayed entity. If the result of the - * hook involves reading from the database, it is highly recommended to - * statically cache the information. - * - * @param $display - * The display settings that will be used to display the field values, as - * found in the 'display' key of $instance definitions. - * @param $context - * An associative array containing: - * - entity_type: The entity type; e.g., 'node' or 'user'. - * - field: The field being rendered. - * - instance: The instance being rendered. - * - entity: The entity being rendered. - * - view_mode: The view mode, e.g. 'full', 'teaser'... - * - * @see hook_field_display_alter() - */ -function hook_field_display_ENTITY_TYPE_alter(&$display, $context) { - // Leave field labels out of the search index. - if ($context['view_mode'] == 'search_index') { - $display['label'] = 'hidden'; - } -} - -/** - * Alters the display settings of pseudo-fields before an entity is displayed. - * - * This hook is called once per displayed entity. If the result of the hook - * involves reading from the database, it is highly recommended to statically - * cache the information. - * - * @param $displays - * An array of display settings for the pseudo-fields in the entity, keyed - * by pseudo-field names. - * @param $context - * An associative array containing: - * - entity_type: The entity type; e.g., 'node' or 'user'. - * - bundle: The bundle name. - * - view_mode: The view mode, e.g. 'full', 'teaser'... - */ -function hook_field_extra_fields_display_alter(&$displays, $context) { - if ($context['entity_type'] == 'taxonomy_term' && $context['view_mode'] == 'full') { - $displays['description']['visible'] = FALSE; - } -} - -/** - * Alters the widget properties of a field instance before it gets displayed. - * - * Note that instead of hook_field_widget_properties_alter(), which is called - * for all fields on all entity types, - * hook_field_widget_properties_ENTITY_TYPE_alter() may be used to alter widget - * properties for fields on a specific entity type only. - * - * This hook is called once per field per added or edit entity. If the result - * of the hook involves reading from the database, it is highly recommended to - * statically cache the information. - * - * @param $widget - * The instance's widget properties. - * @param $context - * An associative array containing: - * - entity_type: The entity type; e.g., 'node' or 'user'. - * - entity: The entity object. - * - field: The field that the widget belongs to. - * - instance: The instance of the field. - * - * @see hook_field_widget_properties_ENTITY_TYPE_alter() - */ -function hook_field_widget_properties_alter(&$widget, $context) { - // Change a widget's type according to the time of day. - $field = $context['field']; - if ($context['entity_type'] == 'node' && $field['field_name'] == 'field_foo') { - $time = date('H'); - $widget['type'] = $time < 12 ? 'widget_am' : 'widget_pm'; - } -} - -/** - * Alters the widget properties of a field instance on a given entity type - * before it gets displayed. - * - * Modules can implement hook_field_widget_properties_ENTITY_TYPE_alter() to - * alter the widget properties for fields on a specific entity type, rather than - * implementing hook_field_widget_properties_alter(). - * - * This hook is called once per field per displayed widget entity. If the result - * of the hook involves reading from the database, it is highly recommended to - * statically cache the information. - * - * @param $widget - * The instance's widget properties. - * @param $context - * An associative array containing: - * - entity_type: The entity type; e.g., 'node' or 'user'. - * - entity: The entity object. - * - field: The field that the widget belongs to. - * - instance: The instance of the field. - * - * @see hook_field_widget_properties_alter() - */ -function hook_field_widget_properties_ENTITY_TYPE_alter(&$widget, $context) { - // Change a widget's type according to the time of day. - $field = $context['field']; - if ($field['field_name'] == 'field_foo') { - $time = date('H'); - $widget['type'] = $time < 12 ? 'widget_am' : 'widget_pm'; - } -} - -/** - * @} End of "ingroup field_storage" - */ - -/********************************************************************** - * Field CRUD API - **********************************************************************/ - -/** - * @ingroup field_crud - * @{ - */ - -/** - * Act on a field being created. - * - * This hook is invoked from field_create_field() after the field is created, to - * allow modules to act on field creation. - * - * @param $field - * The field just created. - */ -function hook_field_create_field($field) { - // @todo Needs function body. -} - -/** - * Act on a field instance being created. - * - * This hook is invoked from field_create_instance() after the instance record - * is saved, so it cannot be used to modify the instance itself. - * - * @param $instance - * The instance just created. - */ -function hook_field_create_instance($instance) { - // @todo Needs function body. -} - -/** - * Forbid a field update from occurring. - * - * Any module may forbid any update for any reason. For example, the - * field's storage module might forbid an update if it would change - * the storage schema while data for the field exists. A field type - * module might forbid an update if it would change existing data's - * semantics, or if there are external dependencies on field settings - * that cannot be updated. - * - * To forbid the update from occurring, throw a FieldUpdateForbiddenException. - * - * @param $field - * The field as it will be post-update. - * @param $prior_field - * The field as it is pre-update. - * @param $has_data - * Whether any data already exists for this field. - */ -function hook_field_update_forbid($field, $prior_field, $has_data) { - // A 'list' field stores integer keys mapped to display values. If - // the new field will have fewer values, and any data exists for the - // abandoned keys, the field will have no way to display them. So, - // forbid such an update. - if ($has_data && count($field['settings']['allowed_values']) < count($prior_field['settings']['allowed_values'])) { - // Identify the keys that will be lost. - $lost_keys = array_diff(array_keys($field['settings']['allowed_values']), array_keys($prior_field['settings']['allowed_values'])); - // If any data exist for those keys, forbid the update. - $query = new EntityFieldQuery(); - $found = $query - ->fieldCondition($prior_field['field_name'], 'value', $lost_keys) - ->range(0, 1) - ->execute(); - if ($found) { - throw new FieldUpdateForbiddenException("Cannot update a list field not to include keys with existing data"); - } - } -} - -/** - * Act on a field being updated. - * - * This hook is invoked just after field is updated in field_update_field(). - * - * @param $field - * The field as it is post-update. - * @param $prior_field - * The field as it was pre-update. - * @param $has_data - * Whether any data already exists for this field. - */ -function hook_field_update_field($field, $prior_field, $has_data) { - // Reset the static value that keeps track of allowed values for list fields. - drupal_static_reset('list_allowed_values'); -} - -/** - * Act on a field being deleted. - * - * This hook is invoked just after a field is deleted by field_delete_field(). - * - * @param $field - * The field just deleted. - */ -function hook_field_delete_field($field) { - // @todo Needs function body. -} - -/** - * Act on a field instance being updated. - * - * This hook is invoked from field_update_instance() after the instance record - * is saved, so it cannot be used by a module to modify the instance itself. - * - * @param $instance - * The instance as it is post-update. - * @param $prior_$instance - * The instance as it was pre-update. - */ -function hook_field_update_instance($instance, $prior_instance) { - // @todo Needs function body. -} - -/** - * Act on a field instance being deleted. - * - * This hook is invoked from field_delete_instance() after the instance is - * deleted. - * - * @param $instance - * The instance just deleted. - */ -function hook_field_delete_instance($instance) { - // @todo Needs function body. -} - -/** - * Act on field records being read from the database. - * - * This hook is invoked from field_read_fields() on each field being read. - * - * @param $field - * The field record just read from the database. - */ -function hook_field_read_field($field) { - // @todo Needs function body. -} - -/** - * Act on a field record being read from the database. - * - * This hook is invoked from field_read_instances() on each instance being read. - * - * @param $instance - * The instance record just read from the database. - */ -function hook_field_read_instance($instance) { - // @todo Needs function body. -} - -/** - * Acts when a field record is being purged. - * - * In field_purge_field(), after the field configuration has been - * removed from the database, the field storage module has had a chance to - * run its hook_field_storage_purge_field(), and the field info cache - * has been cleared, this hook is invoked on all modules to allow them to - * respond to the field being purged. - * - * @param $field - * The field being purged. - */ -function hook_field_purge_field($field) { - db_delete('my_module_field_info') - ->condition('id', $field['id']) - ->execute(); -} - -/** - * Acts when a field instance is being purged. - * - * In field_purge_instance(), after the field instance has been - * removed from the database, the field storage module has had a chance to - * run its hook_field_storage_purge_instance(), and the field info cache - * has been cleared, this hook is invoked on all modules to allow them to - * respond to the field instance being purged. - * - * @param $instance - * The instance being purged. - */ -function hook_field_purge_instance($instance) { - db_delete('my_module_field_instance_info') - ->condition('id', $instance['id']) - ->execute(); -} - -/** - * Remove field storage information when a field record is purged. - * - * Called from field_purge_field() to allow the field storage module - * to remove field information when a field is being purged. - * - * @param $field - * The field being purged. - */ -function hook_field_storage_purge_field($field) { - $table_name = _field_sql_storage_tablename($field); - $revision_name = _field_sql_storage_revision_tablename($field); - db_drop_table($table_name); - db_drop_table($revision_name); -} - -/** - * Remove field storage information when a field instance is purged. - * - * Called from field_purge_instance() to allow the field storage module - * to remove field instance information when a field instance is being - * purged. - * - * @param $instance - * The instance being purged. - */ -function hook_field_storage_purge_field_instance($instance) { - db_delete('my_module_field_instance_info') - ->condition('id', $instance['id']) - ->execute(); -} - -/** - * Remove field storage information when field data is purged. - * - * Called from field_purge_data() to allow the field storage - * module to delete field data information. - * - * @param $entity_type - * The type of $entity; for example, 'node' or 'user'. - * @param $entity - * The pseudo-entity whose field data to delete. - * @param $field - * The (possibly deleted) field whose data is being purged. - * @param $instance - * The deleted field instance whose data is being purged. - */ -function hook_field_storage_purge($entity_type, $entity, $field, $instance) { - list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); - - $table_name = _field_sql_storage_tablename($field); - $revision_name = _field_sql_storage_revision_tablename($field); - db_delete($table_name) - ->condition('entity_type', $entity_type) - ->condition('entity_id', $id) - ->execute(); - db_delete($revision_name) - ->condition('entity_type', $entity_type) - ->condition('entity_id', $id) - ->execute(); -} - -/** - * @} End of "ingroup field_crud" - */ - -/********************************************************************** - * TODO: I'm not sure where these belong yet. - **********************************************************************/ - -/** - * Determine whether the user has access to a given field. - * - * This hook is invoked from field_access() to let modules block access to - * operations on fields. If no module returns FALSE, the operation is allowed. - * - * @param $op - * The operation to be performed. Possible values: 'edit', 'view'. - * @param $field - * The field on which the operation is to be performed. - * @param $entity_type - * The type of $entity; for example, 'node' or 'user'. - * @param $entity - * (optional) The entity for the operation. - * @param $account - * (optional) The account to check; if not given use currently logged in user. - * - * @return - * TRUE if the operation is allowed, and FALSE if the operation is denied. - */ -function hook_field_access($op, $field, $entity_type, $entity, $account) { - if ($field['field_name'] == 'field_of_interest' && $op == 'edit') { - return user_access('edit field of interest', $account); - } - return TRUE; -} diff --git a/modules/field/field.attach.inc b/modules/field/field.attach.inc deleted file mode 100644 index 3f5520a57f5..00000000000 --- a/modules/field/field.attach.inc +++ /dev/null @@ -1,1357 +0,0 @@ -<?php - -/** - * @file - * Field attach API, allowing entities (nodes, users, ...) to be 'fieldable'. - */ - -/** - * Exception thrown by field_attach_validate() on field validation errors. - */ -class FieldValidationException extends FieldException { - var $errors; - - /** - * Constructor for FieldValidationException. - * - * @param $errors - * An array of field validation errors, keyed by field name and - * delta that contains two keys: - * - 'error': A machine-readable error code string, prefixed by - * the field module name. A field widget may use this code to decide - * how to report the error. - * - 'message': A human-readable error message such as to be - * passed to form_error() for the appropriate form element. - */ - function __construct($errors) { - $this->errors = $errors; - parent::__construct(t('Field validation errors')); - } -} - -/** - * @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 backends can choose to represent - * the data in SQL differently or use a completely different storage mechanism - * such as a cloud-based database. - * - * Each field defines which storage backend it uses. The Drupal system variable - * 'field_storage_default' identifies the storage backend used by default. - */ - -/** - * Argument for an update operation. - * - * This is used in hook_field_storage_write when updating an - * existing entity. - */ -define('FIELD_STORAGE_UPDATE', 'update'); - -/** - * Argument for an insert operation. - * - * This is used in hook_field_storage_write when inserting a new entity. - */ -define('FIELD_STORAGE_INSERT', 'insert'); - -/** - * @} End of "defgroup field_storage" - */ - -/** - * @defgroup field_attach Field Attach API - * @{ - * Operate on Field API data attached to Drupal entities. - * - * Field Attach API functions load, store, display, generate Form API - * structures, and perform a variety of other functions for field data attached - * to individual entities. - * - * Field Attach API functions generally take $entity_type and $entity arguments - * along with additional function-specific arguments. $entity_type is the type - * of the fieldable entity, such as 'node' or 'user', and $entity is the entity - * itself. - * - * hook_entity_info() is the central place for entity types to define if and - * how Field API should operate on their entity objects. Notably, the - * 'fieldable' property needs to be set to TRUE. - * - * The Field Attach API uses the concept of bundles: the set of fields for a - * given entity is defined on a per-bundle basis. The collection of bundles for - * an entity type is defined its hook_entity_info() implementation. For - * instance, node_entity_info() exposes each node type as its own bundle. This - * means that the set of fields of a node is determined by the node type. The - * Field API reads the bundle name for a given entity from a particular - * property of the entity object, and hook_entity_info() defines which property - * to use. For instance, node_entity_info() specifies: - * @code $info['entity keys']['bundle'] = 'type'@endcode - * This indicates that for a particular node object, the bundle name can be - * found in $node->type. This property can be omitted if the entity type only - * exposes a single bundle (all entities of this type have the same collection - * of fields). This is the case for the 'user' entity type. - * - * Most Field Attach API functions define a corresponding hook function that - * allows any module to act on Field Attach operations for any entity after the - * operation is complete, and access or modify all the field, form, or display - * data for that entity and operation. For example, field_attach_view() invokes - * hook_field_attach_view_alter(). 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. - * - * field_attach_load(), field_attach_insert(), and field_attach_update() also - * define pre-operation hooks, e.g. hook_field_attach_pre_load(). These hooks - * run before the corresponding Field Storage API and Field Type API - * operations. They allow modules to define additional storage locations (e.g. - * denormalizing, mirroring) for field data on a per-field basis. They also - * allow modules to take over field storage completely by instructing other - * implementations of the same hook and the Field Storage API itself not to - * operate on specified fields. - * - * The pre-operation hooks do not make the Field Storage API irrelevant. The - * Field Storage API is essentially the "fallback mechanism" for any fields - * that aren't being intercepted explicitly by pre-operation hooks. - */ - -/** - * Invoke a field hook. - * - * @param $op - * Possible operations include: - * - form - * - validate - * - presave - * - insert - * - update - * - delete - * - delete revision - * - view - * - prepare translation - * @param $entity_type - * The type of $entity; e.g. 'node' or 'user'. - * @param $entity - * The fully formed $entity_type entity. - * @param $a - * - The $form in the 'form' operation. - * - The value of $view_mode in the 'view' operation. - * - Otherwise NULL. - * @param $b - * - The $form_state in the 'submit' operation. - * - Otherwise NULL. - * @param $options - * An associative array of additional options, with the following keys: - * - 'field_name': The name of the field whose operation should be - * invoked. By default, the operation is invoked on all the fields - * in the entity's bundle. NOTE: This option is not compatible with - * the 'deleted' option; the 'field_id' option should be used - * instead. - * - 'field_id': The id of the field whose operation should be - * invoked. By default, the operation is invoked on all the fields - * in the entity's' bundles. - * - 'default': A boolean value, specifying which implementation of - * the operation should be invoked. - * - if FALSE (default), the field types implementation of the operation - * will be invoked (hook_field_[op]) - * - If TRUE, the default field implementation of the field operation - * will be invoked (field_default_[op]) - * Internal use only. Do not explicitely set to TRUE, but use - * _field_invoke_default() instead. - * - 'deleted': If TRUE, the function will operate on deleted fields - * as well as non-deleted fields. If unset or FALSE, only - * non-deleted fields are operated on. - * - 'language': A language code or an array of language codes keyed by field - * name. It will be used to narrow down to a single value the available - * languages to act on. - */ -function _field_invoke($op, $entity_type, $entity, &$a = NULL, &$b = NULL, $options = array()) { - // Merge default options. - $default_options = array( - 'default' => FALSE, - 'deleted' => FALSE, - 'language' => NULL, - ); - $options += $default_options; - - // Determine the list of instances to iterate on. - list(, , $bundle) = entity_extract_ids($entity_type, $entity); - $instances = _field_invoke_get_instances($entity_type, $bundle, $options); - - // Iterate through the instances and collect results. - $return = array(); - foreach ($instances as $instance) { - $field_name = $instance['field_name']; - $field = field_info_field($field_name); - $function = $options['default'] ? 'field_default_' . $op : $field['module'] . '_field_' . $op; - if (function_exists($function)) { - // Determine the list of languages to iterate on. - $available_languages = field_available_languages($entity_type, $field); - $languages = _field_language_suggestion($available_languages, $options['language'], $field_name); - - foreach ($languages as $langcode) { - $items = isset($entity->{$field_name}[$langcode]) ? $entity->{$field_name}[$langcode] : array(); - $result = $function($entity_type, $entity, $field, $instance, $langcode, $items, $a, $b); - if (isset($result)) { - // For hooks with array results, we merge results together. - // For hooks with scalar results, we collect results in an array. - if (is_array($result)) { - $return = array_merge($return, $result); - } - else { - $return[] = $result; - } - } - - // Populate $items back in the field values, but avoid replacing missing - // fields with an empty array (those are not equivalent on update). - if ($items !== array() || isset($entity->{$field_name}[$langcode])) { - $entity->{$field_name}[$langcode] = $items; - } - } - } - } - - return $return; -} - -/** - * Invoke a field hook across fields on multiple entities. - * - * @param $op - * Possible operations include: - * - load - * - prepare_view - * For all other operations, use _field_invoke() / field_invoke_default() - * instead. - * @param $entity_type - * The type of $entity; e.g. 'node' or 'user'. - * @param $entities - * An array of entities, keyed by entity id. - * @param $a - * - The $age parameter in the 'load' operation. - * - Otherwise NULL. - * @param $b - * Currently always NULL. - * @param $options - * An associative array of additional options, with the following keys: - * - 'field_name': The name of the field whose operation should be - * invoked. By default, the operation is invoked on all the fields - * in the entity's bundle. NOTE: This option is not compatible with - * the 'deleted' option; the 'field_id' option should be used instead. - * - 'field_id': The id of the field whose operation should be - * invoked. By default, the operation is invoked on all the fields - * in the entity's' bundles. - * - 'default': A boolean value, specifying which implementation of - * the operation should be invoked. - * - if FALSE (default), the field types implementation of the operation - * will be invoked (hook_field_[op]) - * - If TRUE, the default field implementation of the field operation - * will be invoked (field_default_[op]) - * Internal use only. Do not explicitely set to TRUE, but use - * _field_invoke_multiple_default() instead. - * - 'deleted': If TRUE, the function will operate on deleted fields - * as well as non-deleted fields. If unset or FALSE, only - * non-deleted fields are operated on. - * - 'language': A language code or an array of arrays of language codes keyed - * by entity id and field name. It will be used to narrow down to a single - * value the available languages to act on. - * - * @return - * An array of returned values keyed by entity id. - */ -function _field_invoke_multiple($op, $entity_type, $entities, &$a = NULL, &$b = NULL, $options = array()) { - // Merge default options. - $default_options = array( - 'default' => FALSE, - 'deleted' => FALSE, - 'language' => NULL, - ); - $options += $default_options; - $field_info = field_info_field_by_ids(); - - $fields = array(); - $grouped_instances = array(); - $grouped_entities = array(); - $grouped_items = array(); - $return = array(); - - // Go through the entities and collect the fields on which the hook should be - // invoked. - // - // We group fields by id, not by name, because this function can operate on - // deleted fields which may have non-unique names. However, entities can only - // contain data for a single field for each name, even if that field - // is deleted, so we reference field data via the - // $entity->$field_name property. - foreach ($entities as $entity) { - // Determine the list of instances to iterate on. - list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); - $instances = _field_invoke_get_instances($entity_type, $bundle, $options); - - foreach ($instances as $instance) { - $field_id = $instance['field_id']; - $field_name = $instance['field_name']; - $field = $field_info[$field_id]; - $function = $options['default'] ? 'field_default_' . $op : $field['module'] . '_field_' . $op; - if (function_exists($function)) { - // Add the field to the list of fields to invoke the hook on. - if (!isset($fields[$field_id])) { - $fields[$field_id] = $field; - } - // Extract the field values into a separate variable, easily accessed - // by hook implementations. - // Unless a language suggestion is provided we iterate on all the - // available languages. - $available_languages = field_available_languages($entity_type, $field); - $language = !empty($options['language'][$id]) ? $options['language'][$id] : $options['language']; - $languages = _field_language_suggestion($available_languages, $language, $field_name); - foreach ($languages as $langcode) { - $grouped_items[$field_id][$langcode][$id] = isset($entity->{$field_name}[$langcode]) ? $entity->{$field_name}[$langcode] : array(); - // Group the instances and entities corresponding to the current - // field. - $grouped_instances[$field_id][$langcode][$id] = $instance; - $grouped_entities[$field_id][$langcode][$id] = $entities[$id]; - } - } - } - // Initialize the return value for each entity. - $return[$id] = array(); - } - - // For each field, invoke the field hook and collect results. - foreach ($fields as $field_id => $field) { - $field_name = $field['field_name']; - $function = $options['default'] ? 'field_default_' . $op : $field['module'] . '_field_' . $op; - // Iterate over all the field translations. - foreach ($grouped_items[$field_id] as $langcode => &$items) { - $entities = $grouped_entities[$field_id][$langcode]; - $instances = $grouped_instances[$field_id][$langcode]; - $results = $function($entity_type, $entities, $field, $instances, $langcode, $items, $a, $b); - if (isset($results)) { - // Collect results by entity. - // For hooks with array results, we merge results together. - // For hooks with scalar results, we collect results in an array. - foreach ($results as $id => $result) { - if (is_array($result)) { - $return[$id] = array_merge($return[$id], $result); - } - else { - $return[$id][] = $result; - } - } - } - } - - // Populate field values back in the entities, but avoid replacing missing - // fields with an empty array (those are not equivalent on update). - foreach ($grouped_entities[$field_id] as $langcode => $entities) { - foreach ($entities as $id => $entity) { - if ($grouped_items[$field_id][$langcode][$id] !== array() || isset($entity->{$field_name}[$langcode])) { - $entity->{$field_name}[$langcode] = $grouped_items[$field_id][$langcode][$id]; - } - } - } - } - - return $return; -} - -/** - * Invoke field.module's version of a field hook. - * - * This function invokes the field_default_[op]() function. - * Use _field_invoke() to invoke the field type implementation, - * hook_field_[op](). - * - * @see _field_invoke() - */ -function _field_invoke_default($op, $entity_type, $entity, &$a = NULL, &$b = NULL, $options = array()) { - $options['default'] = TRUE; - return _field_invoke($op, $entity_type, $entity, $a, $b, $options); -} - -/** - * Invoke field.module's version of a field hook on multiple entities. - * - * This function invokes the field_default_[op]() function. - * Use _field_invoke_multiple() to invoke the field type implementation, - * hook_field_[op](). - * - * @see _field_invoke_multiple() - */ -function _field_invoke_multiple_default($op, $entity_type, $entities, &$a = NULL, &$b = NULL, $options = array()) { - $options['default'] = TRUE; - return _field_invoke_multiple($op, $entity_type, $entities, $a, $b, $options); -} - -/** - * Helper for _field_invoke(): retrieves a list of instances to operate on. - * - * @param $entity_type - * The entity type. - * @param $bundle - * The bundle name. - * @param $options - * An associative array of options, as provided to _field_invoke(). Only the - * following keys are considered : - * - deleted - * - field_name - * - field_id - * See _field_invoke() for details. - * - * @return - * The array of selected instance definitions. - */ -function _field_invoke_get_instances($entity_type, $bundle, $options) { - if ($options['deleted']) { - // Deleted fields are not included in field_info_instances(), and need to - // be fetched from the database with field_read_instances(). - $params = array('entity_type' => $entity_type, 'bundle' => $bundle); - if (isset($options['field_id'])) { - // Single-field mode by field id: field_read_instances() does the filtering. - // Single-field mode by field name is not compatible with the 'deleted' - // option. - $params['field_id'] = $options['field_id']; - } - $instances = field_read_instances($params, array('include_deleted' => TRUE)); - } - elseif (isset($options['field_name'])) { - // Single-field mode by field name: field_info_instance() does the - // filtering. - $instances = array(field_info_instance($entity_type, $options['field_name'], $bundle)); - } - else { - $instances = field_info_instances($entity_type, $bundle); - if (isset($options['field_id'])) { - // Single-field mode by field id: we need to loop on each instance to - // find the right one. - foreach ($instances as $instance) { - if ($instance['field_id'] == $options['field_id']) { - $instances = array($instance); - break; - } - } - } - } - - return $instances; -} - -/** - * Add form elements for all fields for an entity to a form structure. - * - * The form elements for the entity's fields are added by reference as direct - * children in the $form parameter. This parameter can be a full form structure - * (most common case for entity edit forms), or a sub-element of a larger form. - * - * By default, submitted field values appear at the top-level of - * $form_state['values']. A different location within $form_state['values'] can - * be specified by setting the '#parents' property on the incoming $form - * parameter. Because of name clashes, two instances of the same field cannot - * appear within the same $form element, or within the same '#parents' space. - * - * For each call to field_attach_form(), field values are processed by calling - * field_attach_form_validate() and field_attach_submit() on the same $form - * element. - * - * Sample resulting structure in $form: - * @code - * '#parents' => The location of field values in $form_state['values'], - * '#entity_type' => The name of the entity type, - * '#bundle' => The name of the bundle, - * // One sub-array per field appearing in the entity, keyed by field name. - * // The structure of the array differs slightly depending on whether the - * // widget is 'single-value' (provides the input for one field value, - * // most common case), and will therefore be repeated as many times as - * // needed, or 'multiple-values' (one single widget allows the input of - * // several values, e.g checkboxes, select box...). - * // The sub-array is nested into a $langcode key where $langcode has the - * // same value of the $langcode parameter above. - * // The '#language' key holds the same value of $langcode and it is used - * // to access the field sub-array when $langcode is unknown. - * 'field_foo' => array( - * '#tree' => TRUE, - * '#field_name' => The name of the field, - * '#language' => $langcode, - * $langcode => array( - * '#field_name' => The name of the field, - * '#language' => $langcode, - * '#field_parents' => The 'parents' space for the field in the form, - * equal to the #parents property of the $form parameter received by - * field_attach_form(), - * '#required' => Whether or not the field is required, - * '#title' => The label of the field instance, - * '#description' => The description text for the field instance, - * - * // Only for 'single' widgets: - * '#theme' => 'field_multiple_value_form', - * '#cardinality' => The field cardinality, - * // One sub-array per copy of the widget, keyed by delta. - * 0 => array( - * '#entity_type' => The name of the entity type, - * '#bundle' => The name of the bundle, - * '#field_name' => The name of the field, - * '#field_parents' => The 'parents' space for the field in the form, - * equal to the #parents property of the $form parameter received by - * field_attach_form(), - * '#title' => The title to be displayed by the widget, - * '#default_value' => The field value for delta 0, - * '#required' => Whether the widget should be marked required, - * '#delta' => 0, - * '#columns' => The array of field columns, - * // The remaining elements in the sub-array depend on the widget. - * '#type' => The type of the widget, - * ... - * ), - * 1 => array( - * ... - * ), - * - * // Only for multiple widgets: - * '#entity_type' => The name of the entity type, - * '#bundle' => $instance['bundle'], - * '#columns' => array_keys($field['columns']), - * // The remaining elements in the sub-array depend on the widget. - * '#type' => The type of the widget, - * ... - * ), - * ... - * ), - * ) - * @endcode - * - * Additionally, some processing data is placed in $form_state, and can be - * accessed by field_form_get_state() and field_form_set_state(). - * - * @param $entity_type - * The type of $entity; e.g. 'node' or 'user'. - * @param $entity - * The entity for which to load form elements, used to initialize - * default form values. - * @param $form - * The form structure to fill in. This can be a full form structure, or a - * sub-element of a larger form. The #parents property can be set to control - * the location of submitted field values within $form_state['values']. If - * not specified, $form['#parents'] is set to an empty array, placing field - * values at the top-level of $form_state['values']. - * @param $form_state - * An associative array containing the current state of the form. - * @param $langcode - * The language the field values are going to be entered, if no language - * is provided the default site language will be used. - * - * @see field_form_get_state() - * @see field_form_set_state() - */ -function field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode = NULL) { - // Set #parents to 'top-level' by default. - $form += array('#parents' => array()); - - // If no language is provided use the default site language. - $options = array('language' => field_valid_language($langcode)); - $form += (array) _field_invoke_default('form', $entity_type, $entity, $form, $form_state, $options); - - // Add custom weight handling. - list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); - $form['#pre_render'][] = '_field_extra_fields_pre_render'; - $form['#entity_type'] = $entity_type; - $form['#bundle'] = $bundle; - - // Let other modules make changes to the form. - // Avoid module_invoke_all() to let parameters be taken by reference. - foreach (module_implements('field_attach_form') as $module) { - $function = $module . '_field_attach_form'; - $function($entity_type, $entity, $form, $form_state, $langcode); - } -} - -/** - * Loads fields for the current revisions of a group of entities. - * - * Loads all fields for each entity object in a group of a single entity type. - * The loaded field values are added directly to the entity objects. - * - * field_attach_load() is automatically called by the default entity controller - * class, and thus, in most cases, doesn't need to be explicitly called by the - * entity type module. - * - * @param $entity_type - * The type of $entity; e.g., 'node' or 'user'. - * @param $entities - * An array of entities for which to load fields, keyed by entity ID. - * Each entity needs to have its 'bundle', 'id' and (if applicable) - * 'revision' keys filled in. The function adds the loaded field data - * directly in the entity objects of the $entities array. - * @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 entity. Defaults to FIELD_LOAD_CURRENT; use - * field_attach_load_revision() instead of passing FIELD_LOAD_REVISION. - * @param $options - * An associative array of additional options, with the following keys: - * - 'field_id': The field ID that should be loaded, instead of - * loading all fields, for each entity. Note that returned entities - * may contain data for other fields, for example if they are read - * from a cache. - * - 'deleted': If TRUE, the function will operate on deleted fields - * as well as non-deleted fields. If unset or FALSE, only - * non-deleted fields are operated on. - */ -function field_attach_load($entity_type, $entities, $age = FIELD_LOAD_CURRENT, $options = array()) { - $field_info = field_info_field_by_ids(); - $load_current = $age == FIELD_LOAD_CURRENT; - - // Merge default options. - $default_options = array( - 'deleted' => FALSE, - ); - $options += $default_options; - - $info = entity_get_info($entity_type); - // Only the most current revision of non-deleted fields for cacheable entity - // types can be cached. - $cache_read = $load_current && $info['field cache'] && empty($options['deleted']); - // In addition, do not write to the cache when loading a single field. - $cache_write = $cache_read && !isset($options['field_id']); - - if (empty($entities)) { - return; - } - - // Assume all entities will need to be queried. Entities found in the cache - // will be removed from the list. - $queried_entities = $entities; - - // Fetch available entities from cache, if applicable. - if ($cache_read) { - // Build the list of cache entries to retrieve. - $cids = array(); - foreach ($entities as $id => $entity) { - $cids[] = "field:$entity_type:$id"; - } - $cache = cache_get_multiple($cids, 'cache_field'); - // Put the cached field values back into the entities and remove them from - // the list of entities to query. - foreach ($entities as $id => $entity) { - $cid = "field:$entity_type:$id"; - if (isset($cache[$cid])) { - unset($queried_entities[$id]); - foreach ($cache[$cid]->data as $field_name => $values) { - $entity->$field_name = $values; - } - } - } - } - - // Fetch other entities from their storage location. - if ($queried_entities) { - // The invoke order is: - // - hook_field_storage_pre_load() - // - storage backend's hook_field_storage_load() - // - field-type module's hook_field_load() - // - hook_field_attach_load() - - // Invoke hook_field_storage_pre_load(): let any module load field - // data before the storage engine, accumulating along the way. - $skip_fields = array(); - foreach (module_implements('field_storage_pre_load') as $module) { - $function = $module . '_field_storage_pre_load'; - $function($entity_type, $queried_entities, $age, $skip_fields, $options); - } - - $instances = array(); - - // Collect the storage backends used by the remaining fields in the entities. - $storages = array(); - foreach ($queried_entities as $entity) { - list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); - $instances = _field_invoke_get_instances($entity_type, $bundle, $options); - - foreach ($instances as $instance) { - $field_name = $instance['field_name']; - $field_id = $instance['field_id']; - // Make sure all fields are present at least as empty arrays. - if (!isset($queried_entities[$id]->{$field_name})) { - $queried_entities[$id]->{$field_name} = array(); - } - // Collect the storage backend if the field has not been loaded yet. - if (!isset($skip_fields[$field_id])) { - $field = $field_info[$field_id]; - $storages[$field['storage']['type']][$field_id][] = $load_current ? $id : $vid; - } - } - } - - // Invoke hook_field_storage_load() on the relevant storage backends. - foreach ($storages as $storage => $fields) { - $storage_info = field_info_storage_types($storage); - module_invoke($storage_info['module'], 'field_storage_load', $entity_type, $queried_entities, $age, $fields, $options); - } - - // Invoke field-type module's hook_field_load(). - _field_invoke_multiple('load', $entity_type, $queried_entities, $age, $options); - - // Invoke hook_field_attach_load(): let other modules act on loading the - // entitiy. - module_invoke_all('field_attach_load', $entity_type, $queried_entities, $age, $options); - - // Build cache data. - if ($cache_write) { - foreach ($queried_entities as $id => $entity) { - $data = array(); - list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); - $instances = field_info_instances($entity_type, $bundle); - foreach ($instances as $instance) { - $data[$instance['field_name']] = $queried_entities[$id]->{$instance['field_name']}; - } - $cid = "field:$entity_type:$id"; - cache('field')->set($cid, $data); - } - } - } -} - -/** - * Load all fields for previous versions of a group of entities. - * - * Loading different versions of the same entities is not supported, and should - * be done by separate calls to the function. - * - * field_attach_load_revision() is automatically called by the default entity - * controller class, and thus, in most cases, doesn't need to be explicitly - * called by the entity type module. - * - * @param $entity_type - * The type of $entity; e.g. 'node' or 'user'. - * @param $entities - * An array of entities for which to load fields, keyed by entity ID. Each - * entity needs to have its 'bundle', 'id' and (if applicable) 'revision' - * keys filled. The function adds the loaded field data directly in the - * entity objects of the $entities array. - * @param $options - * An associative array of additional options. See field_attach_load() for - * details. - */ -function field_attach_load_revision($entity_type, $entities, $options = array()) { - return field_attach_load($entity_type, $entities, FIELD_LOAD_REVISION, $options); -} - -/** - * Perform field validation against the field data in an entity. - * - * This function does not perform field widget validation on form - * submissions. It is intended to be called during API save - * operations. Use field_attach_form_validate() to validate form - * submissions. - * - * @param $entity_type - * The type of $entity; e.g. 'node' or 'user'. - * @param $entity - * The entity with fields to validate. - * @throws FieldValidationException - * If validation errors are found, a FieldValidationException is thrown. The - * 'errors' property contains the array of errors, keyed by field name, - * language and delta. - */ -function field_attach_validate($entity_type, $entity) { - $errors = array(); - // Check generic, field-type-agnostic errors first. - _field_invoke_default('validate', $entity_type, $entity, $errors); - // Check field-type specific errors. - _field_invoke('validate', $entity_type, $entity, $errors); - - // Let other modules validate the entity. - // Avoid module_invoke_all() to let $errors be taken by reference. - foreach (module_implements('field_attach_validate') as $module) { - $function = $module . '_field_attach_validate'; - $function($entity_type, $entity, $errors); - } - - if ($errors) { - throw new FieldValidationException($errors); - } -} - -/** - * Perform field validation against form-submitted field values. - * - * There are two levels of validation for fields in forms: widget - * validation, and field validation. - * - Widget validation steps are specific to a given widget's own form - * structure and UI metaphors. They are executed through FAPI's - * #element_validate property during normal form validation. - * - Field validation steps are common to a given field type, independently of - * the specific widget being used in a given form. They are defined in the - * field type's implementation of hook_field_validate(). - * - * This function performs field validation in the context of a form - * submission. It converts field validation errors into form errors - * on the correct form elements. Fieldable entity types should call - * this function during their own form validation function. - * - * @param $entity_type - * The type of $entity; e.g. 'node' or 'user'. - * @param $entity - * The entity being submitted. The 'bundle', 'id' and (if applicable) - * 'revision' keys should be present. The actual field values will be read - * from $form_state['values']. - * @param $form - * The form structure where field elements are attached to. This might be a - * full form structure, or a sub-element of a larger form. - * @param $form_state - * An associative array containing the current state of the form. - */ -function field_attach_form_validate($entity_type, $entity, $form, &$form_state) { - // Extract field values from submitted values. - _field_invoke_default('extract_form_values', $entity_type, $entity, $form, $form_state); - - // Perform field_level validation. - try { - field_attach_validate($entity_type, $entity); - } - catch (FieldValidationException $e) { - // Pass field-level validation errors back to widgets for accurate error - // flagging. - foreach ($e->errors as $field_name => $field_errors) { - foreach ($field_errors as $langcode => $errors) { - $field_state = field_form_get_state($form['#parents'], $field_name, $langcode, $form_state); - $field_state['errors'] = $errors; - field_form_set_state($form['#parents'], $field_name, $langcode, $form_state, $field_state); - } - } - _field_invoke_default('form_errors', $entity_type, $entity, $form, $form_state); - } -} - -/** - * 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 $entity_type - * The type of $entity; e.g. 'node' or 'user'. - * @param $entity - * The entity being submitted. The 'bundle', 'id' and (if applicable) - * 'revision' keys should be present. The actual field values will be read - * from $form_state['values']. - * @param $form - * The form structure where field elements are attached to. This might be a - * full form structure, or a sub-element of a larger form. - * @param $form_state - * An associative array containing the current state of the form. - */ -function field_attach_submit($entity_type, $entity, $form, &$form_state) { - // Extract field values from submitted values. - _field_invoke_default('extract_form_values', $entity_type, $entity, $form, $form_state); - - _field_invoke_default('submit', $entity_type, $entity, $form, $form_state); - - // Let other modules act on submitting the entity. - // Avoid module_invoke_all() to let $form_state be taken by reference. - foreach (module_implements('field_attach_submit') as $module) { - $function = $module . '_field_attach_submit'; - $function($entity_type, $entity, $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 $entity_type - * The type of $entity; e.g. 'node' or 'user'. - * @param $entity - * The entity with fields to process. - */ -function field_attach_presave($entity_type, $entity) { - _field_invoke('presave', $entity_type, $entity); - - // Let other modules act on presaving the entity. - module_invoke_all('field_attach_presave', $entity_type, $entity); -} - -/** - * Save field data for a new entity. - * - * The passed-in entity must already contain its id and (if applicable) - * revision id attributes. - * Default values (if any) will be saved for fields not present in the - * $entity. - * - * @param $entity_type - * The type of $entity; e.g. 'node' or 'user'. - * @param $entity - * The entity with fields to save. - * @return - * Default values (if any) will be added to the $entity parameter for fields - * it leaves unspecified. - */ -function field_attach_insert($entity_type, $entity) { - _field_invoke_default('insert', $entity_type, $entity); - _field_invoke('insert', $entity_type, $entity); - - list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); - - // Let any module insert field data before the storage engine, accumulating - // saved fields along the way. - $skip_fields = array(); - foreach (module_implements('field_storage_pre_insert') as $module) { - $function = $module . '_field_storage_pre_insert'; - $function($entity_type, $entity, $skip_fields); - } - - // Collect the storage backends used by the remaining fields in the entities. - $storages = array(); - foreach (field_info_instances($entity_type, $bundle) as $instance) { - $field = field_info_field_by_id($instance['field_id']); - $field_id = $field['id']; - $field_name = $field['field_name']; - if (!empty($entity->$field_name)) { - // Collect the storage backend if the field has not been written yet. - if (!isset($skip_fields[$field_id])) { - $storages[$field['storage']['type']][$field_id] = $field_id; - } - } - } - - // Field storage backends save any remaining unsaved fields. - foreach ($storages as $storage => $fields) { - $storage_info = field_info_storage_types($storage); - module_invoke($storage_info['module'], 'field_storage_write', $entity_type, $entity, FIELD_STORAGE_INSERT, $fields); - } - - // Let other modules act on inserting the entity. - module_invoke_all('field_attach_insert', $entity_type, $entity); - - $entity_info = entity_get_info($entity_type); -} - -/** - * Save field data for an existing entity. - * - * @param $entity_type - * The type of $entity; e.g. 'node' or 'user'. - * @param $entity - * The entity with fields to save. - */ -function field_attach_update($entity_type, $entity) { - _field_invoke('update', $entity_type, $entity); - - list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); - - // Let any module update field data before the storage engine, accumulating - // saved fields along the way. - $skip_fields = array(); - foreach (module_implements('field_storage_pre_update') as $module) { - $function = $module . '_field_storage_pre_update'; - $function($entity_type, $entity, $skip_fields); - } - - // Collect the storage backends used by the remaining fields in the entities. - $storages = array(); - foreach (field_info_instances($entity_type, $bundle) as $instance) { - $field = field_info_field_by_id($instance['field_id']); - $field_id = $field['id']; - $field_name = $field['field_name']; - // Leave the field untouched if $entity comes with no $field_name property, - // but empty the field if it comes as a NULL value or an empty array. - // Function property_exists() is slower, so we catch the more frequent - // cases where it's an empty array with the faster isset(). - if (isset($entity->$field_name) || property_exists($entity, $field_name)) { - // Collect the storage backend if the field has not been written yet. - if (!isset($skip_fields[$field_id])) { - $storages[$field['storage']['type']][$field_id] = $field_id; - } - } - } - - // Field storage backends save any remaining unsaved fields. - foreach ($storages as $storage => $fields) { - $storage_info = field_info_storage_types($storage); - module_invoke($storage_info['module'], 'field_storage_write', $entity_type, $entity, FIELD_STORAGE_UPDATE, $fields); - } - - // Let other modules act on updating the entity. - module_invoke_all('field_attach_update', $entity_type, $entity); - - $entity_info = entity_get_info($entity_type); - if ($entity_info['field cache']) { - cache('field')->delete("field:$entity_type:$id"); - } -} - -/** - * Delete field data for an existing entity. This deletes all - * revisions of field data for the entity. - * - * @param $entity_type - * The type of $entity; e.g. 'node' or 'user'. - * @param $entity - * The entity whose field data to delete. - */ -function field_attach_delete($entity_type, $entity) { - _field_invoke('delete', $entity_type, $entity); - - list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); - - // Collect the storage backends used by the fields in the entities. - $storages = array(); - foreach (field_info_instances($entity_type, $bundle) as $instance) { - $field = field_info_field_by_id($instance['field_id']); - $field_id = $field['id']; - $storages[$field['storage']['type']][$field_id] = $field_id; - } - - // Field storage backends delete their data. - foreach ($storages as $storage => $fields) { - $storage_info = field_info_storage_types($storage); - module_invoke($storage_info['module'], 'field_storage_delete', $entity_type, $entity, $fields); - } - - // Let other modules act on deleting the entity. - module_invoke_all('field_attach_delete', $entity_type, $entity); - - $entity_info = entity_get_info($entity_type); - if ($entity_info['field cache']) { - cache('field')->delete("field:$entity_type:$id"); - } -} - -/** - * Delete field data for a single revision of an existing entity. The - * passed entity must have a revision id attribute. - * - * @param $entity_type - * The type of $entity; e.g. 'node' or 'user'. - * @param $entity - * The entity with fields to save. - */ -function field_attach_delete_revision($entity_type, $entity) { - _field_invoke('delete_revision', $entity_type, $entity); - - list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); - - // Collect the storage backends used by the fields in the entities. - $storages = array(); - foreach (field_info_instances($entity_type, $bundle) as $instance) { - $field = field_info_field_by_id($instance['field_id']); - $field_id = $field['id']; - $storages[$field['storage']['type']][$field_id] = $field_id; - } - - // Field storage backends delete their data. - foreach ($storages as $storage => $fields) { - $storage_info = field_info_storage_types($storage); - module_invoke($storage_info['module'], 'field_storage_delete_revision', $entity_type, $entity, $fields); - } - - // Let other modules act on deleting the revision. - module_invoke_all('field_attach_delete_revision', $entity_type, $entity); -} - -/** - * Prepare field data prior to display. - * - * This function lets field types and formatters load additional data - * needed for display that is not automatically loaded during - * field_attach_load(). It accepts an array of entities to allow query - * optimisation when displaying lists of entities. - * - * field_attach_prepare_view() and field_attach_view() are two halves - * of the same operation. It is safe to call - * field_attach_prepare_view() multiple times on the same entity - * before calling field_attach_view() on it, but calling any Field - * API operation on an entity between passing that entity to these two - * functions may yield incorrect results. - * - * @param $entity_type - * The type of $entities; e.g. 'node' or 'user'. - * @param $entities - * An array of entities, keyed by entity id. - * @param $view_mode - * View mode, e.g. 'full', 'teaser'... - * @param $langcode - * (Optional) The language the field values are to be shown in. If no language - * is provided the current language is used. - */ -function field_attach_prepare_view($entity_type, $entities, $view_mode, $langcode = NULL) { - $options = array('language' => array()); - - // To ensure hooks are only run once per entity, only process items without - // the _field_view_prepared flag. - // @todo: resolve this more generally for both entity and field level hooks. - $prepare = array(); - foreach ($entities as $id => $entity) { - if (empty($entity->_field_view_prepared)) { - // Add this entity to the items to be prepared. - $prepare[$id] = $entity; - - // Determine the actual language to display for each field, given the - // languages available in the field data. - $options['language'][$id] = field_language($entity_type, $entity, NULL, $langcode); - - // Mark this item as prepared. - $entity->_field_view_prepared = TRUE; - } - } - - $null = NULL; - // First let the field types do their preparation. - _field_invoke_multiple('prepare_view', $entity_type, $prepare, $null, $null, $options); - // Then let the formatters do their own specific massaging. - // field_default_prepare_view() takes care of dispatching to the correct - // formatters according to the display settings for the view mode. - _field_invoke_multiple_default('prepare_view', $entity_type, $prepare, $view_mode, $null, $options); -} - -/** - * Returns a renderable array for the fields on an entity. - * - * Each field is displayed according to the display options specified in the - * $instance definition for the given $view_mode. - * - * field_attach_prepare_view() and field_attach_view() are two halves - * of the same operation. It is safe to call - * field_attach_prepare_view() multiple times on the same entity - * before calling field_attach_view() on it, but calling any Field - * API operation on an entity between passing that entity to these two - * functions may yield incorrect results. - * - * Sample structure: - * @code - * array( - * 'field_foo' => array( - * '#theme' => 'field', - * '#title' => the label of the field instance, - * '#label_display' => the label display mode, - * '#object' => the fieldable entity being displayed, - * '#entity_type' => the type of the entity being displayed, - * '#language' => the language of the field values being displayed, - * '#view_mode' => the view mode, - * '#field_name' => the name of the field, - * '#field_type' => the type of the field, - * '#formatter' => the name of the formatter, - * '#items' => the field values being displayed, - * // The element's children are the formatted values returned by - * // hook_field_formatter_view(). - * ), - * ); - * @endcode - * - * @param $entity_type - * The type of $entity; e.g. 'node' or 'user'. - * @param $entity - * The entity with fields to render. - * @param $view_mode - * View mode, e.g. 'full', 'teaser'... - * @param $langcode - * The language the field values are to be shown in. If no language is - * provided the current language is used. - * @return - * A renderable array for the field values. - */ -function field_attach_view($entity_type, $entity, $view_mode, $langcode = NULL) { - // Determine the actual language to display for each field, given the - // languages available in the field data. - $display_language = field_language($entity_type, $entity, NULL, $langcode); - $options = array('language' => $display_language); - - // Invoke field_default_view(). - $null = NULL; - $output = _field_invoke_default('view', $entity_type, $entity, $view_mode, $null, $options); - - // Add custom weight handling. - list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); - $output['#pre_render'][] = '_field_extra_fields_pre_render'; - $output['#entity_type'] = $entity_type; - $output['#bundle'] = $bundle; - - // Let other modules alter the renderable array. - $context = array( - 'entity_type' => $entity_type, - 'entity' => $entity, - 'view_mode' => $view_mode, - 'display' => $view_mode, - 'language' => $langcode, - ); - drupal_alter('field_attach_view', $output, $context); - - // Reset the _field_view_prepared flag set in field_attach_prepare_view(), - // in case the same entity is displayed with different settings later in - // the request. - unset($entity->_field_view_prepared); - - return $output; -} - -/** - * Populate the template variables with the field values available for rendering. - * - * The $variables array will be populated with all the field instance values - * associated with the given entity type, keyed by field name; in case of - * translatable fields the language currently chosen for display will be - * selected. - * - * @param $entity_type - * The type of $entity; e.g. 'node' or 'user'. - * @param $entity - * The entity with fields to render. - * @param $element - * The structured array containing the values ready for rendering. - * @param $variables - * The variables array is passed by reference and will be populated with field - * values. - */ -function field_attach_preprocess($entity_type, $entity, $element, &$variables) { - list(, , $bundle) = entity_extract_ids($entity_type, $entity); - - foreach (field_info_instances($entity_type, $bundle) as $instance) { - $field_name = $instance['field_name']; - if (isset($element[$field_name]['#language'])) { - $langcode = $element[$field_name]['#language']; - $variables[$field_name] = isset($entity->{$field_name}[$langcode]) ? $entity->{$field_name}[$langcode] : NULL; - } - } - - // Let other modules make changes to the $variables array. - $context = array( - 'entity_type' => $entity_type, - 'entity' => $entity, - 'element' => $element, - ); - drupal_alter('field_attach_preprocess', $variables, $context); -} - -/** - * Prepares an entity for translation. - * - * This function is used to fill-in the form default values for Field API fields - * while performing entity translation. By default it copies all the source - * values in the given source language to the new entity and assigns them the - * target language. - * - * This is used as part of the 'per entity' translation pattern, which is - * implemented only for nodes by translation.module. Other entity types may be - * supported through contributed modules. - * - * @param $entity_type - * The type of $entity; e.g. 'node' or 'user'. - * @param $entity - * The entity to be prepared for translation. - * @param $langcode - * The language the entity has to be translated in. - * @param $source_entity - * The source entity holding the field values to be translated. - * @param $source_langcode - * The source language from which translate. - */ -function field_attach_prepare_translation($entity_type, $entity, $langcode, $source_entity, $source_langcode) { - $options = array('language' => $langcode); - // Copy source field values into the entity to be prepared. - _field_invoke_default('prepare_translation', $entity_type, $entity, $source_entity, $source_langcode, $options); - // Let field types handle their own advanced translation pattern if needed. - _field_invoke('prepare_translation', $entity_type, $entity, $source_entity, $source_langcode, $options); - // Let other modules alter the entity translation. - $context = array( - 'entity_type' => $entity_type, - 'langcode' => $langcode, - 'source_entity' => $source_entity, - 'source_langcode' => $source_langcode, - ); - drupal_alter('field_attach_prepare_translation', $entity, $context); -} - -/** - * Notify field.module that a new bundle was created. - * - * The default SQL-based storage doesn't need to do anything about it, but - * others might. - * - * @param $entity_type - * The entity type to which the bundle is bound. - * @param $bundle - * The name of the newly created bundle. - */ -function field_attach_create_bundle($entity_type, $bundle) { - // Clear the cache. - field_cache_clear(); - - // Let other modules act on creating the bundle. - module_invoke_all('field_attach_create_bundle', $entity_type, $bundle); -} - -/** - * Notify field.module that a bundle was renamed. - * - * @param $entity_type - * The entity type to which the bundle is bound. - * @param $bundle_old - * The previous name of the bundle. - * @param $bundle_new - * The new name of the bundle. - */ -function field_attach_rename_bundle($entity_type, $bundle_old, $bundle_new) { - db_update('field_config_instance') - ->fields(array('bundle' => $bundle_new)) - ->condition('entity_type', $entity_type) - ->condition('bundle', $bundle_old) - ->execute(); - - // Clear the cache. - field_cache_clear(); - - // Update bundle settings. - $settings = variable_get('field_bundle_settings_' . $entity_type . '__' . $bundle_old, array()); - variable_set('field_bundle_settings_' . $entity_type . '__' . $bundle_new, $settings); - variable_del('field_bundle_settings_' . $entity_type . '__' . $bundle_old); - - // Let other modules act on renaming the bundle. - module_invoke_all('field_attach_rename_bundle', $entity_type, $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 $entity_type - * The entity type to which the bundle is bound. - * @param $bundle - * The bundle to delete. - */ -function field_attach_delete_bundle($entity_type, $bundle) { - // First, delete the instances themselves. field_read_instances() must be - // used here since field_info_instances() does not return instances for - // disabled entity types or bundles. - $instances = field_read_instances(array('entity_type' => $entity_type, 'bundle' => $bundle), array('include_inactive' => 1)); - foreach ($instances as $instance) { - field_delete_instance($instance); - } - - // Clear the cache. - field_cache_clear(); - - // Clear bundle display settings. - variable_del('field_bundle_settings_' . $entity_type . '__' . $bundle); - - // Let other modules act on deleting the bundle. - module_invoke_all('field_attach_delete_bundle', $entity_type, $bundle, $instances); -} - - -/** - * @} End of "defgroup field_attach" - */ diff --git a/modules/field/field.crud.inc b/modules/field/field.crud.inc deleted file mode 100644 index e34c0c5282b..00000000000 --- a/modules/field/field.crud.inc +++ /dev/null @@ -1,971 +0,0 @@ -<?php - -/** - * @file - * Field CRUD API, handling field and field instance creation and deletion. - */ - -/** - * @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 Field API data structures @endlink. - */ - -/** - * Creates a field. - * - * This function does not bind the field to any bundle; use - * field_create_instance() for that. - * - * @param $field - * A field definition array. The field_name and type properties are required. - * Other properties, if omitted, will be given the following default values: - * - cardinality: 1 - * - locked: FALSE - * - indexes: the field-type indexes, specified by the field type's - * hook_field_schema(). The indexes specified in $field are added - * to those default indexes. It is possible to override the - * definition of a field-type index by providing an index with the - * same name, or to remove it by redefining it as an empty array - * of columns. Overriding field-type indexes should be done - * carefully, for it might seriously affect the site's performance. - * - settings: each omitted setting is given the default value defined in - * hook_field_info(). - * - storage: - * - type: the storage backend specified in the 'field_storage_default' - * system variable. - * - settings: each omitted setting is given the default value specified in - * hook_field_storage_info(). - * @return - * The $field array with the id property filled in. - * @throw - * FieldException - * - * See: @link field Field API data structures @endlink. - */ -function field_create_field($field) { - // Field name is required. - if (empty($field['field_name'])) { - throw new FieldException('Attempt to create an unnamed field.'); - } - // Field type is required. - if (empty($field['type'])) { - throw new FieldException('Attempt to create a field with no type.'); - } - // Field name cannot contain invalid characters. - if (!preg_match('/^[_a-z]+[_a-z0-9]*$/', $field['field_name'])) { - throw new FieldException('Attempt to create a field with invalid characters. Only lowercase alphanumeric characters and underscores are allowed, and only lowercase letters and underscore are allowed as the first character'); - } - - // Field name cannot be longer than 32 characters. We use drupal_strlen() - // because the DB layer assumes that column widths are given in characters, - // not bytes. - if (drupal_strlen($field['field_name']) > 32) { - throw new FieldException(t('Attempt to create a field with a name longer than 32 characters: %name', - array('%name' => $field['field_name']))); - } - - // Ensure the field name is unique over active and disabled fields. - // We do not care about deleted fields. - $prior_field = field_read_field($field['field_name'], array('include_inactive' => TRUE)); - if (!empty($prior_field)) { - $message = $prior_field['active']? - t('Attempt to create field name %name which already exists and is active.', array('%name' => $field['field_name'])): - t('Attempt to create field name %name which already exists, although it is inactive.', array('%name' => $field['field_name'])); - throw new FieldException($message); - } - - // Disallow reserved field names. This can't prevent all field name - // collisions with existing entity properties, but some is better - // than none. - foreach (entity_get_info() as $type => $info) { - if (in_array($field['field_name'], $info['entity keys'])) { - throw new FieldException(t('Attempt to create field name %name which is reserved by entity type %type.', array('%name' => $field['field_name'], '%type' => $type))); - } - } - - $field += array( - 'entity_types' => array(), - 'cardinality' => 1, - 'translatable' => FALSE, - 'locked' => FALSE, - 'settings' => array(), - 'storage' => array(), - 'deleted' => 0, - ); - - // Check that the field type is known. - $field_type = field_info_field_types($field['type']); - if (!$field_type) { - throw new FieldException(t('Attempt to create a field of unknown type %type.', array('%type' => $field['type']))); - } - // Create all per-field-type properties (needed here as long as we have - // settings that impact column definitions). - $field['settings'] += field_info_field_settings($field['type']); - $field['module'] = $field_type['module']; - $field['active'] = 1; - - // Provide default storage. - $field['storage'] += array( - 'type' => variable_get('field_storage_default', 'field_sql_storage'), - 'settings' => array(), - ); - // Check that the storage type is known. - $storage_type = field_info_storage_types($field['storage']['type']); - if (!$storage_type) { - throw new FieldException(t('Attempt to create a field with unknown storage type %type.', array('%type' => $field['storage']['type']))); - } - // Provide default storage settings. - $field['storage']['settings'] += field_info_storage_settings($field['storage']['type']); - $field['storage']['module'] = $storage_type['module']; - $field['storage']['active'] = 1; - // Collect storage information. - module_load_install($field['module']); - $schema = (array) module_invoke($field['module'], 'field_schema', $field); - $schema += array('columns' => array(), 'indexes' => array(), 'foreign keys' => array()); - // 'columns' are hardcoded in the field type. - $field['columns'] = $schema['columns']; - // 'foreign keys' are hardcoded in the field type. - $field['foreign keys'] = $schema['foreign keys']; - // 'indexes' can be both hardcoded in the field type, and specified in the - // incoming $field definition. - $field += array( - 'indexes' => array(), - ); - $field['indexes'] += $schema['indexes']; - - // The serialized 'data' column contains everything from $field that does not - // have its own column and is not automatically populated when the field is - // read. - $data = $field; - unset($data['columns'], $data['field_name'], $data['type'], $data['active'], $data['module'], $data['storage_type'], $data['storage_active'], $data['storage_module'], $data['locked'], $data['cardinality'], $data['deleted']); - // Additionally, do not save the 'bundles' property populated by - // field_info_field(). - unset($data['bundles']); - - $record = array( - 'field_name' => $field['field_name'], - 'type' => $field['type'], - 'module' => $field['module'], - 'active' => $field['active'], - 'storage_type' => $field['storage']['type'], - 'storage_module' => $field['storage']['module'], - 'storage_active' => $field['storage']['active'], - 'locked' => $field['locked'], - 'data' => $data, - 'cardinality' => $field['cardinality'], - 'translatable' => $field['translatable'], - 'deleted' => $field['deleted'], - ); - - // Store the field and get the id back. - drupal_write_record('field_config', $record); - $field['id'] = $record['id']; - - // Invoke hook_field_storage_create_field after the field is - // complete (e.g. it has its id). - try { - // Invoke hook_field_storage_create_field after - // drupal_write_record() sets the field id. - module_invoke($storage_type['module'], 'field_storage_create_field', $field); - } - catch (Exception $e) { - // If storage creation failed, remove the field_config record before - // rethrowing the exception. - db_delete('field_config') - ->condition('id', $field['id']) - ->execute(); - throw $e; - } - - // Clear caches - field_cache_clear(TRUE); - - // Invoke external hooks after the cache is cleared for API consistency. - module_invoke_all('field_create_field', $field); - - return $field; -} - -/** - * Updates a field. - * - * Any module may forbid any update for any reason. For example, the - * field's storage module might forbid an update if it would change - * the storage schema while data for the field exists. A field type - * module might forbid an update if it would change existing data's - * semantics, or if there are external dependencies on field settings - * that cannot be updated. - * - * @param $field - * A field structure. $field['field_name'] must provided; it - * identifies the field that will be updated to match this - * structure. Any other properties of the field that are not - * specified in $field will be left unchanged, so it is not - * necessary to pass in a fully populated $field structure. - * @return - * Throws a FieldException if the update cannot be performed. - * @see field_create_field() - */ -function field_update_field($field) { - // Check that the specified field exists. - $prior_field = field_read_field($field['field_name']); - if (empty($prior_field)) { - throw new FieldException('Attempt to update a non-existent field.'); - } - - // Use the prior field values for anything not specifically set by the new - // field to be sure that all values are set. - $field += $prior_field; - $field['settings'] += $prior_field['settings']; - - // Some updates are always disallowed. - if ($field['type'] != $prior_field['type']) { - throw new FieldException("Cannot change an existing field's type."); - } - if ($field['entity_types'] != $prior_field['entity_types']) { - throw new FieldException("Cannot change an existing field's entity_types property."); - } - if ($field['storage']['type'] != $prior_field['storage']['type']) { - throw new FieldException("Cannot change an existing field's storage type."); - } - - // Collect the new storage information, since what is in - // $prior_field may no longer be right. - module_load_install($field['module']); - $schema = (array) module_invoke($field['module'], 'field_schema', $field); - $schema += array('columns' => array(), 'indexes' => array()); - // 'columns' are hardcoded in the field type. - $field['columns'] = $schema['columns']; - // 'indexes' can be both hardcoded in the field type, and specified in the - // incoming $field definition. - $field += array( - 'indexes' => array(), - ); - $field['indexes'] += $schema['indexes']; - - $has_data = field_has_data($field); - - // See if any module forbids the update by throwing an exception. - foreach (module_implements('field_update_forbid') as $module) { - $function = $module . '_field_update_forbid'; - $function($field, $prior_field, $has_data); - } - - // Tell the storage engine to update the field. Do this before - // saving the new definition since it still might fail. - $storage_type = field_info_storage_types($field['storage']['type']); - module_invoke($storage_type['module'], 'field_storage_update_field', $field, $prior_field, $has_data); - - // Save the new field definition. @todo: refactor with - // field_create_field. - - // The serialized 'data' column contains everything from $field that does not - // have its own column and is not automatically populated when the field is - // read. - $data = $field; - unset($data['columns'], $data['field_name'], $data['type'], $data['locked'], $data['module'], $data['cardinality'], $data['active'], $data['deleted']); - // Additionally, do not save the 'bundles' property populated by - // field_info_field(). - unset($data['bundles']); - - $field['data'] = $data; - - // Store the field and create the id. - $primary_key = array('id'); - drupal_write_record('field_config', $field, $primary_key); - - // Clear caches - field_cache_clear(TRUE); - - // Invoke external hooks after the cache is cleared for API consistency. - module_invoke_all('field_update_field', $field, $prior_field, $has_data); -} - -/** - * Reads a single field record directly from the database. - * - * Generally, you should use the field_info_field() instead. - * - * This function will not return deleted fields. Use - * field_read_fields() instead for this purpose. - * - * @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. Setting - * $include_additional['include_inactive'] to TRUE will override this - * behavior. - * @return - * A field definition array, 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; -} - -/** - * Reads 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. If - * $include_additional['include_deleted'] is TRUE, the array is keyed - * by field id, otherwise it is keyed by field name. - */ -function field_read_fields($params = array(), $include_additional = array()) { - $query = db_select('field_config', 'fc', array('fetch' => PDO::FETCH_ASSOC)); - $query->fields('fc'); - - // Turn the conditions into a query. - foreach ($params as $key => $value) { - $query->condition($key, $value); - } - if (!isset($include_additional['include_inactive']) || !$include_additional['include_inactive']) { - $query - ->condition('fc.active', 1) - ->condition('fc.storage_active', 1); - } - $include_deleted = (isset($include_additional['include_deleted']) && $include_additional['include_deleted']); - if (!$include_deleted) { - $query->condition('fc.deleted', 0); - } - - $fields = array(); - $results = $query->execute(); - foreach ($results as $record) { - $field = unserialize($record['data']); - $field['id'] = $record['id']; - $field['field_name'] = $record['field_name']; - $field['type'] = $record['type']; - $field['module'] = $record['module']; - $field['active'] = $record['active']; - $field['storage']['type'] = $record['storage_type']; - $field['storage']['module'] = $record['storage_module']; - $field['storage']['active'] = $record['storage_active']; - $field['locked'] = $record['locked']; - $field['cardinality'] = $record['cardinality']; - $field['translatable'] = $record['translatable']; - $field['deleted'] = $record['deleted']; - - module_invoke_all('field_read_field', $field); - - // Populate storage information. - module_load_install($field['module']); - $schema = (array) module_invoke($field['module'], 'field_schema', $field); - $schema += array('columns' => array(), 'indexes' => array()); - $field['columns'] = $schema['columns']; - - $field_name = $field['field_name']; - if ($include_deleted) { - $field_name = $field['id']; - } - $fields[$field_name] = $field; - } - return $fields; -} - -/** - * Marks a field and its instances and data for deletion. - * - * @param $field_name - * The field name to delete. - */ -function field_delete_field($field_name) { - // Delete all non-deleted instances. - $field = field_info_field($field_name); - if (isset($field['bundles'])) { - foreach ($field['bundles'] as $entity_type => $bundles) { - foreach ($bundles as $bundle) { - $instance = field_info_instance($entity_type, $field_name, $bundle); - field_delete_instance($instance, FALSE); - } - } - } - - // Mark field data for deletion. - module_invoke($field['storage']['module'], 'field_storage_delete_field', $field); - - // Mark the field for deletion. - db_update('field_config') - ->fields(array('deleted' => 1)) - ->condition('field_name', $field_name) - ->execute(); - - // Clear the cache. - field_cache_clear(TRUE); - - module_invoke_all('field_delete_field', $field); -} - -/** - * Creates an instance of a field, binding it to a bundle. - * - * @param $instance - * A field instance definition array. The field_name, entity_type and - * bundle properties are required. Other properties, if omitted, - * will be given the following default values: - * - label: the field name - * - description: empty string - * - required: FALSE - * - default_value_function: empty string - * - settings: each omitted setting is given the default value specified in - * hook_field_info(). - * - widget: - * - type: the default widget specified in hook_field_info(). - * - settings: each omitted setting is given the default value specified in - * hook_field_widget_info(). - * - display: - * Settings for the 'default' view mode will be added if not present, and - * each view mode in the definition will be completed with the following - * default values: - * - label: 'above' - * - type: the default formatter specified in hook_field_info(). - * - settings: each omitted setting is given the default value specified in - * hook_field_formatter_info(). - * View modes not present in the definition are left empty, and the field - * will not be displayed in this mode. - * - * @return - * The $instance array with the id property filled in. - * @throw - * FieldException - * - * See: @link field Field API data structures @endlink. - */ -function field_create_instance($instance) { - $field = field_read_field($instance['field_name']); - if (empty($field)) { - throw new FieldException(t("Attempt to create an instance of a field @field_name that doesn't exist or is currently inactive.", array('@field_name' => $instance['field_name']))); - } - // Check that the required properties exists. - if (empty($instance['entity_type'])) { - throw new FieldException(t('Attempt to create an instance of field @field_name without an entity type.', array('@field_name' => $instance['field_name']))); - } - if (empty($instance['bundle'])) { - throw new FieldException(t('Attempt to create an instance of field @field_name without a bundle.', array('@field_name' => $instance['field_name']))); - } - // Check that the field can be attached to this entity type. - if (!empty($field['entity_types']) && !in_array($instance['entity_type'], $field['entity_types'])) { - throw new FieldException(t('Attempt to create an instance of field @field_name on forbidden entity type @entity_type.', array('@field_name' => $instance['field_name'], '@entity_type' => $instance['entity_type']))); - } - - // Set the field id. - $instance['field_id'] = $field['id']; - - // Note that we do *not* prevent creating a field on non-existing bundles, - // because that would break the 'Body as field' upgrade for contrib - // node types. - - // TODO: Check that the widget type is known and can handle the field type ? - // TODO: Check that the formatters are known and can handle the field type ? - // TODO: Check that the display view modes are known for the entity type ? - // Those checks should probably happen in _field_write_instance() ? - // Problem : this would mean that a UI module cannot update an instance with a disabled formatter. - - // Ensure the field instance is unique within the bundle. - // We only check for instances of active fields, since adding an instance of - // a disabled field is not supported. - $prior_instance = field_read_instance($instance['entity_type'], $instance['field_name'], $instance['bundle']); - if (!empty($prior_instance)) { - $message = t('Attempt to create an instance of field @field_name on bundle @bundle that already has an instance of that field.', array('@field_name' => $instance['field_name'], '@bundle' => $instance['bundle'])); - throw new FieldException($message); - } - - _field_write_instance($instance); - - // Clear caches - field_cache_clear(); - - // Invoke external hooks after the cache is cleared for API consistency. - module_invoke_all('field_create_instance', $instance); - - return $instance; -} - -/** - * Updates an instance of a field. - * - * @param $instance - * An associative array representing an instance structure. The required - * keys and values are: - * - entity_type: The type of the entity the field is attached to. - * - bundle: The bundle this field belongs to. - * - field_name: The name of an existing field. - * Read-only_id properties are assigned automatically. 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(t('Attempt to update an instance of a nonexistent field @field.', array('@field' => $instance['field_name']))); - } - - // Check that the field instance exists (even if it is inactive, since we - // want to be able to replace inactive widgets with new ones). - $prior_instance = field_read_instance($instance['entity_type'], $instance['field_name'], $instance['bundle'], array('include_inactive' => TRUE)); - if (empty($prior_instance)) { - throw new FieldException(t("Attempt to update an instance of field @field on bundle @bundle that doesn't exist.", array('@field' => $instance['field_name'], '@bundle' => $instance['bundle']))); - } - - $instance['id'] = $prior_instance['id']; - $instance['field_id'] = $prior_instance['field_id']; - - _field_write_instance($instance, TRUE); - - // Clear caches. - field_cache_clear(); - - module_invoke_all('field_update_instance', $instance, $prior_instance); -} - -/** - * Stores an instance record in the field configuration database. - * - * @param $instance - * An instance structure. - * @param $update - * Whether this is a new or existing instance. - */ -function _field_write_instance($instance, $update = FALSE) { - $field = field_read_field($instance['field_name']); - $field_type = field_info_field_types($field['type']); - - // Set defaults. - $instance += array( - 'settings' => array(), - 'display' => array(), - 'widget' => array(), - 'required' => FALSE, - 'label' => $instance['field_name'], - 'description' => '', - 'deleted' => 0, - ); - - // Set default instance settings. - $instance['settings'] += field_info_instance_settings($field['type']); - - // Set default widget and settings. - $instance['widget'] += array( - // TODO: what if no 'default_widget' specified ? - 'type' => $field_type['default_widget'], - 'settings' => array(), - ); - // If no weight specified, make sure the field sinks at the bottom. - if (!isset($instance['widget']['weight'])) { - $max_weight = field_info_max_weight($instance['entity_type'], $instance['bundle'], 'form'); - $instance['widget']['weight'] = isset($max_weight) ? $max_weight + 1 : 0; - } - // Check widget module. - $widget_type = field_info_widget_types($instance['widget']['type']); - $instance['widget']['module'] = $widget_type['module']; - $instance['widget']['settings'] += field_info_widget_settings($instance['widget']['type']); - - // Make sure there are at least display settings for the 'default' view mode, - // and fill in defaults for each view mode specified in the definition. - $instance['display'] += array( - 'default' => array(), - ); - foreach ($instance['display'] as $view_mode => $display) { - $display += array( - 'label' => 'above', - 'type' => isset($field_type['default_formatter']) ? $field_type['default_formatter'] : 'hidden', - 'settings' => array(), - ); - if ($display['type'] != 'hidden') { - $formatter_type = field_info_formatter_types($display['type']); - $display['module'] = $formatter_type['module']; - $display['settings'] += field_info_formatter_settings($display['type']); - } - // If no weight specified, make sure the field sinks at the bottom. - if (!isset($display['weight'])) { - $max_weight = field_info_max_weight($instance['entity_type'], $instance['bundle'], $view_mode); - $display['weight'] = isset($max_weight) ? $max_weight + 1 : 0; - } - $instance['display'][$view_mode] = $display; - } - - // The serialized 'data' column contains everything from $instance that does - // not have its own column and is not automatically populated when the - // instance is read. - $data = $instance; - unset($data['id'], $data['field_id'], $data['field_name'], $data['entity_type'], $data['bundle'], $data['deleted']); - - $record = array( - 'field_id' => $instance['field_id'], - 'field_name' => $instance['field_name'], - 'entity_type' => $instance['entity_type'], - 'bundle' => $instance['bundle'], - 'data' => $data, - 'deleted' => $instance['deleted'], - ); - // We need to tell drupal_update_record() the primary keys to trigger an - // update. - if ($update) { - $record['id'] = $instance['id']; - $primary_key = array('id'); - } - else { - $primary_key = array(); - } - drupal_write_record('field_config_instance', $record, $primary_key); -} - -/** - * Reads a single instance record from the database. - * - * Generally, you should use field_info_instance() instead, as it - * provides caching and allows other modules the opportunity to - * append additional formatters, widgets, and other information. - * - * @param $entity_type - * The type of entity to which the field is bound. - * @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 - * has been deleted, or whose field is inactive. 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($entity_type, $field_name, $bundle, $include_additional = array()) { - $instances = field_read_instances(array('entity_type' => $entity_type, 'field_name' => $field_name, 'bundle' => $bundle), $include_additional); - return $instances ? current($instances) : FALSE; -} - -/** - * Reads in field instances that match an array of conditions. - * - * @param $param - * An array of properties to use in selecting a field - * instance. Valid keys include any column of the - * field_config_instance table. If NULL, all instances will be returned. - * @param $include_additional - * The default behavior of this function is to not return field - * instances that have been marked deleted, or whose field is inactive. - * Setting $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()) { - $include_inactive = isset($include_additional['include_inactive']) && $include_additional['include_inactive']; - $include_deleted = isset($include_additional['include_deleted']) && $include_additional['include_deleted']; - - $query = db_select('field_config_instance', 'fci', array('fetch' => PDO::FETCH_ASSOC)); - $query->join('field_config', 'fc', 'fc.id = fci.field_id'); - $query->fields('fci'); - - // Turn the conditions into a query. - foreach ($params as $key => $value) { - $query->condition('fci.' . $key, $value); - } - if (!$include_inactive) { - $query - ->condition('fc.active', 1) - ->condition('fc.storage_active', 1); - } - if (!$include_deleted) { - $query->condition('fc.deleted', 0); - $query->condition('fci.deleted', 0); - } - - $instances = array(); - $results = $query->execute(); - - foreach ($results as $record) { - // Filter out instances on unknown entity types (for instance because the - // module exposing them was disabled). - $entity_info = entity_get_info($record['entity_type']); - if ($include_inactive || $entity_info) { - $instance = unserialize($record['data']); - $instance['id'] = $record['id']; - $instance['field_id'] = $record['field_id']; - $instance['field_name'] = $record['field_name']; - $instance['entity_type'] = $record['entity_type']; - $instance['bundle'] = $record['bundle']; - $instance['deleted'] = $record['deleted']; - - module_invoke_all('field_read_instance', $instance); - $instances[] = $instance; - } - } - return $instances; -} - -/** - * Marks a field instance and its data for deletion. - * - * @param $instance - * An instance structure. - * @param $field_cleanup - * If TRUE, the field will be deleted as well if its last instance is being - * deleted. If FALSE, it is the caller's responsibility to handle the case of - * fields left without instances. Defaults to TRUE. - */ -function field_delete_instance($instance, $field_cleanup = TRUE) { - // Mark the field instance for deletion. - db_update('field_config_instance') - ->fields(array('deleted' => 1)) - ->condition('field_name', $instance['field_name']) - ->condition('entity_type', $instance['entity_type']) - ->condition('bundle', $instance['bundle']) - ->execute(); - - // Clear the cache. - field_cache_clear(); - - // Mark instance data for deletion. - $field = field_info_field($instance['field_name']); - module_invoke($field['storage']['module'], 'field_storage_delete_instance', $instance); - - // Let modules react to the deletion of the instance. - module_invoke_all('field_delete_instance', $instance); - - // Delete the field itself if we just deleted its last instance. - if ($field_cleanup && count($field['bundles']) == 0) { - field_delete_field($field['field_name']); - } -} - -/** - * @} End of "defgroup field_crud". - */ - -/** - * @defgroup field_purge Field API bulk data deletion - * @{ - * Clean up after Field API bulk deletion operations. - * - * Field API provides functions for deleting data attached to individual - * entities as well as deleting entire fields or field instances in a single - * operation. - * - * Deleting field data items for an entity with field_attach_delete() involves - * three separate operations: - * - Invoking the Field Type API hook_field_delete() for each field on the - * entity. The hook for each field type receives the entity and the specific - * field being deleted. A file field module might use this hook to delete - * uploaded files from the filesystem. - * - Invoking the Field Storage API hook_field_storage_delete() to remove - * data from the primary field storage. The hook implementation receives the - * entity being deleted and deletes data for all of the entity's bundle's - * fields. - * - Invoking the global Field Attach API hook_field_attach_delete() for all - * modules that implement it. Each hook implementation receives the entity - * being deleted and can operate on whichever subset of the entity's bundle's - * fields it chooses to. - * - * These hooks are invoked immediately when field_attach_delete() is - * called. Similar operations are performed for field_attach_delete_revision(). - * - * When a field, bundle, or field instance is deleted, it is not practical to - * invoke these hooks immediately on every affected entity in a single page - * request; there could be thousands or millions of them. Instead, the - * appropriate field data items, instances, and/or fields are marked as deleted - * so that subsequent load or query operations will not return them. Later, a - * separate process cleans up, or "purges", the marked-as-deleted data by going - * through the three-step process described above and, finally, removing - * deleted field and instance records. - * - * Purging field data is made somewhat tricky by the fact that, while - * field_attach_delete() has a complete entity to pass to the various deletion - * hooks, the Field API purge process only has the field data it has previously - * stored. It cannot reconstruct complete original entities to pass to the - * deletion hooks. It is even possible that the original entity to which some - * Field API data was attached has been itself deleted before the field purge - * operation takes place. - * - * Field API resolves this problem by using "pseudo-entities" during purge - * operations. A pseudo-entity contains only the information from the original - * entity that Field API knows about: entity type, id, revision id, and - * bundle. It also contains the field data for whichever field instance is - * currently being purged. For example, suppose that the node type 'story' used - * to contain a field called 'subtitle' but the field was deleted. If node 37 - * was a story with a subtitle, the pseudo-entity passed to the purge hooks - * would look something like this: - * - * @code - * $entity = stdClass Object( - * [nid] => 37, - * [vid] => 37, - * [type] => 'story', - * [subtitle] => array( - * [0] => array( - * 'value' => 'subtitle text', - * ), - * ), - * ); - * @endcode - */ - -/** - * Purges a batch of deleted Field API data, instances, or fields. - * - * This function will purge deleted field data on up to a specified maximum - * number of entities and then return. If a deleted field instance with no - * remaining data records is found, the instance itself will be purged. - * If a deleted field with no remaining field instances is found, the field - * itself will be purged. - * - * @param $batch_size - * The maximum number of field data records to purge before returning. - */ -function field_purge_batch($batch_size) { - // Retrieve all deleted field instances. We cannot use field_info_instances() - // because that function does not return deleted instances. - $instances = field_read_instances(array('deleted' => 1), array('include_deleted' => 1)); - - foreach ($instances as $instance) { - // field_purge_data() will need the field array. - $field = field_info_field_by_id($instance['field_id']); - // Retrieve some entities. - $query = new EntityFieldQuery(); - $results = $query - ->fieldCondition($field) - ->entityCondition('bundle', $instance['bundle']) - ->deleted(TRUE) - ->range(0, $batch_size) - ->execute(); - - if ($results) { - foreach ($results as $entity_type => $stub_entities) { - field_attach_load($entity_type, $stub_entities, FIELD_LOAD_CURRENT, array('field_id' => $field['id'], 'deleted' => 1)); - foreach ($stub_entities as $stub_entity) { - // Purge the data for the entity. - field_purge_data($entity_type, $stub_entity, $field, $instance); - } - } - } - else { - // No field data remains for the instance, so we can remove it. - field_purge_instance($instance); - } - } - - // Retrieve all deleted fields. Any that have no instances can be purged. - $fields = field_read_fields(array('deleted' => 1), array('include_deleted' => 1)); - foreach ($fields as $field) { - $instances = field_read_instances(array('field_id' => $field['id']), array('include_deleted' => 1)); - if (empty($instances)) { - field_purge_field($field); - } - } -} - -/** - * Purges the field data for a single field on a single pseudo-entity. - * - * This is basically the same as field_attach_delete() except it only applies - * to a single field. The entity itself is not being deleted, and it is quite - * possible that other field data will remain attached to it. - * - * @param $entity_type - * The type of $entity; e.g. 'node' or 'user'. - * @param $entity - * The pseudo-entity whose field data is being purged. - * @param $field - * The (possibly deleted) field whose data is being purged. - * @param $instance - * The deleted field instance whose data is being purged. - */ -function field_purge_data($entity_type, $entity, $field, $instance) { - // Each field type's hook_field_delete() only expects to operate on a single - // field at a time, so we can use it as-is for purging. - $options = array('field_id' => $instance['field_id'], 'deleted' => TRUE); - _field_invoke('delete', $entity_type, $entity, $dummy, $dummy, $options); - - // Tell the field storage system to purge the data. - module_invoke($field['storage']['module'], 'field_storage_purge', $entity_type, $entity, $field, $instance); - - // Let other modules act on purging the data. - foreach (module_implements('field_attach_purge') as $module) { - $function = $module . '_field_attach_purge'; - $function($entity_type, $entity, $field, $instance); - } -} - -/** - * Purges a field instance record from the database. - * - * This function assumes all data for the instance has already been purged, and - * should only be called by field_purge_batch(). - * - * @param $instance - * The instance record to purge. - */ -function field_purge_instance($instance) { - db_delete('field_config_instance') - ->condition('id', $instance['id']) - ->execute(); - - // Notify the storage engine. - $field = field_info_field_by_id($instance['field_id']); - module_invoke($field['storage']['module'], 'field_storage_purge_instance', $instance); - - // Clear the cache. - field_info_cache_clear(); - - // Invoke external hooks after the cache is cleared for API consistency. - module_invoke_all('field_purge_instance', $instance); -} - -/** - * Purges a field record from the database. - * - * This function assumes all instances for the field has already been purged, - * and should only be called by field_purge_batch(). - * - * @param $field - * The field record to purge. - */ -function field_purge_field($field) { - $instances = field_read_instances(array('field_id' => $field['id']), array('include_deleted' => 1)); - if (count($instances) > 0) { - throw new FieldException(t('Attempt to purge a field @field_name that still has instances.', array('@field_name' => $field['field_name']))); - } - - db_delete('field_config') - ->condition('id', $field['id']) - ->execute(); - - // Notify the storage engine. - module_invoke($field['storage']['module'], 'field_storage_purge_field', $field); - - // Clear the cache. - field_info_cache_clear(); - - // Invoke external hooks after the cache is cleared for API consistency. - module_invoke_all('field_purge_field', $field); -} - -/** - * @} End of "defgroup field_purge". - */ diff --git a/modules/field/field.default.inc b/modules/field/field.default.inc deleted file mode 100644 index cb49bdb85ef..00000000000 --- a/modules/field/field.default.inc +++ /dev/null @@ -1,268 +0,0 @@ -<?php - -/** - * @file - * Default 'implementations' of hook_field_*(): common field housekeeping. - * - * Those implementations are special, as field.module does not define any field - * types. Those functions take care of default stuff common to all field types. - * They are called through the _field_invoke_default() iterator, generally in - * the corresponding field_attach_[operation]() function. - */ - -/** - * Extracts field values from submitted form values. - * - * @param $entity_type - * The type of $entity. - * @param $entity - * The entity for the operation. - * @param $field - * The field structure for the operation. - * @param $instance - * The instance structure for $field on $entity's bundle. - * @param $langcode - * The language associated to $items. - * @param $items - * The field values. This parameter is altered by reference to receive the - * incoming form values. - * @param $form - * The form structure where field elements are attached to. This might be a - * full form structure, or a sub-element of a larger form. - * @param $form_state - * The form state. - */ -function field_default_extract_form_values($entity_type, $entity, $field, $instance, $langcode, &$items, $form, &$form_state) { - $path = array_merge($form['#parents'], array($field['field_name'], $langcode)); - $key_exists = NULL; - $values = drupal_array_get_nested_value($form_state['values'], $path, $key_exists); - if ($key_exists) { - // Remove the 'value' of the 'add more' button. - unset($values['add_more']); - $items = $values; - } -} - -/** - * Generic field validation handler. - * - * Possible error codes: - * - 'field_cardinality': The number of values exceeds the field cardinality. - * - * @see _hook_field_validate() - * - * @param $entity_type - * The type of $entity. - * @param $entity - * The entity for the operation. - * @param $field - * The field structure for the operation. - * @param $instance - * The instance structure for $field on $entity's bundle. - * @param $langcode - * The language associated to $items. - * @param $items - * $entity->{$field['field_name']}[$langcode], or an empty array if unset. - * @param $errors - * The array of errors, keyed by field name and by value delta, that have - * already been reported for the entity. The function should add its errors - * to this array. Each error is an associative array, with the following - * keys and values: - * - 'error': an error code (should be a string, prefixed with the module name) - * - 'message': the human readable message to be displayed. - */ -function field_default_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) { - // Filter out empty values. - $items = _field_filter_items($field, $items); - - // Check that the number of values doesn't exceed the field cardinality. - // For form submitted values, this can only happen with 'multiple value' - // widgets. - if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED && count($items) > $field['cardinality']) { - $errors[$field['field_name']][$langcode][0][] = array( - 'error' => 'field_cardinality', - 'message' => t('%name: this field cannot hold more than @count values.', array('%name' => $instance['label'], '@count' => $field['cardinality'])), - ); - } -} - -function field_default_submit($entity_type, $entity, $field, $instance, $langcode, &$items, $form, &$form_state) { - // Filter out empty values. - $items = _field_filter_items($field, $items); - // Reorder items to account for drag-n-drop reordering. - $items = _field_sort_items($field, $items); -} - -/** - * Default field 'insert' operation. - * - * Insert default value if no $entity->$field_name entry was provided. - * This can happen with programmatic saves, or on form-based creation where - * the current user doesn't have 'edit' permission for the field. - */ -function field_default_insert($entity_type, $entity, $field, $instance, $langcode, &$items) { - // _field_invoke() populates $items with an empty array if the $entity has no - // entry for the field, so we check on the $entity itself. - // We also check that the current field translation is actually defined before - // assigning it a default value. This way we ensure that only the intended - // languages get a default value. Otherwise we could have default values for - // not yet open languages. - if (empty($entity) || !property_exists($entity, $field['field_name']) || - (isset($entity->{$field['field_name']}[$langcode]) && count($entity->{$field['field_name']}[$langcode]) == 0)) { - $items = field_get_default_value($entity_type, $entity, $field, $instance, $langcode); - } -} - -/** - * Invokes hook_field_formatter_prepare_view() on the relevant formatters. - * - * @param $entity_type - * The type of $entity; e.g. 'node' or 'user'. - * @param $entities - * An array of entities being displayed, keyed by entity id. - * @param $field - * The field structure for the operation. - * @param $instances - * Array of instance structures for $field for each entity, keyed by entity - * id. - * @param $langcode - * The language associated to $items. - * @param $items - * Array of field values already loaded for the entities, keyed by entity id. - * @param $display - * Can be either: - * - the name of a view mode - * - or an array of display settings to use for display, as found in the - * 'display' entry of $instance definitions. - */ -function field_default_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $display) { - // Group entities, instances and items by formatter module. - $modules = array(); - foreach ($instances as $id => $instance) { - if (is_string($display)) { - $view_mode = $display; - $instance_display = field_get_display($instance, $view_mode, $entities[$id]); - } - else { - $instance_display = $display; - } - - if ($instance_display['type'] !== 'hidden') { - $module = $instance_display['module']; - $modules[$module] = $module; - $grouped_entities[$module][$id] = $entities[$id]; - $grouped_instances[$module][$id] = $instance; - $grouped_displays[$module][$id] = $instance_display; - // hook_field_formatter_prepare_view() alters $items by reference. - $grouped_items[$module][$id] = &$items[$id]; - } - } - - foreach ($modules as $module) { - // Invoke hook_field_formatter_prepare_view(). - $function = $module . '_field_formatter_prepare_view'; - if (function_exists($function)) { - $function($entity_type, $grouped_entities[$module], $field, $grouped_instances[$module], $langcode, $grouped_items[$module], $grouped_displays[$module]); - } - } -} - -/** - * Builds a renderable array for one field on one entity instance. - * - * @param $entity_type - * The type of $entity; e.g. 'node' or 'user'. - * @param $entity - * A single object of type $entity_type. - * @param $field - * The field structure for the operation. - * @param $instance - * An array containing each field on $entity's bundle. - * @param $langcode - * The language associated to $items. - * @param $items - * Array of field values already loaded for the entities, keyed by entity id. - * @param $display - * Can be either: - * - the name of a view mode; - * - or an array of custom display settings, as found in the 'display' entry - * of $instance definitions. - */ -function field_default_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) { - list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); - - $addition = array(); - - // Prepare incoming display specifications. - if (is_string($display)) { - $view_mode = $display; - $display = field_get_display($instance, $view_mode, $entity); - } - else { - $view_mode = '_custom_display'; - } - - if ($display['type'] !== 'hidden') { - // Calling the formatter function through module_invoke() can have a - // performance impact on pages with many fields and values. - $function = $display['module'] . '_field_formatter_view'; - if (function_exists($function)) { - $elements = $function($entity_type, $entity, $field, $instance, $langcode, $items, $display); - - if ($elements) { - $info = array( - '#theme' => 'field', - '#weight' => $display['weight'], - '#title' => $instance['label'], - '#access' => field_access('view', $field, $entity_type, $entity), - '#label_display' => $display['label'], - '#view_mode' => $view_mode, - '#language' => $langcode, - '#field_name' => $field['field_name'], - '#field_type' => $field['type'], - '#field_translatable' => $field['translatable'], - '#entity_type' => $entity_type, - '#bundle' => $bundle, - '#object' => $entity, - '#items' => $items, - '#formatter' => $display['type'] - ); - - $addition[$field['field_name']] = array_merge($info, $elements); - } - } - } - - return $addition; -} - -/** - * Copies source field values into the entity to be prepared. - * - * @param $entity_type - * The type of $entity; e.g. 'node' or 'user'. - * @param $entity - * The entity to be prepared for translation. - * @param $field - * The field structure for the operation. - * @param $instance - * The instance structure for $field on $entity's bundle. - * @param $langcode - * The language the entity has to be translated in. - * @param $items - * $entity->{$field['field_name']}[$langcode], or an empty array if unset. - * @param $source_entity - * The source entity holding the field values to be translated. - * @param $source_langcode - * The source language from which translate. - */ -function field_default_prepare_translation($entity_type, $entity, $field, $instance, $langcode, &$items, $source_entity, $source_langcode) { - $field_name = $field['field_name']; - // If the field is untranslatable keep using LANGUAGE_NONE. - if ($langcode == LANGUAGE_NONE) { - $source_langcode = LANGUAGE_NONE; - } - if (isset($source_entity->{$field_name}[$source_langcode])) { - $items = $source_entity->{$field_name}[$source_langcode]; - } -} diff --git a/modules/field/field.form.inc b/modules/field/field.form.inc deleted file mode 100644 index be3685d875e..00000000000 --- a/modules/field/field.form.inc +++ /dev/null @@ -1,570 +0,0 @@ -<?php - -/** - * @file - * Field forms management. - */ - -/** - * Create a separate form element for each field. - */ -function field_default_form($entity_type, $entity, $field, $instance, $langcode, $items, &$form, &$form_state, $get_delta = NULL) { - // This could be called with no entity, as when a UI module creates a - // dummy form to set default values. - if ($entity) { - list($id, , ) = entity_extract_ids($entity_type, $entity); - } - - $parents = $form['#parents']; - - $addition = array(); - $field_name = $field['field_name']; - $addition[$field_name] = array(); - - // Populate widgets with default values when creating a new entity. - if (empty($items) && empty($id)) { - $items = field_get_default_value($entity_type, $entity, $field, $instance, $langcode); - } - - // Let modules alter the widget properties. - $context = array( - 'entity_type' => $entity_type, - 'entity' => $entity, - 'field' => $field, - 'instance' => $instance, - ); - drupal_alter(array('field_widget_properties', 'field_widget_properties_' . $entity_type), $instance['widget'], $context); - - // Collect widget elements. - $elements = array(); - if (field_access('edit', $field, $entity_type, $entity)) { - // Store field information in $form_state. - if (!field_form_get_state($parents, $field_name, $langcode, $form_state)) { - $field_state = array( - 'field' => $field, - 'instance' => $instance, - 'items_count' => count($items), - 'array_parents' => array(), - 'errors' => array(), - ); - field_form_set_state($parents, $field_name, $langcode, $form_state, $field_state); - } - - // 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) { - $elements = field_multiple_value_form($field, $instance, $langcode, $items, $form, $form_state); - } - // If the widget is handling multiple values (e.g Options), or if we are - // 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_form'; - if (function_exists($function)) { - $element = array( - '#entity_type' => $instance['entity_type'], - '#bundle' => $instance['bundle'], - '#field_name' => $field_name, - '#language' => $langcode, - '#field_parents' => $parents, - '#columns' => array_keys($field['columns']), - '#title' => check_plain($instance['label']), - '#description' => field_filter_xss($instance['description']), - // Only the first widget should be required. - '#required' => $delta == 0 && $instance['required'], - '#delta' => $delta, - ); - if ($element = $function($form, $form_state, $field, $instance, $langcode, $items, $delta, $element)) { - // Allow modules to alter the field widget form element. - $context = array( - 'form' => $form, - 'field' => $field, - 'instance' => $instance, - 'langcode' => $langcode, - 'items' => $items, - 'delta' => $delta, - ); - drupal_alter(array('field_widget_form', 'field_widget_' . $instance['widget']['type'] . '_form'), $element, $form_state, $context); - - // 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 element. - if (field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_DEFAULT) { - $elements[$delta] = $element; - } - else { - $elements = $element; - } - } - } - } - } - - if ($elements) { - // Also aid in theming of field widgets by rendering a classified - // container. - $addition[$field_name] = array( - '#type' => 'container', - '#attributes' => array( - 'class' => array( - 'field-type-' . drupal_html_class($field['type']), - 'field-name-' . drupal_html_class($field_name), - 'field-widget-' . drupal_html_class($instance['widget']['type']), - ), - ), - '#weight' => $instance['widget']['weight'], - ); - } - - // Populate the 'array_parents' information in $form_state['field'] after - // the form is built, so that we catch changes in the form structure performed - // in alter() hooks. - $elements['#after_build'][] = 'field_form_element_after_build'; - $elements['#field_name'] = $field_name; - $elements['#language'] = $langcode; - $elements['#field_parents'] = $parents; - - $addition[$field_name] += array( - '#tree' => TRUE, - // The '#language' key can be used to access the field's form element - // when $langcode is unknown. - '#language' => $langcode, - $langcode => $elements, - ); - - 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, $langcode, $items, &$form, &$form_state) { - $field_name = $field['field_name']; - $parents = $form['#parents']; - - // Determine the number of widgets to display. - switch ($field['cardinality']) { - case FIELD_CARDINALITY_UNLIMITED: - $field_state = field_form_get_state($parents, $field_name, $langcode, $form_state); - $max = $field_state['items_count']; - break; - - default: - $max = $field['cardinality'] - 1; - break; - } - - $title = check_plain($instance['label']); - $description = field_filter_xss($instance['description']); - - $id_prefix = implode('-', array_merge($parents, array($field_name))); - $wrapper_id = drupal_html_id($id_prefix . '-add-more-wrapper'); - - $field_elements = array(); - - $function = $instance['widget']['module'] . '_field_widget_form'; - if (function_exists($function)) { - for ($delta = 0; $delta <= $max; $delta++) { - $multiple = $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED; - $element = array( - '#entity_type' => $instance['entity_type'], - '#bundle' => $instance['bundle'], - '#field_name' => $field_name, - '#language' => $langcode, - '#field_parents' => $parents, - '#columns' => array_keys($field['columns']), - // For multiple fields, title and description are handled by the wrapping table. - '#title' => $multiple ? '' : $title, - '#description' => $multiple ? '' : $description, - // Only the first widget should be required. - '#required' => $delta == 0 && $instance['required'], - '#delta' => $delta, - '#weight' => $delta, - ); - if ($element = $function($form, $form_state, $field, $instance, $langcode, $items, $delta, $element)) { - // Input field for the delta (drag-n-drop reordering). - if ($multiple) { - // We name the element '_weight' to avoid clashing with elements - // defined by widget. - $element['_weight'] = array( - '#type' => 'weight', - '#title' => t('Weight for row @number', array('@number' => $delta + 1)), - '#title_display' => 'invisible', - // Note: this 'delta' is the FAPI 'weight' element's property. - '#delta' => $max, - '#default_value' => isset($items[$delta]['_weight']) ? $items[$delta]['_weight'] : $delta, - '#weight' => 100, - ); - } - - // Allow modules to alter the field widget form element. - $context = array( - 'form' => $form, - 'field' => $field, - 'instance' => $instance, - 'langcode' => $langcode, - 'items' => $items, - 'delta' => $delta, - ); - drupal_alter(array('field_widget_form', 'field_widget_' . $instance['widget']['type'] . '_form'), $element, $form_state, $context); - - $field_elements[$delta] = $element; - } - } - - if ($field_elements) { - $field_elements += array( - '#theme' => 'field_multiple_value_form', - '#field_name' => $field['field_name'], - '#cardinality' => $field['cardinality'], - '#title' => $title, - '#required' => $instance['required'], - '#description' => $description, - '#prefix' => '<div id="' . $wrapper_id . '">', - '#suffix' => '</div>', - '#max_delta' => $max, - ); - // Add 'add more' button, if not working with a programmed form. - if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED && empty($form_state['programmed'])) { - $field_elements['add_more'] = array( - '#type' => 'submit', - '#name' => strtr($id_prefix, '-', '_') . '_add_more', - '#value' => t('Add another item'), - '#attributes' => array('class' => array('field-add-more-submit')), - '#limit_validation_errors' => array(array_merge($parents, array($field_name, $langcode))), - '#submit' => array('field_add_more_submit'), - '#ajax' => array( - 'callback' => 'field_add_more_js', - 'wrapper' => $wrapper_id, - 'effect' => 'fade', - ), - ); - } - } - } - - return $field_elements; -} - -/** - * Returns HTML for an individual form element. - * - * Combine multiple values into a table with drag-n-drop reordering. - * TODO : convert to a template. - * - * @param $variables - * An associative array containing: - * - element: A render element representing the form element. - * - * @ingroup themeable - */ -function theme_field_multiple_value_form($variables) { - $element = $variables['element']; - $output = ''; - - if ($element['#cardinality'] > 1 || $element['#cardinality'] == FIELD_CARDINALITY_UNLIMITED) { - $table_id = drupal_html_id($element['#field_name'] . '_values'); - $order_class = $element['#field_name'] . '-delta-order'; - $required = !empty($element['#required']) ? theme('form_required_marker', $variables) : ''; - - $header = array( - array( - 'data' => '<label>' . t('!title: !required', array('!title' => $element['#title'], '!required' => $required)) . "</label>", - 'colspan' => 2, - 'class' => array('field-label'), - ), - 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 === 'add_more') { - $add_more_button = &$element[$key]; - } - else { - $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'] = array($order_class); - $delta_element = drupal_render($item['_weight']); - $cells = array( - array('data' => '', 'class' => array('field-multiple-drag')), - drupal_render($item), - array('data' => $delta_element, 'class' => array('delta-order')), - ); - $rows[] = array( - 'data' => $cells, - 'class' => array('draggable'), - ); - } - - $output = '<div class="form-item">'; - $output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => $table_id, 'class' => array('field-multiple-table')))); - $output .= $element['#description'] ? '<div class="description">' . $element['#description'] . '</div>' : ''; - $output .= '<div class="clearfix">' . drupal_render($add_more_button) . '</div>'; - $output .= '</div>'; - - drupal_add_tabledrag($table_id, 'order', 'sibling', $order_class); - } - else { - foreach (element_children($element) as $key) { - $output .= drupal_render($element[$key]); - } - } - - return $output; -} - -/** - * #after_build callback for field elements in a form. - * - * This stores the final location of the field within the form structure so - * that field_default_form_errors() can assign validation errors to the right - * form element. - * - * @see field_default_form_errors() - */ -function field_form_element_after_build($element, &$form_state) { - $parents = $element['#field_parents']; - $field_name = $element['#field_name']; - $langcode = $element['#language']; - - $field_state = field_form_get_state($parents, $field_name, $langcode, $form_state); - $field_state['array_parents'] = $element['#array_parents']; - field_form_set_state($parents, $field_name, $langcode, $form_state, $field_state); - - return $element; -} - -/** - * Transfer field-level validation errors to widgets. - */ -function field_default_form_errors($entity_type, $entity, $field, $instance, $langcode, $items, $form, &$form_state) { - $field_state = field_form_get_state($form['#parents'], $field['field_name'], $langcode, $form_state); - - if (!empty($field_state['errors'])) { - $function = $instance['widget']['module'] . '_field_widget_error'; - $function_exists = function_exists($function); - - // Locate the correct element in the the form. - $element = drupal_array_get_nested_value($form_state['complete_form'], $field_state['array_parents']); - - $multiple_widget = field_behaviors_widget('multiple values', $instance) != FIELD_BEHAVIOR_DEFAULT; - foreach ($field_state['errors'] as $delta => $delta_errors) { - // For multiple single-value widgets, pass errors by delta. - // For a multiple-value widget, all errors are passed to the main widget. - $error_element = $multiple_widget ? $element : $element[$delta]; - foreach ($delta_errors as $error) { - if ($function_exists) { - $function($error_element, $error, $form, $form_state); - } - else { - // Make sure that errors are reported (even incorrectly flagged) if - // the widget module fails to implement hook_field_widget_error(). - form_error($error_element, $error['error']); - } - } - } - // Reinitialize the errors list for the next submit. - $field_state['errors'] = array(); - field_form_set_state($form['#parents'], $field['field_name'], $langcode, $form_state, $field_state); - } -} - -/** - * Submit handler for the "Add another item" button of a field form. - * - * This handler is run regardless of whether JS is enabled or not. It makes - * changes to the form state. If the button was clicked with JS disabled, then - * the page is reloaded with the complete rebuilt form. If the button was - * clicked with JS enabled, then ajax_form_callback() calls field_add_more_js() - * to return just the changed part of the form. - */ -function field_add_more_submit($form, &$form_state) { - $button = $form_state['triggering_element']; - - // Go one level up in the form, to the widgets container. - $element = drupal_array_get_nested_value($form, array_slice($button['#array_parents'], 0, -1)); - $field_name = $element['#field_name']; - $langcode = $element['#language']; - $parents = $element['#field_parents']; - - // Increment the items count. - $field_state = field_form_get_state($parents, $field_name, $langcode, $form_state); - $field_state['items_count']++; - field_form_set_state($parents, $field_name, $langcode, $form_state, $field_state); - - $form_state['rebuild'] = TRUE; -} - -/** - * Ajax callback in response to a new empty widget being added to the form. - * - * This returns the new page content to replace the page content made obsolete - * by the form submission. - * - * @see field_add_more_submit() - */ -function field_add_more_js($form, $form_state) { - $button = $form_state['triggering_element']; - - // Go one level up in the form, to the widgets container. - $element = drupal_array_get_nested_value($form, array_slice($button['#array_parents'], 0, -1)); - $field_name = $element['#field_name']; - $langcode = $element['#language']; - $parents = $element['#field_parents']; - - $field_state = field_form_get_state($parents, $field_name, $langcode, $form_state); - - $field = $field_state['field']; - if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED) { - return; - } - - // Add a DIV around the delta receiving the Ajax effect. - $delta = $element['#max_delta']; - $element[$delta]['#prefix'] = '<div class="ajax-new-content">' . (isset($element[$delta]['#prefix']) ? $element[$delta]['#prefix'] : ''); - $element[$delta]['#suffix'] = (isset($element[$delta]['#suffix']) ? $element[$delta]['#suffix'] : '') . '</div>'; - - return $element; -} - -/** - * Retrieves processing information about a field from $form_state. - * - * @param $parents - * The array of #parents where the field lives in the form. - * @param $field_name - * The field name. - * @param $langcode - * The language in which the field values are entered. - * @param $form_state - * The form state. - * - * @return - * An array with the following key/data pairs: - * - field: the field definition array, - * - instance: the field instance definition array, - * - items_count: the number of widgets to display for the field, - * - array_parents: the location of the field's widgets within the $form - * structure. This entry is populated at '#after_build' time. - * - errors: the array of field validation errors reported on the field. This - * entry is populated at field_attach_form_validate() time. - * - * @see field_form_set_state() - */ -function field_form_get_state($parents, $field_name, $langcode, &$form_state) { - $form_state_parents = _field_form_state_parents($parents, $field_name, $langcode); - return drupal_array_get_nested_value($form_state, $form_state_parents); -} - -/** - * Stores processing information about a field in $form_state. - * - * @param $parents - * The array of #parents where the field lives in the form. - * @param $field_name - * The field name. - * @param $langcode - * The language in which the field values are entered. - * @param $form_state - * The form state. - * @param $field_state - * The array of data to store. See field_form_get_state() for the structure - * and content of the array. - * - * @see field_form_get_state() - */ -function field_form_set_state($parents, $field_name, $langcode, &$form_state, $field_state) { - $form_state_parents = _field_form_state_parents($parents, $field_name, $langcode); - drupal_array_set_nested_value($form_state, $form_state_parents, $field_state); -} - -/** - * Returns the location of processing information within $form_state. - */ -function _field_form_state_parents($parents, $field_name, $langcode) { - // To ensure backwards compatibility on regular entity forms for widgets that - // still access $form_state['field'][$field_name] directly, - // - top-level fields (empty $parents) are placed directly under - // $form_state['fields'][$field_name]. - // - Other fields are placed under - // $form_state['field']['#parents'][...$parents...]['#fields'][$field_name] - // to avoid clashes between field names and $parents parts. - // @todo Remove backwards compatibility in Drupal 8, and use a unique - // $form_state['field'][...$parents...]['#fields'][$field_name] structure. - if (!empty($parents)) { - $form_state_parents = array_merge(array('#parents'), $parents, array('#fields')); - } - else { - $form_state_parents = array(); - } - $form_state_parents = array_merge(array('field'), $form_state_parents, array($field_name, $langcode)); - - return $form_state_parents; -} - -/** - * Retrieves the field definition for a widget's helper callbacks. - * - * Widgets helper element callbacks (such as #process, #element_validate, - * #value_callback, ...) should use field_widget_field() and - * field_widget_instance() instead of field_info_field() and - * field_info_instance() when they need to access field or instance properties. - * See hook_field_widget_form() for more details. - * - * @param $element - * The structured array for the widget. - * @param $form_state - * The form state. - * - * @return - * The $field definition array for the current widget. - * - * @see field_widget_instance() - * @see hook_field_widget_form() - */ -function field_widget_field($element, $form_state) { - $field_state = field_form_get_state($element['#field_parents'], $element['#field_name'], $element['#language'], $form_state); - return $field_state['field']; -} - -/** - * Retrieves the instance definition array for a widget's helper callbacks. - * - * Widgets helper element callbacks (such as #process, #element_validate, - * #value_callback, ...) should use field_widget_field() and - * field_widget_instance() instead of field_info_field() and - * field_info_instance() when they need to access field or instance properties. - * See hook_field_widget_form() for more details. - * - * @param $element - * The structured array for the widget. - * @param $form_state - * The form state. - * - * @return - * The $instance definition array for the current widget. - * - * @see field_widget_field() - * @see hook_field_widget_form() - */ -function field_widget_instance($element, $form_state) { - $field_state = field_form_get_state($element['#field_parents'], $element['#field_name'], $element['#language'], $form_state); - return $field_state['instance']; -} diff --git a/modules/field/field.info b/modules/field/field.info deleted file mode 100644 index c61c501beb8..00000000000 --- a/modules/field/field.info +++ /dev/null @@ -1,12 +0,0 @@ -name = Field -description = Field API to add fields to entities like nodes and users. -package = Core -version = VERSION -core = 8.x -files[] = field.module -files[] = field.attach.inc -files[] = tests/field.test -dependencies[] = field_sql_storage -dependencies[] = entity -required = TRUE -stylesheets[all][] = theme/field.css diff --git a/modules/field/field.info.inc b/modules/field/field.info.inc deleted file mode 100644 index 69fefca4f43..00000000000 --- a/modules/field/field.info.inc +++ /dev/null @@ -1,900 +0,0 @@ -<?php - -/** - * @file - * Field Info API, providing information about available fields and field types. - */ - -/** - * @defgroup field_info Field Info API - * @{ - * Obtain information about Field API configuration. - * - * The Field Info API exposes information about field types, fields, - * instances, bundles, widget types, display formatters, behaviors, - * and settings defined by or with the Field API. - */ - -/** - * Clears the field info cache without clearing the field data cache. - * - * This is useful when deleted fields or instances are purged. We - * need to remove the purged records, but no actual field data items - * are affected. - */ -function field_info_cache_clear() { - drupal_static_reset('field_view_mode_settings'); - - // @todo: Remove this when field_attach_*_bundle() bundle management - // functions are moved to the entity API. - entity_info_cache_clear(); - - _field_info_collate_types_reset(); - _field_info_collate_fields_reset(); -} - -/** - * Collates all information on field types, widget types and related structures. - * - * @return - * An associative array containing: - * - 'field types': Array of hook_field_info() results, keyed by field_type. - * Each element has the following components: label, description, settings, - * instance_settings, default_widget, default_formatter, and behaviors - * from hook_field_info(), as well as module, giving the module that exposes - * the field type. - * - 'widget types': Array of hook_field_widget_info() results, keyed by - * widget_type. Each element has the following components: label, field - * types, settings, and behaviors from hook_field_widget_info(), as well - * as module, giving the module that exposes the widget type. - * - 'formatter types': Array of hook_field_formatter_info() results, keyed by - * formatter_type. Each element has the following components: label, field - * types, and behaviors from hook_field_formatter_info(), as well as - * module, giving the module that exposes the formatter type. - * - 'storage types': Array of hook_field_storage_info() results, keyed by - * storage type names. Each element has the following components: label, - * description, and settings from hook_field_storage_info(), as well as - * module, giving the module that exposes the storage type. - * - 'fieldable types': Array of hook_entity_info() results, keyed by - * entity_type. Each element has the following components: name, id key, - * revision key, bundle key, cacheable, and bundles from hook_entity_info(), - * as well as module, giving the module that exposes the entity type. - * - * @see _field_info_collate_types_reset() - */ -function _field_info_collate_types() { - global $language; - - // Use the advanced drupal_static() pattern, since this is called very often. - static $drupal_static_fast; - - if (!isset($drupal_static_fast)) { - $drupal_static_fast['field_info_collate_types'] = &drupal_static(__FUNCTION__); - } - $info = &$drupal_static_fast['field_info_collate_types']; - - // The _info() hooks invoked below include translated strings, so each - // language is cached separately. - $langcode = $language->language; - - if (!isset($info)) { - if ($cached = cache('field')->get("field_info_types:$langcode")) { - $info = $cached->data; - } - else { - $info = array( - 'field types' => array(), - 'widget types' => array(), - 'formatter types' => array(), - 'storage types' => array(), - ); - - // Populate field types. - foreach (module_implements('field_info') as $module) { - $field_types = (array) module_invoke($module, 'field_info'); - foreach ($field_types as $name => $field_info) { - // Provide defaults. - $field_info += array( - 'settings' => array(), - 'instance_settings' => array(), - ); - $info['field types'][$name] = $field_info; - $info['field types'][$name]['module'] = $module; - } - } - drupal_alter('field_info', $info['field types']); - - // Populate widget types. - foreach (module_implements('field_widget_info') as $module) { - $widget_types = (array) module_invoke($module, 'field_widget_info'); - foreach ($widget_types as $name => $widget_info) { - // Provide defaults. - $widget_info += array( - 'settings' => array(), - ); - $info['widget types'][$name] = $widget_info; - $info['widget types'][$name]['module'] = $module; - } - } - drupal_alter('field_widget_info', $info['widget types']); - - // Populate formatter types. - foreach (module_implements('field_formatter_info') as $module) { - $formatter_types = (array) module_invoke($module, 'field_formatter_info'); - foreach ($formatter_types as $name => $formatter_info) { - // Provide defaults. - $formatter_info += array( - 'settings' => array(), - ); - $info['formatter types'][$name] = $formatter_info; - $info['formatter types'][$name]['module'] = $module; - } - } - drupal_alter('field_formatter_info', $info['formatter types']); - - // Populate storage types. - foreach (module_implements('field_storage_info') as $module) { - $storage_types = (array) module_invoke($module, 'field_storage_info'); - foreach ($storage_types as $name => $storage_info) { - // Provide defaults. - $storage_info += array( - 'settings' => array(), - ); - $info['storage types'][$name] = $storage_info; - $info['storage types'][$name]['module'] = $module; - } - } - drupal_alter('field_storage_info', $info['storage types']); - - cache('field')->set("field_info_types:$langcode", $info); - } - } - - return $info; -} - -/** - * Clear collated information on field and widget types and related structures. - */ -function _field_info_collate_types_reset() { - drupal_static_reset('_field_info_collate_types'); - // Clear all languages. - cache('field')->deletePrefix('field_info_types:'); -} - -/** - * Collates all information on existing fields and instances. - * - * @return - * An associative array containing: - * - fields: Array of existing fields, keyed by field ID. This element - * lists deleted and non-deleted fields, but not inactive ones. - * Each field has an additional element, 'bundles', which is an array - * of all non-deleted instances of that field. - * - field_ids: Array of field IDs, keyed by field name. This element - * only lists non-deleted, active fields. - * - instances: Array of existing instances, keyed by entity type, bundle - * name and field name. This element only lists non-deleted instances - * whose field is active. - * - * @see _field_info_collate_fields_reset() - */ -function _field_info_collate_fields() { - // Use the advanced drupal_static() pattern, since this is called very often. - static $drupal_static_fast; - - if (!isset($drupal_static_fast)) { - $drupal_static_fast['field_info_collate_fields'] = &drupal_static(__FUNCTION__); - } - $info = &$drupal_static_fast['field_info_collate_fields']; - - if (!isset($info)) { - if ($cached = cache('field')->get('field_info_fields')) { - $info = $cached->data; - } - else { - $definitions = array( - 'field_ids' => field_read_fields(array(), array('include_deleted' => 1)), - 'instances' => field_read_instances(), - ); - - // Populate 'fields' with all fields, keyed by ID. - $info['fields'] = array(); - foreach ($definitions['field_ids'] as $key => $field) { - $info['fields'][$key] = $definitions['field_ids'][$key] = _field_info_prepare_field($field); - } - - // Build an array of field IDs for non-deleted fields, keyed by name. - $info['field_ids'] = array(); - foreach ($info['fields'] as $key => $field) { - if (!$field['deleted']) { - $info['field_ids'][$field['field_name']] = $key; - } - } - - // Populate 'instances'. Only non-deleted instances are considered. - $info['instances'] = array(); - foreach (field_info_bundles() as $entity_type => $bundles) { - foreach ($bundles as $bundle => $bundle_info) { - $info['instances'][$entity_type][$bundle] = array(); - } - } - foreach ($definitions['instances'] as $instance) { - $field = $info['fields'][$instance['field_id']]; - $instance = _field_info_prepare_instance($instance, $field); - $info['instances'][$instance['entity_type']][$instance['bundle']][$instance['field_name']] = $instance; - // Enrich field definitions with the list of bundles where they have - // instances. NOTE: Deleted fields in $info['field_ids'] are not - // enriched because all of their instances are deleted, too, and - // are thus not in $definitions['instances']. - $info['fields'][$instance['field_id']]['bundles'][$instance['entity_type']][] = $instance['bundle']; - } - - // Populate 'extra_fields'. - $extra = module_invoke_all('field_extra_fields'); - drupal_alter('field_extra_fields', $extra); - // Merge in saved settings. - foreach ($extra as $entity_type => $bundles) { - foreach ($bundles as $bundle => $extra_fields) { - $extra_fields = _field_info_prepare_extra_fields($extra_fields, $entity_type, $bundle); - $info['extra_fields'][$entity_type][$bundle] = $extra_fields; - } - } - - cache('field')->set('field_info_fields', $info); - } - } - - return $info; -} - -/** - * Clear collated information on existing fields and instances. - */ -function _field_info_collate_fields_reset() { - drupal_static_reset('_field_info_collate_fields'); - cache('field')->delete('field_info_fields'); -} - -/** - * Prepares a field definition for the current run-time context. - * - * Since the field was last saved or updated, new field settings can be - * expected. - * - * @param $field - * The raw field structure as read from the database. - */ -function _field_info_prepare_field($field) { - // Make sure all expected field settings are present. - $field['settings'] += field_info_field_settings($field['type']); - $field['storage']['settings'] += field_info_storage_settings($field['storage']['type']); - - // Add storage details. - $details = (array) module_invoke($field['storage']['module'], 'field_storage_details', $field); - drupal_alter('field_storage_details', $details, $field, $instance); - $field['storage']['details'] = $details; - - // Initialize the 'bundles' list. - $field['bundles'] = array(); - - return $field; -} - -/** - * Prepares an instance definition for the current run-time context. - * - * Since the instance was last saved or updated, a number of things might have - * changed: widgets or formatters disabled, new settings expected, new view - * modes added... - * - * @param $instance - * The raw instance structure as read from the database. - * @param $field - * The field structure for the instance. - * - * @return - * Field instance array. - */ -function _field_info_prepare_instance($instance, $field) { - // Make sure all expected instance settings are present. - $instance['settings'] += field_info_instance_settings($field['type']); - - // Set a default value for the instance. - if (field_behaviors_widget('default value', $instance) == FIELD_BEHAVIOR_DEFAULT && !isset($instance['default_value'])) { - $instance['default_value'] = NULL; - } - - $instance['widget'] = _field_info_prepare_instance_widget($field, $instance['widget']); - - foreach ($instance['display'] as $view_mode => $display) { - $instance['display'][$view_mode] = _field_info_prepare_instance_display($field, $display); - } - - // Fallback to 'hidden' for view modes configured to use custom display - // settings, and for which the instance has no explicit settings. - $entity_info = entity_get_info($instance['entity_type']); - $view_modes = array_merge(array('default'), array_keys($entity_info['view modes'])); - $view_mode_settings = field_view_mode_settings($instance['entity_type'], $instance['bundle']); - foreach ($view_modes as $view_mode) { - if ($view_mode == 'default' || !empty($view_mode_settings[$view_mode]['custom_settings'])) { - if (!isset($instance['display'][$view_mode])) { - $instance['display'][$view_mode] = array( - 'type' => 'hidden', - 'label' => 'above', - 'settings' => array(), - 'weight' => 0, - ); - } - } - } - - return $instance; -} - -/** - * Adapts display specifications to the current run-time context. - * - * @param $field - * The field structure for the instance. - * @param $display - * Display specifications as found in - * $instance['display']['some_view_mode']. - */ -function _field_info_prepare_instance_display($field, $display) { - $field_type = field_info_field_types($field['type']); - - // Fill in default values. - $display += array( - 'label' => 'above', - 'type' => $field_type['default_formatter'], - 'settings' => array(), - 'weight' => 0, - ); - if ($display['type'] != 'hidden') { - $formatter_type = field_info_formatter_types($display['type']); - // Fallback to default formatter if formatter type is not available. - if (!$formatter_type) { - $display['type'] = $field_type['default_formatter']; - $formatter_type = field_info_formatter_types($display['type']); - } - $display['module'] = $formatter_type['module']; - // Fill in default settings for the formatter. - $display['settings'] += field_info_formatter_settings($display['type']); - } - - return $display; -} - -/** - * Prepares widget specifications for the current run-time context. - * - * @param $field - * The field structure for the instance. - * @param $widget - * Widget specifications as found in $instance['widget']. - */ -function _field_info_prepare_instance_widget($field, $widget) { - $field_type = field_info_field_types($field['type']); - - // Fill in default values. - $widget += array( - 'type' => $field_type['default_widget'], - 'settings' => array(), - 'weight' => 0, - ); - - $widget_type = field_info_widget_types($widget['type']); - // Fallback to default formatter if formatter type is not available. - if (!$widget_type) { - $widget['type'] = $field_type['default_widget']; - $widget_type = field_info_widget_types($widget['type']); - } - $widget['module'] = $widget_type['module']; - // Fill in default settings for the widget. - $widget['settings'] += field_info_widget_settings($widget['type']); - - return $widget; -} - -/** - * Prepares 'extra fields' for the current run-time context. - * - * @param $extra_fields - * The array of extra fields, as collected in hook_field_extra_fields(). - * @param $entity_type - * The entity type. - * @param $bundle - * The bundle name. - */ -function _field_info_prepare_extra_fields($extra_fields, $entity_type, $bundle) { - $entity_type_info = entity_get_info($entity_type); - $bundle_settings = field_bundle_settings($entity_type, $bundle); - $extra_fields += array('form' => array(), 'display' => array()); - - $result = array(); - // Extra fields in forms. - foreach ($extra_fields['form'] as $name => $field_data) { - $settings = isset($bundle_settings['extra_fields']['form'][$name]) ? $bundle_settings['extra_fields']['form'][$name] : array(); - if (isset($settings['weight'])) { - $field_data['weight'] = $settings['weight']; - } - $result['form'][$name] = $field_data; - } - - // Extra fields in displayed entities. - $data = $extra_fields['display']; - foreach ($extra_fields['display'] as $name => $field_data) { - $settings = isset($bundle_settings['extra_fields']['display'][$name]) ? $bundle_settings['extra_fields']['display'][$name] : array(); - $view_modes = array_merge(array('default'), array_keys($entity_type_info['view modes'])); - foreach ($view_modes as $view_mode) { - if (isset($settings[$view_mode])) { - $field_data['display'][$view_mode] = $settings[$view_mode]; - } - else { - $field_data['display'][$view_mode] = array( - 'weight' => $field_data['weight'], - 'visible' => TRUE, - ); - } - } - unset($field_data['weight']); - $result['display'][$name] = $field_data; - } - - return $result; -} - -/** - * Determines the behavior of a widget with respect to an operation. - * - * @param $op - * The name of the operation. Currently supported: 'default value', - * 'multiple values'. - * @param $instance - * The field instance array. - * - * @return - * One of these values: - * - FIELD_BEHAVIOR_NONE: Do nothing for this operation. - * - FIELD_BEHAVIOR_CUSTOM: Use the widget's callback function. - * - FIELD_BEHAVIOR_DEFAULT: Use field.module default behavior. - */ -function field_behaviors_widget($op, $instance) { - $info = field_info_widget_types($instance['widget']['type']); - return isset($info['behaviors'][$op]) ? $info['behaviors'][$op] : FIELD_BEHAVIOR_DEFAULT; -} - -/** - * Returns information about field types from hook_field_info(). - * - * @param $field_type - * (optional) A field type name. If omitted, all field types will be - * returned. - * - * @return - * Either a field type description, as provided by hook_field_info(), or an - * array of all existing field types, keyed by field type name. - */ -function field_info_field_types($field_type = NULL) { - $info = _field_info_collate_types(); - $field_types = $info['field types']; - if ($field_type) { - if (isset($field_types[$field_type])) { - return $field_types[$field_type]; - } - } - else { - return $field_types; - } -} - -/** - * Returns information about field widgets from hook_field_widget_info(). - * - * @param $widget_type - * (optional) A widget type name. If omitted, all widget types will be - * returned. - * - * @return - * Either a single widget type description, as provided by - * hook_field_widget_info(), or an array of all existing widget types, keyed - * by widget type name. - */ -function field_info_widget_types($widget_type = NULL) { - $info = _field_info_collate_types(); - $widget_types = $info['widget types']; - if ($widget_type) { - if (isset($widget_types[$widget_type])) { - return $widget_types[$widget_type]; - } - } - else { - return $widget_types; - } -} - -/** - * Returns information about field formatters from hook_field_formatter_info(). - * - * @param $formatter_type - * (optional) A formatter type name. If omitted, all formatter types will be - * returned. - * - * @return - * Either a single formatter type description, as provided by - * hook_field_formatter_info(), or an array of all existing formatter types, - * keyed by formatter type name. - */ -function field_info_formatter_types($formatter_type = NULL) { - $info = _field_info_collate_types(); - $formatter_types = $info['formatter types']; - if ($formatter_type) { - if (isset($formatter_types[$formatter_type])) { - return $formatter_types[$formatter_type]; - } - } - else { - return $formatter_types; - } -} - -/** - * Returns information about field storage from hook_field_storage_info(). - * - * @param $storage_type - * (optional) A storage type name. If omitted, all storage types will be - * returned. - * - * @return - * Either a storage type description, as provided by - * hook_field_storage_info(), or an array of all existing storage types, - * keyed by storage type name. - */ -function field_info_storage_types($storage_type = NULL) { - $info = _field_info_collate_types(); - $storage_types = $info['storage types']; - if ($storage_type) { - if (isset($storage_types[$storage_type])) { - return $storage_types[$storage_type]; - } - } - else { - return $storage_types; - } -} - -/** - * Returns information about existing bundles. - * - * @param $entity_type - * The type of entity; e.g. 'node' or 'user'. - * - * @return - * An array of bundles for the $entity_type keyed by bundle name, - * or, if no $entity_type was provided, the array of all existing bundles, - * keyed by entity type. - */ -function field_info_bundles($entity_type = NULL) { - $info = entity_get_info(); - - if ($entity_type) { - return isset($info[$entity_type]['bundles']) ? $info[$entity_type]['bundles'] : array(); - } - - $bundles = array(); - foreach ($info as $type => $entity_info) { - $bundles[$type] = $entity_info['bundles']; - } - return $bundles; -} - -/** - * Returns all field definitions. - * - * @return - * An array of field definitions, keyed by field name. Each field has an - * additional property, 'bundles', which is an array of all the bundles to - * which this field belongs keyed by entity type. - */ -function field_info_fields() { - $fields = array(); - $info = _field_info_collate_fields(); - foreach ($info['fields'] as $key => $field) { - if (!$field['deleted']) { - $fields[$field['field_name']] = $field; - } - } - return $fields; -} - -/** - * Returns data about an individual field, given a field name. - * - * @param $field_name - * The name of the field to retrieve. $field_name can only refer to a - * non-deleted, active field. Use field_read_fields() to retrieve information - * on deleted or inactive fields. - * - * @return - * The field array, as returned by field_read_fields(), with an - * additional element 'bundles', whose value is an array of all the bundles - * this field belongs to keyed by entity type. - * - * @see field_info_field_by_id() - */ -function field_info_field($field_name) { - $info = _field_info_collate_fields(); - if (isset($info['field_ids'][$field_name])) { - return $info['fields'][$info['field_ids'][$field_name]]; - } -} - -/** - * Returns data about an individual field, given a field ID. - * - * @param $field_id - * The id of the field to retrieve. $field_id can refer to a - * deleted field. - * - * @return - * The field array, as returned by field_read_fields(), with an - * additional element 'bundles', whose value is an array of all the bundles - * this field belongs to. - * - * @see field_info_field() - */ -function field_info_field_by_id($field_id) { - $info = _field_info_collate_fields(); - if (isset($info['fields'][$field_id])) { - return $info['fields'][$field_id]; - } -} - -/** - * Returns the same data as field_info_field_by_id() for every field. - * - * This function is typically used when handling all fields of some entities - * to avoid thousands of calls to field_info_field_by_id(). - * - * @return - * An array, each key is a field ID and the values are field arrays as - * returned by field_read_fields(), with an additional element 'bundles', - * whose value is an array of all the bundle this field belongs to. - * - * @see field_info_field() - * @see field_info_field_by_id() - */ -function field_info_field_by_ids() { - $info = _field_info_collate_fields(); - return $info['fields']; -} - -/** - * Retrieves information about field instances. - * - * @param $entity_type - * The entity type for which to return instances. - * @param $bundle_name - * The bundle name for which to return instances. - * - * @return - * If $entity_type is not set, return all instances keyed by entity type and - * bundle name. If $entity_type is set, return all instances for that entity - * type, keyed by bundle name. If $entity_type and $bundle_name are set, return - * all instances for that bundle. - */ -function field_info_instances($entity_type = NULL, $bundle_name = NULL) { - $info = _field_info_collate_fields(); - if (!isset($entity_type)) { - return $info['instances']; - } - if (!isset($bundle_name)) { - return $info['instances'][$entity_type]; - } - if (isset($info['instances'][$entity_type][$bundle_name])) { - return $info['instances'][$entity_type][$bundle_name]; - } - return array(); -} - -/** - * Returns an array of instance data for a specific field and bundle. - * - * @param $entity_type - * The entity type for the instance. - * @param $field_name - * The field name for the instance. - * @param $bundle_name - * The bundle name for the instance. - */ -function field_info_instance($entity_type, $field_name, $bundle_name) { - $info = _field_info_collate_fields(); - if (isset($info['instances'][$entity_type][$bundle_name][$field_name])) { - return $info['instances'][$entity_type][$bundle_name][$field_name]; - } -} - -/** - * Returns a list and settings of pseudo-field elements in a given bundle. - * - * If $context is 'form', an array with the following structure: - * @code - * array( - * 'name_of_pseudo_field_component' => array( - * 'label' => The human readable name of the component, - * 'description' => A short description of the component content, - * 'weight' => The weight of the component in edit forms, - * ), - * 'name_of_other_pseudo_field_component' => array( - * // ... - * ), - * ); - * @endcode - * - * If $context is 'display', an array with the following structure: - * @code - * array( - * 'name_of_pseudo_field_component' => array( - * 'label' => The human readable name of the component, - * 'description' => A short description of the component content, - * // One entry per view mode, including the 'default' mode: - * 'display' => array( - * 'default' => array( - * 'weight' => The weight of the component in displayed entities in - * this view mode, - * 'visible' => TRUE if the component is visible, FALSE if hidden, in - * displayed entities in this view mode, - * ), - * 'teaser' => array( - * // ... - * ), - * ), - * ), - * 'name_of_other_pseudo_field_component' => array( - * // ... - * ), - * ); - * @endcode - * - * @param $entity_type - * The type of entity; e.g. 'node' or 'user'. - * @param $bundle - * The bundle name. - * @param $context - * The context for which the list of pseudo-fields is requested. Either - * 'form' or 'display'. - * - * @return - * The array of pseudo-field elements in the bundle. - */ -function field_info_extra_fields($entity_type, $bundle, $context) { - $info = _field_info_collate_fields(); - if (isset($info['extra_fields'][$entity_type][$bundle][$context])) { - return $info['extra_fields'][$entity_type][$bundle][$context]; - } - return array(); -} - -/** - * Returns the maximum weight of all the components in an entity. - * - * This includes fields, 'extra_fields', and other components added by - * third-party modules (e.g. field_group). - * - * @param $entity_type - * The type of entity; e.g. 'node' or 'user'. - * @param $bundle - * The bundle name. - * @param $context - * The context for which the maximum weight is requested. Either 'form', or - * the name of a view mode. - * @return - * The maximum weight of the entity's components, or NULL if no components - * were found. - */ -function field_info_max_weight($entity_type, $bundle, $context) { - $weights = array(); - - // Collect weights for fields. - foreach (field_info_instances($entity_type, $bundle) as $instance) { - if ($context == 'form') { - $weights[] = $instance['widget']['weight']; - } - elseif (isset($instance['display'][$context]['weight'])) { - $weights[] = $instance['display'][$context]['weight']; - } - } - // Collect weights for extra fields. - foreach (field_info_extra_fields($entity_type, $bundle, $context) as $extra) { - $weights[] = $extra['weight']; - } - - // Let other modules feedback about their own additions. - $weights = array_merge($weights, module_invoke_all('field_info_max_weight', $entity_type, $bundle, $context)); - $max_weight = $weights ? max($weights) : NULL; - - return $max_weight; -} - -/** - * Returns a field type's default settings. - * - * @param $type - * A field type name. - * - * @return - * The field type's default settings, as provided by hook_field_info(), or an - * empty array if type or settings are not defined. - */ -function field_info_field_settings($type) { - $info = field_info_field_types($type); - return isset($info['settings']) ? $info['settings'] : array(); -} - -/** - * Returns a field type's default instance settings. - * - * @param $type - * A field type name. - * - * @return - * The field type's default instance settings, as provided by - * hook_field_info(), or an empty array if type or settings are not defined. - */ -function field_info_instance_settings($type) { - $info = field_info_field_types($type); - return isset($info['instance_settings']) ? $info['instance_settings'] : array(); -} - -/** - * Returns a field widget's default settings. - * - * @param $type - * A widget type name. - * - * @return - * The widget type's default settings, as provided by - * hook_field_widget_info(), or an empty array if type or settings are - * undefined. - */ -function field_info_widget_settings($type) { - $info = field_info_widget_types($type); - return isset($info['settings']) ? $info['settings'] : array(); -} - -/** - * Returns a field formatter's default settings. - * - * @param $type - * A field formatter type name. - * - * @return - * The formatter type's default settings, as provided by - * hook_field_formatter_info(), or an empty array if type or settings are - * undefined. - */ -function field_info_formatter_settings($type) { - $info = field_info_formatter_types($type); - return isset($info['settings']) ? $info['settings'] : array(); -} - -/** - * Returns a field storage type's default settings. - * - * @param $type - * A field storage type name. - * - * @return - * The storage type's default settings, as provided by - * hook_field_storage_info(), or an empty array if type or settings are - * undefined. - */ -function field_info_storage_settings($type) { - $info = field_info_storage_types($type); - return isset($info['settings']) ? $info['settings'] : array(); -} - -/** - * @} End of "defgroup field_info" - */ diff --git a/modules/field/field.install b/modules/field/field.install deleted file mode 100644 index 16b09e1c925..00000000000 --- a/modules/field/field.install +++ /dev/null @@ -1,377 +0,0 @@ -<?php - -/** - * @file - * Install, update and uninstall functions for the field module. - */ - -/** - * Implements hook_schema(). - */ -function field_schema() { - // Static (meta) tables. - $schema['field_config'] = array( - 'fields' => array( - 'id' => array( - 'type' => 'serial', - 'not null' => TRUE, - 'description' => 'The primary identifier for a field', - ), - 'field_name' => array( - 'type' => 'varchar', - 'length' => 32, - 'not null' => TRUE, - 'description' => 'The name of this field. Non-deleted field names are unique, but multiple deleted fields can have the same name.', - ), - 'type' => array( - 'type' => 'varchar', - 'length' => 128, - 'not null' => TRUE, - 'description' => 'The type of this field.', - ), - 'module' => array( - 'type' => 'varchar', - 'length' => 128, - 'not null' => TRUE, - 'default' => '', - 'description' => 'The module that implements the field type.', - ), - 'active' => array( - 'type' => 'int', - 'size' => 'tiny', - 'not null' => TRUE, - 'default' => 0, - 'description' => 'Boolean indicating whether the module that implements the field type is enabled.', - ), - 'storage_type' => array( - 'type' => 'varchar', - 'length' => 128, - 'not null' => TRUE, - 'description' => 'The storage backend for the field.', - ), - 'storage_module' => array( - 'type' => 'varchar', - 'length' => 128, - 'not null' => TRUE, - 'default' => '', - 'description' => 'The module that implements the storage backend.', - ), - 'storage_active' => array( - 'type' => 'int', - 'size' => 'tiny', - 'not null' => TRUE, - 'default' => 0, - 'description' => 'Boolean indicating whether the module that implements the storage backend is enabled.', - ), - 'locked' => array( - 'type' => 'int', - 'size' => 'tiny', - 'not null' => TRUE, - 'default' => 0, - 'description' => '@TODO', - ), - 'data' => array( - 'type' => 'blob', - 'size' => 'big', - 'not null' => TRUE, - 'serialize' => TRUE, - 'description' => 'Serialized data containing the field properties that do not warrant a dedicated column.', - ), - 'cardinality' => array( - 'type' => 'int', - 'size' => 'tiny', - 'not null' => TRUE, - 'default' => 0, - ), - 'translatable' => array( - 'type' => 'int', - 'size' => 'tiny', - 'not null' => TRUE, - 'default' => 0, - ), - 'deleted' => array( - 'type' => 'int', - 'size' => 'tiny', - 'not null' => TRUE, - 'default' => 0, - ), - ), - 'primary key' => array('id'), - 'indexes' => array( - 'field_name' => array('field_name'), - // Used by field_read_fields(). - 'active' => array('active'), - 'storage_active' => array('storage_active'), - 'deleted' => array('deleted'), - // Used by field_modules_disabled(). - 'module' => array('module'), - 'storage_module' => array('storage_module'), - // Used by field_associate_fields(). - 'type' => array('type'), - 'storage_type' => array('storage_type'), - ), - ); - $schema['field_config_instance'] = array( - 'fields' => array( - 'id' => array( - 'type' => 'serial', - 'not null' => TRUE, - 'description' => 'The primary identifier for a field instance', - ), - 'field_id' => array( - 'type' => 'int', - 'not null' => TRUE, - 'description' => 'The identifier of the field attached by this instance', - ), - 'field_name' => array( - 'type' => 'varchar', - 'length' => 32, - 'not null' => TRUE, - 'default' => '' - ), - 'entity_type' => array( - 'type' => 'varchar', - 'length' => 32, - 'not null' => TRUE, - 'default' => '' - ), - 'bundle' => array( - 'type' => 'varchar', - 'length' => 128, - 'not null' => TRUE, - 'default' => '' - ), - 'data' => array( - 'type' => 'blob', - 'size' => 'big', - 'not null' => TRUE, - 'serialize' => TRUE, - ), - 'deleted' => array( - 'type' => 'int', - 'size' => 'tiny', - 'not null' => TRUE, - 'default' => 0, - ), - ), - 'primary key' => array('id'), - 'indexes' => array( - // Used by field_delete_instance(). - 'field_name_bundle' => array('field_name', 'entity_type', 'bundle'), - // Used by field_read_instances(). - 'deleted' => array('deleted'), - ), - ); - $schema['cache_field'] = drupal_get_schema_unprocessed('system', 'cache'); - - return $schema; -} - -/** - * Utility function: create a field by writing directly to the database. - * - * @ingroup update-api-7.x-to-8.x - */ -function _update_7000_field_create_field(&$field) { - // Merge in default values.` - $field += array( - 'entity_types' => array(), - 'cardinality' => 1, - 'translatable' => FALSE, - 'locked' => FALSE, - 'settings' => array(), - 'indexes' => array(), - 'deleted' => 0, - 'active' => 1, - ); - - // Set storage. - $field['storage'] = array( - 'type' => 'field_sql_storage', - 'settings' => array(), - 'module' => 'field_sql_storage', - 'active' => 1, - ); - - // Fetch the field schema to initialize columns and indexes. The field module - // is not guaranteed to be loaded at this point. - module_load_install($field['module']); - $schema = (array) module_invoke($field['module'], 'field_schema', $field); - $schema += array('columns' => array(), 'indexes' => array()); - // 'columns' are hardcoded in the field type. - $field['columns'] = $schema['columns']; - // 'indexes' can be both hardcoded in the field type, and specified in the - // incoming $field definition. - $field['indexes'] += $schema['indexes']; - - // The serialized 'data' column contains everything from $field that does not - // have its own column and is not automatically populated when the field is - // read. - $data = $field; - unset($data['columns'], $data['field_name'], $data['type'], $data['active'], $data['module'], $data['storage_type'], $data['storage_active'], $data['storage_module'], $data['locked'], $data['cardinality'], $data['deleted']); - // Additionally, do not save the 'bundles' property populated by - // field_info_field(). - unset($data['bundles']); - - // Write the field to the database. - $record = array( - 'field_name' => $field['field_name'], - 'type' => $field['type'], - 'module' => $field['module'], - 'active' => (int) $field['active'], - 'storage_type' => $field['storage']['type'], - 'storage_module' => $field['storage']['module'], - 'storage_active' => (int) $field['storage']['active'], - 'locked' => (int) $field['locked'], - 'data' => serialize($data), - 'cardinality' => $field['cardinality'], - 'translatable' => (int) $field['translatable'], - 'deleted' => (int) $field['deleted'], - ); - // We don't use drupal_write_record() here because it depends on the schema. - $field['id'] = db_insert('field_config') - ->fields($record) - ->execute(); - - // Create storage for the field. - field_sql_storage_field_storage_create_field($field); -} - -/** - * Utility function: delete a field stored in SQL storage directly from the database. - * - * To protect user data, this function can only be used to delete fields once - * all information it stored is gone. Delete all data from the - * field_data_$field_name table before calling by either manually issuing - * delete queries against it or using _update_7000_field_delete_instance(). - * - * @param $field_name - * The field name to delete. - * - * @ingroup update-api-7.x-to-8.x - */ -function _update_7000_field_delete_field($field_name) { - $table_name = 'field_data_' . $field_name; - if (db_select($table_name)->range(0, 1)->countQuery()->execute()->fetchField()) { - $t = get_t(); - throw new Exception($t('This function can only be used to delete fields without data')); - } - // Delete all instances. - db_delete('field_config_instance') - ->condition('field_name', $field_name) - ->execute(); - - // Nuke field data and revision tables. - db_drop_table($table_name); - db_drop_table('field_revision_' . $field_name); - - // Delete the field. - db_delete('field_config') - ->condition('field_name', $field_name) - ->execute(); -} - - -/** - * Utility function: delete an instance and all its data of a field stored in SQL Storage. - * - * BEWARE: this function deletes user data from the field storage tables. - * - * @ingroup update-api-7.x-to-8.x - */ -function _update_7000_field_delete_instance($field_name, $entity_type, $bundle) { - // Delete field instance configuration data. - db_delete('field_config_instance') - ->condition('field_name', $field_name) - ->condition('entity_type', $entity_type) - ->condition('bundle', $bundle) - ->execute(); - - // Nuke data. - db_delete('field_data_' . $field_name) - ->condition('entity_type', $entity_type) - ->condition('bundle', $bundle) - ->execute(); - db_delete('field_revision_' . $field_name) - ->condition('entity_type', $entity_type) - ->condition('bundle', $bundle) - ->execute(); -} - -/** - * Utility function: fetch all the field definitions from the database. - * - * Warning: unlike the field_read_fields() API function, this function returns - * all fields by default, including deleted and inactive fields, unless - * specified otherwise in the $conditions parameter. - * - * @param $conditions - * An array of conditions to limit the select query to. - * @param $key - * The name of the field property the return array is indexed by. Using - * anything else than 'id' might cause incomplete results if the $conditions - * do not filter out deleted fields. - * - * @return - * An array of fields matching $conditions, keyed by the property specified - * by the $key parameter. - * @ingroup update-api-7.x-to-8.x - */ -function _update_7000_field_read_fields(array $conditions = array(), $key = 'id') { - $fields = array(); - $query = db_select('field_config', 'fc', array('fetch' => PDO::FETCH_ASSOC)) - ->fields('fc'); - foreach ($conditions as $column => $value) { - $query->condition($column, $value); - } - foreach ($query->execute() as $record) { - $field = unserialize($record['data']); - $field['id'] = $record['id']; - $field['field_name'] = $record['field_name']; - $field['type'] = $record['type']; - $field['module'] = $record['module']; - $field['active'] = $record['active']; - $field['storage']['type'] = $record['storage_type']; - $field['storage']['module'] = $record['storage_module']; - $field['storage']['active'] = $record['storage_active']; - $field['locked'] = $record['locked']; - $field['cardinality'] = $record['cardinality']; - $field['translatable'] = $record['translatable']; - $field['deleted'] = $record['deleted']; - - $fields[$field[$key]] = $field; - } - return $fields; -} - -/** - * Utility function: write a field instance directly to the database. - * - * @ingroup update-api-7.x-to-8.x - */ -function _update_7000_field_create_instance($field, &$instance) { - // Merge in defaults. - $instance += array( - 'field_id' => $field['id'], - 'field_name' => $field['field_name'], - 'deleted' => 0, - ); - - // The serialized 'data' column contains everything from $instance that does - // not have its own column and is not automatically populated when the - // instance is read. - $data = $instance; - unset($data['id'], $data['field_id'], $data['field_name'], $data['entity_type'], $data['bundle'], $data['deleted']); - - $record = array( - 'field_id' => $instance['field_id'], - 'field_name' => $instance['field_name'], - 'entity_type' => $instance['entity_type'], - 'bundle' => $instance['bundle'], - 'data' => serialize($data), - 'deleted' => (int) $instance['deleted'], - ); - $instance['id'] = db_insert('field_config_instance') - ->fields($record) - ->execute(); -} diff --git a/modules/field/field.module b/modules/field/field.module deleted file mode 100644 index 3cda80e5133..00000000000 --- a/modules/field/field.module +++ /dev/null @@ -1,1206 +0,0 @@ -<?php -/** - * @file - * Attach custom data fields to Drupal entities. - */ - -/** - * Base class for all exceptions thrown by Field API functions. - * - * This class has no functionality of its own other than allowing all - * Field API exceptions to be caught by a single catch block. - */ -class FieldException extends Exception {} - -/* - * Load all public Field API functions. Drupal currently has no - * mechanism for auto-loading core APIs, so we have to load them on - * every page request. - */ -require_once DRUPAL_ROOT . '/modules/field/field.crud.inc'; -require_once DRUPAL_ROOT . '/modules/field/field.default.inc'; -require_once DRUPAL_ROOT . '/modules/field/field.info.inc'; -require_once DRUPAL_ROOT . '/modules/field/field.multilingual.inc'; -require_once DRUPAL_ROOT . '/modules/field/field.attach.inc'; -require_once DRUPAL_ROOT . '/modules/field/field.form.inc'; - -/** - * @defgroup field Field API - * @{ - * Attach custom data fields to Drupal entities. - * - * The Field API allows custom data fields to be attached to Drupal - * entities and takes care of storing, loading, editing, and rendering - * field data. Any entity type (node, user, etc.) can use the Field - * API to make itself "fieldable" and thus allow fields to be attached - * to it. Other modules can provide a user interface for managing custom - * fields via a web browser as well as a wide and flexible variety of - * data type, form element, and display format capabilities. - * - * 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 entities. 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 and - * is related to a single fieldable entity type. - * - * For example, suppose a site administrator wants Article nodes to - * have a subtitle and photo. Using the Field API or Field 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 'node' bundle 'article' and one attaching - * the field 'photo' to the 'node' bundle 'article'. When the node - * system uses the Field Attach API to load all fields for an Article - * node, it passes the node's entity type (which is 'node') and - * 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 'node' bundle 'article'. - * - * Field definitions are represented as an array of key/value pairs. - * - * array $field: - * - id (integer, read-only) - * The primary identifier of the field. It is assigned automatically - * by field_create_field(). - * - field_name (string) - * The name of the field. Each field name is unique within Field API. - * When a field is attached to an entity, the field's data is stored - * in $entity->$field_name. Maximum length is 32 characters. - * - type (string) - * The type of the field, such as 'text' or 'image'. Field types - * are defined by modules that implement hook_field_info(). - * - entity_types (array) - * The array of entity types that can hold instances of this field. If - * empty or not specified, the field can have instances in any entity type. - * - cardinality (integer) - * The number of values the field can hold. Legal values are any - * positive integer or FIELD_CARDINALITY_UNLIMITED. - * - translatable (integer) - * Whether the field is translatable. - * - locked (integer) - * Whether or not the field is available for editing. If TRUE, users can't - * change field settings or create new instances of the field in the UI. - * Defaults to FALSE. - * - module (string, read-only) - * The name of the module that implements the field type. - * - active (integer, read-only) - * TRUE if the module that implements the field type is currently - * enabled, FALSE otherwise. - * - deleted (integer, read-only) - * TRUE if this field has been deleted, FALSE otherwise. Deleted - * fields are ignored by the Field Attach API. This property exists - * because fields can be marked for deletion but only actually - * destroyed by a separate garbage-collection process. - * - columns (array, read-only). - * An array of the Field API columns used to store each value of - * this field. The column list may depend on field settings; it is - * not constant per field type. Field API column specifications are - * exactly like Schema API column specifications but, depending on - * the field storage module in use, the name of the column may not - * represent an actual column in an SQL database. - * - indexes (array). - * An array of indexes on data columns, using the same definition format - * as Schema API index specifications. Only columns that appear in the - * 'columns' setting are allowed. Note that field types can specify - * default indexes, which can be modified or added to when - * creating a field. - * - foreign keys: (optional) An associative array of relations, using the same - * structure as the 'foreign keys' definition of hook_schema(). Note, however, - * that the field data is not necessarily stored in SQL. Also, the possible - * usage is limited, as you cannot specify another field as related, only - * existing SQL tables, such as filter formats. - * - settings (array) - * A sub-array of key/value pairs of field-type-specific settings. Each - * field type module defines and documents its own field settings. - * - storage (array) - * A sub-array of key/value pairs identifying the storage backend to use for - * the for the field. - * - type (string) - * The storage backend used by the field. Storage backends are defined - * by modules that implement hook_field_storage_info(). - * - module (string, read-only) - * The name of the module that implements the storage backend. - * - active (integer, read-only) - * TRUE if the module that implements the storage backend is currently - * enabled, FALSE otherwise. - * - settings (array) - * A sub-array of key/value pairs of settings. Each storage backend - * defines and documents its own settings. - * - * Field instance definitions are represented as an array of key/value pairs. - * - * array $instance: - * - id (integer, read-only) - * The primary identifier of this field instance. It is assigned - * automatically by field_create_instance(). - * - field_id (integer, read-only) - * The foreign key of the field attached to the bundle by this instance. - * It is populated automatically by field_create_instance(). - * - field_name (string) - * The name of the field attached to the bundle by this instance. - * - entity_type (string) - * The name of the entity type the instance is attached to. - * - bundle (string) - * The name of the bundle that the field is attached to. - * - label (string) - * A human-readable label for the field when used with this - * bundle. For example, the label will be the title of Form API - * elements for this instance. - * - description (string) - * A human-readable description for the field when used with this - * bundle. For example, the description will be the help text of - * Form API elements for this instance. - * - required (integer) - * TRUE if a value for this field is required when used with this - * bundle, FALSE otherwise. Currently, required-ness is only enforced - * during Form API operations, not by field_attach_load(), - * field_attach_insert(), or field_attach_update(). - * - default_value_function (string) - * The name of the function, if any, that will provide a default value. - * - default_value (array) - * If default_value_function is not set, then fixed values can be provided. - * - deleted (integer, read-only) - * TRUE if this instance has been deleted, FALSE otherwise. - * Deleted instances are ignored by the Field Attach API. - * This property exists because instances can be marked for deletion but - * only actually destroyed by a separate garbage-collection process. - * - settings (array) - * A sub-array of key/value pairs of field-type-specific instance - * settings. Each field type module defines and documents its own - * instance settings. - * - widget (array) - * A sub-array of key/value pairs identifying the Form API input widget - * for the field when used by this bundle. - * - type (string) - * The type of the widget, such as text_textfield. Widget types - * are defined by modules that implement hook_field_widget_info(). - * - settings (array) - * A sub-array of key/value pairs of widget-type-specific settings. - * Each field widget type module defines and documents its own - * widget settings. - * - weight (float) - * The weight of the widget relative to the other elements in entity - * edit forms. - * - module (string, read-only) - * The name of the module that implements the widget type. - * - display (array) - * A sub-array of key/value pairs identifying the way field values should - * be displayed in each of the entity type's view modes, plus the 'default' - * mode. For each view mode, Field UI lets site administrators define - * whether they want to use a dedicated set of display options or the - * 'default' options to reduce the number of displays to maintain as they - * add new fields. For nodes, on a fresh install, only the 'teaser' view - * mode is configured to use custom display options, all other view modes - * defined use the 'default' options by default. When programmatically - * adding field instances on nodes, it is therefore recommended to at least - * specify display options for 'default' and 'teaser'. - * - default (array) - * A sub-array of key/value pairs describing the display options to be - * used when the field is being displayed in view modes that are not - * configured to use dedicated display options. - * - label (string) - * Position of the label. 'inline', 'above' and 'hidden' are the - * values recognized by the default 'field' theme implementation. - * - type (string) - * The type of the display formatter, or 'hidden' for no display. - * - settings (array) - * A sub-array of key/value pairs of display options specific to - * the formatter. - * - weight (float) - * The weight of the field relative to the other entity components - * displayed in this view mode. - * - module (string, read-only) - * The name of the module which implements the display formatter. - * - some_mode - * A sub-array of key/value pairs describing the display options to be - * used when the field is being displayed in the 'some_mode' view mode. - * Those options will only be actually applied at run time if the view - * mode is not configured to use default settings for this bundle. - * - ... - * - other_mode - * - ... - * - * The (default) render arrays produced for field instances are documented at - * field_attach_view(). - * - * Bundles are represented by two strings, an entity type and a bundle name. - * - * - @link field_types Field Types API @endlink. Defines field types, - * widget types, and display formatters. Field modules use this API - * to provide field types like Text and Node Reference along with the - * associated form elements and display formatters. - * - * - @link field_crud Field CRUD API @endlink. Create, updates, and - * deletes fields, bundles (a.k.a. "content types"), and instances. - * Modules use this API, often in hook_install(), to create - * custom data structures. - * - * - @link field_attach Field Attach API @endlink. Connects entity - * types to the Field API. Field Attach API functions load, store, - * generate Form API structures, display, and perform a variety of - * other functions for field data connected to individual entities. - * Fieldable entity types like node and user use this API to make - * themselves fieldable. - * - * - @link field_info Field Info API @endlink. Exposes information - * about all fields, instances, widgets, and related information - * defined by or with the Field API. - * - * - @link field_storage Field Storage API @endlink. Provides a - * pluggable back-end storage system for actual field data. The - * default implementation, field_sql_storage.module, stores field data - * in the local SQL database. - * - * - @link field_purge Field API bulk data deletion @endlink. Cleans - * up after bulk deletion operations such as field_delete_field() - * and field_delete_instance(). - * - * - @link field_language Field language API @endlink. Provides native - * multilingual support for the Field API. - */ - -/** - * Value for field API indicating a field accepts an unlimited number of values. - */ -define('FIELD_CARDINALITY_UNLIMITED', -1); - -/** - * Value for field API indicating a widget doesn't accept default values. - * - * @see hook_field_widget_info() - */ -define('FIELD_BEHAVIOR_NONE', 0x0001); - -/** - * Value for field API concerning widget default and multiple value settings. - * - * @see hook_field_widget_info() - * - * When used in a widget default context, indicates the widget accepts default - * values. When used in a multiple value context for a widget that allows the - * input of one single field value, indicates that the widget will be repeated - * for each value input. - */ -define('FIELD_BEHAVIOR_DEFAULT', 0x0002); - -/** - * Value for field API indicating a widget can receive several field values. - * - * @see hook_field_widget_info() - */ -define('FIELD_BEHAVIOR_CUSTOM', 0x0004); - -/** - * Age argument for loading the most recent version of an entity's - * field data with field_attach_load(). - */ -define('FIELD_LOAD_CURRENT', 'FIELD_LOAD_CURRENT'); - -/** - * Age argument for loading the version of an entity's field data - * specified in the entity with field_attach_load(). - */ -define('FIELD_LOAD_REVISION', 'FIELD_LOAD_REVISION'); - -/** - * Exception class thrown by hook_field_update_forbid(). - */ -class FieldUpdateForbiddenException extends FieldException {} - -/** - * Implements hook_help(). - */ -function field_help($path, $arg) { - switch ($path) { - case 'admin/help#field': - $output = ''; - $output .= '<h3>' . t('About') . '</h3>'; - $output .= '<p>' . t('The Field module allows custom data fields to be defined for <em>entity</em> types (entities include content items, comments, user accounts, and taxonomy terms). The Field module takes care of storing, loading, editing, and rendering field data. Most users will not interact with the Field module directly, but will instead use the <a href="@field-ui-help">Field UI module</a> user interface. Module developers can use the Field API to make new entity types "fieldable" and thus allow fields to be attached to them. For more information, see the online handbook entry for <a href="@field">Field module</a>.', array('@field-ui-help' => url('admin/help/field_ui'), '@field' => 'http://drupal.org/handbook/modules/field')) . '</p>'; - $output .= '<h3>' . t('Uses') . '</h3>'; - $output .= '<dl>'; - $output .= '<dt>' . t('Enabling field types') . '</dt>'; - $output .= '<dd>' . t('The Field module provides the infrastructure for fields and field attachment; the field types and input widgets themselves are provided by additional modules. Some of the modules are required; the optional modules can be enabled from the <a href="@modules">Modules administration page</a>. Drupal core includes the following field type modules: Number (required), Text (required), List (required), Taxonomy (optional), Image (optional), and File (optional); the required Options module provides input widgets for other field modules. Additional fields and widgets may be provided by contributed modules, which you can find in the <a href="@contrib">contributed module section of Drupal.org</a>. Currently enabled field and input widget modules:', array('@modules' => url('admin/modules'), '@contrib' => 'http://drupal.org/project/modules', '@options' => url('admin/help/options'))); - - // Make a list of all widget and field modules currently enabled, in - // order by displayed module name (module names are not translated). - $items = array(); - $info = system_get_info('module'); - $modules = array_merge(module_implements('field_info'), module_implements('field_widget_info')); - $modules = array_unique($modules); - sort($modules); - foreach ($modules as $module) { - $display = $info[$module]['name']; - if (module_hook($module, 'help')) { - $items['items'][] = l($display, 'admin/help/' . $module); - } - else { - $items['items'][] = $display; - } - } - $output .= theme('item_list', $items) . '</dd>'; - $output .= '<dt>' . t('Managing field data storage') . '</dt>'; - $output .= '<dd>' . t('Developers of field modules can either use the default <a href="@sql-store">Field SQL storage module</a> to store data for their fields, or a contributed or custom module developed using the <a href="@storage-api">field storage API</a>.', array('@storage-api' => 'http://api.drupal.org/api/group/field_storage/7', '@sql-store' => url('admin/help/field_sql_storage'))) . '</dd>'; - $output .= '</dl>'; - return $output; - } -} - -/** - * Implements hook_theme(). - */ -function field_theme() { - return array( - 'field' => array( - 'render element' => 'element', - ), - 'field_multiple_value_form' => array( - 'render element' => 'element', - ), - ); -} - -/** - * Implements hook_cron(). - * - * Purges some deleted Field API data, if any exists. - */ -function field_cron() { - field_sync_field_status(); - $limit = variable_get('field_purge_batch_size', 10); - field_purge_batch($limit); -} - -/** - * Implements hook_modules_uninstalled(). - */ -function field_modules_uninstalled($modules) { - module_load_include('inc', 'field', 'field.crud'); - foreach ($modules as $module) { - // TODO D7: field_module_delete is not yet implemented - // field_module_delete($module); - } -} - -/** - * Implements hook_system_info_alter(). - * - * Goes through a list of all modules that provide a field type, and makes them - * required if there are any active fields of that type. - */ -function field_system_info_alter(&$info, $file, $type) { - if ($type == 'module' && module_hook($file->name, 'field_info')) { - $fields = field_read_fields(array('module' => $file->name), array('include_deleted' => TRUE)); - if ($fields) { - $info['required'] = TRUE; - - // Provide an explanation message (only mention pending deletions if there - // remains no actual, non-deleted fields) - $non_deleted = FALSE; - foreach ($fields as $field) { - if (empty($field['deleted'])) { - $non_deleted = TRUE; - break; - } - } - if ($non_deleted) { - if (module_exists('field_ui')) { - $explanation = t('Field type(s) in use - see !link', array('!link' => l(t('Field list'), 'admin/reports/fields'))); - } - else { - $explanation = t('Fields type(s) in use'); - } - } - else { - $explanation = t('Fields pending deletion'); - } - $info['explanation'] = $explanation; - } - } -} - -/** - * Implements hook_flush_caches(). - */ -function field_flush_caches() { - field_sync_field_status(); - return array('field'); -} - -/** - * Refreshes the 'active' and 'storage_active' columns for fields. - */ -function field_sync_field_status() { - // Refresh the 'active' and 'storage_active' columns according to the current - // set of enabled modules. - $all_modules = system_rebuild_module_data(); - $modules = array(); - foreach ($all_modules as $module_name => $module) { - if ($module->status) { - $modules[] = $module_name; - field_associate_fields($module_name); - } - } - db_update('field_config') - ->fields(array('active' => 0)) - ->condition('module', $modules, 'NOT IN') - ->execute(); - db_update('field_config') - ->fields(array('storage_active' => 0)) - ->condition('storage_module', $modules, 'NOT IN') - ->execute(); -} - -/** - * Allows a module to update the database for fields and columns it controls. - * - * @param $module - * The name of the module to update on. - */ -function field_associate_fields($module) { - // Associate field types. - $field_types = (array) module_invoke($module, 'field_info'); - foreach ($field_types as $name => $field_info) { - db_update('field_config') - ->fields(array('module' => $module, 'active' => 1)) - ->condition('type', $name) - ->execute(); - } - // Associate storage backends. - $storage_types = (array) module_invoke($module, 'field_storage_info'); - foreach ($storage_types as $name => $storage_info) { - db_update('field_config') - ->fields(array('storage_module' => $module, 'storage_active' => 1)) - ->condition('storage_type', $name) - ->execute(); - } -} - -/** - * Helper function to get the default value for a field on an entity. - * - * @param $entity_type - * The type of $entity; e.g., 'node' or 'user'. - * @param $entity - * The entity for the operation. - * @param $field - * The field structure. - * @param $instance - * The instance structure. - * @param $langcode - * The field language to fill-in with the default value. - */ -function field_get_default_value($entity_type, $entity, $field, $instance, $langcode = NULL) { - $items = array(); - if (!empty($instance['default_value_function'])) { - $function = $instance['default_value_function']; - if (function_exists($function)) { - $items = $function($entity_type, $entity, $field, $instance, $langcode); - } - } - elseif (!empty($instance['default_value'])) { - $items = $instance['default_value']; - } - return $items; -} - -/** - * Helper function to filter out empty field values. - * - * @param $field - * The field definition. - * @param $items - * The field values to filter. - * - * @return - * The array of items without empty field values. The function also renumbers - * the array keys to ensure sequential deltas. - */ -function _field_filter_items($field, $items) { - $function = $field['module'] . '_field_is_empty'; - foreach ((array) $items as $delta => $item) { - // Explicitly break if the function is undefined. - if ($function($item, $field)) { - unset($items[$delta]); - } - } - return array_values($items); -} - -/** - * Helper function to sort items in a field according to - * user drag-n-drop reordering. - */ -function _field_sort_items($field, $items) { - if (($field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED) && isset($items[0]['_weight'])) { - usort($items, '_field_sort_items_helper'); - foreach ($items as $delta => $item) { - if (is_array($items[$delta])) { - unset($items[$delta]['_weight']); - } - } - } - return $items; -} - -/** - * Sort function for items order. - * (copied form element_sort(), which acts on #weight keys) - */ -function _field_sort_items_helper($a, $b) { - $a_weight = (is_array($a) ? $a['_weight'] : 0); - $b_weight = (is_array($b) ? $b['_weight'] : 0); - return $a_weight - $b_weight; -} - -/** - * Same as above, using ['_weight']['#value'] - */ -function _field_sort_items_value_helper($a, $b) { - $a_weight = (is_array($a) && isset($a['_weight']['#value']) ? $a['_weight']['#value'] : 0); - $b_weight = (is_array($b) && isset($b['_weight']['#value']) ? $b['_weight']['#value'] : 0); - return $a_weight - $b_weight; -} - -/** - * Gets or sets administratively defined bundle settings. - * - * For each bundle, settings are provided as a nested array with the following - * structure: - * @code - * array( - * 'view_modes' => array( - * // One sub-array per view mode for the entity type: - * 'full' => array( - * 'custom_display' => Whether the view mode uses custom display - * settings or settings of the 'default' mode, - * ), - * 'teaser' => ... - * ), - * 'extra_fields' => array( - * 'form' => array( - * // One sub-array per pseudo-field in displayed entities: - * 'extra_field_1' => array( - * 'weight' => The weight of the pseudo-field, - * ), - * 'extra_field_2' => ... - * ), - * 'display' => array( - * // One sub-array per pseudo-field in displayed entities: - * 'extra_field_1' => array( - * // One sub-array per view mode for the entity type, including - * // the 'default' mode: - * 'default' => array( - * 'weight' => The weight of the pseudo-field, - * 'visible' => TRUE if the pseudo-field is visible, FALSE if hidden, - * ), - * 'full' => ... - * ), - * 'extra_field_2' => ... - * ), - * ), - * ); - * @endcode - * - * @param $entity_type - * The type of $entity; e.g., 'node' or 'user'. - * @param $bundle - * The bundle name. - * @param $settings - * (optional) The settings to store. - * - * @return - * If no $settings are passed, the current settings are returned. - */ -function field_bundle_settings($entity_type, $bundle, $settings = NULL) { - if (isset($settings)) { - variable_set('field_bundle_settings_' . $entity_type . '__' . $bundle, $settings); - field_info_cache_clear(); - } - else { - $settings = variable_get('field_bundle_settings_' . $entity_type . '__' . $bundle, array()); - $settings += array( - 'view_modes' => array(), - 'extra_fields' => array(), - ); - $settings['extra_fields'] += array( - 'form' => array(), - 'display' => array(), - ); - - return $settings; - } -} - -/** - * Returns view mode settings in a given bundle. - * - * @param $entity_type - * The type of entity; e.g. 'node' or 'user'. - * @param $bundle - * The bundle name to return view mode settings for. - * - * @return - * An array keyed by view mode, with the following key/value pairs: - * - custom_settings: Boolean specifying whether the view mode uses a - * dedicated set of display options (TRUE), or the 'default' options - * (FALSE). Defaults to FALSE. - */ -function field_view_mode_settings($entity_type, $bundle) { - $cache = &drupal_static(__FUNCTION__, array()); - - if (!isset($cache[$entity_type][$bundle])) { - $bundle_settings = field_bundle_settings($entity_type, $bundle); - $settings = $bundle_settings['view_modes']; - // Include view modes for which nothing has been stored yet, but whose - // definition in hook_entity_info() specify they should use custom settings - // by default. - $entity_info = entity_get_info($entity_type); - foreach ($entity_info['view modes'] as $view_mode => $view_mode_info) { - if (!isset($settings[$view_mode]['custom_settings']) && $view_mode_info['custom settings']) { - $settings[$view_mode]['custom_settings'] = TRUE; - } - } - $cache[$entity_type][$bundle] = $settings; - } - - return $cache[$entity_type][$bundle]; -} - -/** - * Returns the display settings to use for an instance in a given view mode. - * - * @param $instance - * The field instance being displayed. - * @param $view_mode - * The view mode. - * @param $entity - * The entity being displayed. - * - * @return - * The display settings to be used when displaying the field values. - */ -function field_get_display($instance, $view_mode, $entity) { - // Check whether the view mode uses custom display settings or the 'default' - // mode. - $view_mode_settings = field_view_mode_settings($instance['entity_type'], $instance['bundle']); - $actual_mode = (!empty($view_mode_settings[$view_mode]['custom_settings']) ? $view_mode : 'default'); - $display = $instance['display'][$actual_mode]; - - // Let modules alter the display settings. - $context = array( - 'entity_type' => $instance['entity_type'], - 'field' => field_info_field($instance['field_name']), - 'instance' => $instance, - 'entity' => $entity, - 'view_mode' => $view_mode, - ); - drupal_alter(array('field_display', 'field_display_' . $instance['entity_type']), $display, $context); - - return $display; -} - -/** - * Returns the display settings to use for pseudo-fields in a given view mode. - * - * @param $entity_type - * The type of $entity; e.g., 'node' or 'user'. - * @param $bundle - * The bundle name. - * @param $view_mode - * The view mode. - * - * @return - * The display settings to be used when viewing the bundle's pseudo-fields. - */ -function field_extra_fields_get_display($entity_type, $bundle, $view_mode) { - // Check whether the view mode uses custom display settings or the 'default' - // mode. - $view_mode_settings = field_view_mode_settings($entity_type, $bundle); - $actual_mode = (!empty($view_mode_settings[$view_mode]['custom_settings'])) ? $view_mode : 'default'; - $extra_fields = field_info_extra_fields($entity_type, $bundle, 'display'); - - $displays = array(); - foreach ($extra_fields as $name => $value) { - $displays[$name] = $extra_fields[$name]['display'][$actual_mode]; - } - - // Let modules alter the display settings. - $context = array( - 'entity_type' => $entity_type, - 'bundle' => $bundle, - 'view_mode' => $view_mode, - ); - drupal_alter('field_extra_fields_display', $displays, $context); - - return $displays; -} - -/** - * Pre-render callback to adjust weights and visibility of non-field elements. - */ -function _field_extra_fields_pre_render($elements) { - $entity_type = $elements['#entity_type']; - $bundle = $elements['#bundle']; - - if (isset($elements['#type']) && $elements['#type'] == 'form') { - $extra_fields = field_info_extra_fields($entity_type, $bundle, 'form'); - foreach ($extra_fields as $name => $settings) { - if (isset($elements[$name])) { - $elements[$name]['#weight'] = $settings['weight']; - } - } - } - elseif (isset($elements['#view_mode'])) { - $view_mode = $elements['#view_mode']; - $extra_fields = field_extra_fields_get_display($entity_type, $bundle, $view_mode); - foreach ($extra_fields as $name => $settings) { - if (isset($elements[$name])) { - $elements[$name]['#weight'] = $settings['weight']; - // Visibility: make sure we do not accidentally show a hidden element. - $elements[$name]['#access'] = isset($elements[$name]['#access']) ? ($elements[$name]['#access'] && $settings['visible']) : $settings['visible']; - } - } - } - - return $elements; -} - -/** - * Clear the field info and field data caches. - */ -function field_cache_clear() { - cache('field')->flush(); - field_info_cache_clear(); -} - -/** - * Like filter_xss_admin(), but with a shorter list of allowed tags. - * - * Used for items entered by administrators, like field descriptions, - * allowed values, where some (mainly inline) mark-up may be desired - * (so check_plain() is not acceptable). - */ -function field_filter_xss($string) { - return filter_xss($string, _field_filter_xss_allowed_tags()); -} - -/** - * List of tags allowed by field_filter_xss(). - */ -function _field_filter_xss_allowed_tags() { - return array('a', 'b', 'big', 'code', 'del', 'em', 'i', 'ins', 'pre', 'q', 'small', 'span', 'strong', 'sub', 'sup', 'tt', 'ol', 'ul', 'li', 'p', 'br', 'img'); -} - -/** - * Human-readable list of allowed tags, for display in help texts. - */ -function _field_filter_xss_display_allowed_tags() { - return '<' . implode('> <', _field_filter_xss_allowed_tags()) . '>'; -} - -/** - * Returns a renderable array for a single field value. - * - * @param $entity_type - * The type of $entity; e.g., 'node' or 'user'. - * @param $entity - * The entity containing the field to display. Must at least contain the id - * key and the field data to display. - * @param $field_name - * The name of the field to display. - * @param $item - * The field value to display, as found in - * $entity->field_name[$langcode][$delta]. - * @param $display - * Can be either the name of a view mode, or an array of display settings. - * See field_view_field() for more information. - * @param $langcode - * (Optional) The language of the value in $item. If not provided, the - * current language will be assumed. - * @return - * A renderable array for the field value. - */ -function field_view_value($entity_type, $entity, $field_name, $item, $display = array(), $langcode = NULL) { - $output = array(); - - if ($field = field_info_field($field_name)) { - // Determine the langcode that will be used by language fallback. - $langcode = field_language($entity_type, $entity, $field_name, $langcode); - - // Push the item as the single value for the field, and defer to - // field_view_field() to build the render array for the whole field. - $clone = clone $entity; - $clone->{$field_name}[$langcode] = array($item); - $elements = field_view_field($entity_type, $clone, $field_name, $display, $langcode); - - // Extract the part of the render array we need. - $output = isset($elements[0]) ? $elements[0] : array(); - if (isset($elements['#access'])) { - $output['#access'] = $elements['#access']; - } - } - - return $output; -} - -/** - * Returns a renderable array for the value of a single field in an entity. - * - * The resulting output is a fully themed field with label and multiple values. - * - * This function can be used by third-party modules that need to output an - * isolated field. - * - Do not use inside node (or other entities) templates, use - * render($content[FIELD_NAME]) instead. - * - Do not use to display all fields in an entity, use - * field_attach_prepare_view() and field_attach_view() instead. - * - The field_view_value() function can be used to output a single formatted - * field value, without label or wrapping field markup. - * - * The function takes care of invoking the prepare_view steps. It also respects - * field access permissions. - * - * @param $entity_type - * The type of $entity; e.g., 'node' or 'user'. - * @param $entity - * The entity containing the field to display. Must at least contain the id - * key and the field data to display. - * @param $field_name - * The name of the field to display. - * @param $display - * Can be either: - * - The name of a view mode. The field will be displayed according to the - * display settings specified for this view mode in the $instance - * definition for the field in the entity's bundle. - * If no display settings are found for the view mode, the settings for - * the 'default' view mode will be used. - * - An array of display settings, as found in the 'display' entry of - * $instance definitions. The following key/value pairs are allowed: - * - label: (string) Position of the label. The default 'field' theme - * implementation supports the values 'inline', 'above' and 'hidden'. - * Defaults to 'above'. - * - type: (string) The formatter to use. Defaults to the - * 'default_formatter' for the field type, specified in - * hook_field_info(). The default formatter will also be used if the - * requested formatter is not available. - * - settings: (array) Settings specific to the formatter. Defaults to the - * formatter's default settings, specified in - * hook_field_formatter_info(). - * - weight: (float) The weight to assign to the renderable element. - * Defaults to 0. - * @param $langcode - * (Optional) The language the field values are to be shown in. The site's - * current language fallback logic will be applied no values are available - * for the language. If no language is provided the current language will be - * used. - * @return - * A renderable array for the field value. - * - * @see field_view_value() - */ -function field_view_field($entity_type, $entity, $field_name, $display = array(), $langcode = NULL) { - $output = array(); - - if ($field = field_info_field($field_name)) { - if (is_array($display)) { - // When using custom display settings, fill in default values. - $display = _field_info_prepare_instance_display($field, $display); - } - - // Hook invocations are done through the _field_invoke() functions in - // 'single field' mode, to reuse the language fallback logic. - // Determine the actual language to display for the field, given the - // languages available in the field data. - $display_language = field_language($entity_type, $entity, $field_name, $langcode); - $options = array('field_name' => $field_name, 'language' => $display_language); - $null = NULL; - - // Invoke prepare_view steps if needed. - if (empty($entity->_field_view_prepared)) { - list($id) = entity_extract_ids($entity_type, $entity); - - // First let the field types do their preparation. - _field_invoke_multiple('prepare_view', $entity_type, array($id => $entity), $display, $null, $options); - // Then let the formatters do their own specific massaging. - _field_invoke_multiple_default('prepare_view', $entity_type, array($id => $entity), $display, $null, $options); - } - - // Build the renderable array. - $result = _field_invoke_default('view', $entity_type, $entity, $display, $null, $options); - - // Invoke hook_field_attach_view_alter() to let other modules alter the - // renderable array, as in a full field_attach_view() execution. - $context = array( - 'entity_type' => $entity_type, - 'entity' => $entity, - 'view_mode' => '_custom', - 'display' => $display, - ); - drupal_alter('field_attach_view', $result, $context); - - if (isset($result[$field_name])) { - $output = $result[$field_name]; - } - } - - return $output; -} - -/** - * Returns the field items in the language they currently would be displayed. - * - * @param $entity_type - * The type of $entity; e.g., 'node' or 'user'. - * @param $entity - * The entity containing the data to be displayed. - * @param $field_name - * The field to be displayed. - * @param $langcode - * (optional) The language code $entity->{$field_name} has to be displayed in. - * Defaults to the current language. - * - * @return - * An array of field items keyed by delta if available, FALSE otherwise. - */ -function field_get_items($entity_type, $entity, $field_name, $langcode = NULL) { - $langcode = field_language($entity_type, $entity, $field_name, $langcode); - return isset($entity->{$field_name}[$langcode]) ? $entity->{$field_name}[$langcode] : FALSE; -} - -/** - * Determine whether a field has any data. - * - * @param $field - * A field structure. - * @return - * TRUE if the field has data for any entity; FALSE otherwise. - */ -function field_has_data($field) { - $query = new EntityFieldQuery(); - return (bool) $query - ->fieldCondition($field) - ->range(0, 1) - ->count() - ->execute(); -} - -/** - * 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 $entity_type - * The type of $entity; e.g., 'node' or 'user'. - * @param $entity - * (optional) The entity for the operation. - * @param $account - * (optional) The account to check, if not given use currently logged in user. - * @return - * TRUE if the operation is allowed; - * FALSE if the operation is denied. - */ -function field_access($op, $field, $entity_type, $entity = NULL, $account = NULL) { - global $user; - - if (!isset($account)) { - $account = $user; - } - - foreach (module_implements('field_access') as $module) { - $function = $module . '_field_access'; - $access = $function($op, $field, $entity_type, $entity, $account); - if ($access === FALSE) { - return FALSE; - } - } - return TRUE; -} - -/** - * Helper function to extract the bundle name of from a bundle object. - * - * @param $entity_type - * The type of $entity; e.g., 'node' or 'user'. - * @param $bundle - * The bundle object (or string if bundles for this entity type do not exist - * as standalone objects). - * @return - * The bundle name. - */ -function field_extract_bundle($entity_type, $bundle) { - if (is_string($bundle)) { - return $bundle; - } - - $info = entity_get_info($entity_type); - if (is_object($bundle) && isset($info['bundle keys']['bundle']) && isset($bundle->{$info['bundle keys']['bundle']})) { - return $bundle->{$info['bundle keys']['bundle']}; - } -} - -/** - * Theme preprocess function for theme_field() and field.tpl.php. - * - * @see theme_field() - * @see field.tpl.php - */ -function template_preprocess_field(&$variables, $hook) { - $element = $variables['element']; - - // There's some overhead in calling check_plain() so only call it if the label - // variable is being displayed. Otherwise, set it to NULL to avoid PHP - // warnings if a theme implementation accesses the variable even when it's - // supposed to be hidden. If a theme implementation needs to print a hidden - // label, it needs to supply a preprocess function that sets it to the - // sanitized element title or whatever else is wanted in its place. - $variables['label_hidden'] = ($element['#label_display'] == 'hidden'); - $variables['label'] = $variables['label_hidden'] ? NULL : check_plain($element['#title']); - - // We want other preprocess functions and the theme implementation to have - // fast access to the field item render arrays. The item render array keys - // (deltas) should always be a subset of the keys in #items, and looping on - // those keys is faster than calling element_children() or looping on all keys - // within $element, since that requires traversal of all element properties. - $variables['items'] = array(); - foreach ($element['#items'] as $delta => $item) { - if (!empty($element[$delta])) { - $variables['items'][$delta] = $element[$delta]; - } - } - - // Add default CSS classes. Since there can be many fields rendered on a page, - // save some overhead by calling strtr() directly instead of - // drupal_html_class(). - $variables['field_name_css'] = strtr($element['#field_name'], '_', '-'); - $variables['field_type_css'] = strtr($element['#field_type'], '_', '-'); - $variables['classes_array'] = array( - 'field', - 'field-name-' . $variables['field_name_css'], - 'field-type-' . $variables['field_type_css'], - 'field-label-' . $element['#label_display'], - ); - // Add a "clearfix" class to the wrapper since we float the label and the - // field items in field.css if the label is inline. - if ($element['#label_display'] == 'inline') { - $variables['classes_array'][] = 'clearfix'; - } - - // Add specific suggestions that can override the default implementation. - $variables['theme_hook_suggestions'] = array( - 'field__' . $element['#field_type'], - 'field__' . $element['#field_name'], - 'field__' . $element['#bundle'], - 'field__' . $element['#field_name'] . '__' . $element['#bundle'], - ); -} - -/** - * Theme process function for theme_field() and field.tpl.php. - * - * @see theme_field() - * @see field.tpl.php - */ -function template_process_field(&$variables, $hook) { - // The default theme implementation is a function, so template_process() does - // not automatically run, so we need to flatten the classes and attributes - // here. For best performance, only call drupal_attributes() when needed, and - // note that template_preprocess_field() does not initialize the - // *_attributes_array variables. - $variables['classes'] = implode(' ', $variables['classes_array']); - $variables['attributes'] = empty($variables['attributes_array']) ? '' : drupal_attributes($variables['attributes_array']); - $variables['title_attributes'] = empty($variables['title_attributes_array']) ? '' : drupal_attributes($variables['title_attributes_array']); - $variables['content_attributes'] = empty($variables['content_attributes_array']) ? '' : drupal_attributes($variables['content_attributes_array']); - foreach ($variables['items'] as $delta => $item) { - $variables['item_attributes'][$delta] = empty($variables['item_attributes_array'][$delta]) ? '' : drupal_attributes($variables['item_attributes_array'][$delta]); - } -} -/** - * @} End of "defgroup field" - */ - -/** - * Returns HTML for a field. - * - * This is the default theme implementation to display the value of a field. - * Theme developers who are comfortable with overriding theme functions may do - * so in order to customize this markup. This function can be overridden with - * varying levels of specificity. For example, for a field named 'body' - * displayed on the 'article' content type, any of the following functions will - * override this default implementation. The first of these functions that - * exists is used: - * - THEMENAME_field__body__article() - * - THEMENAME_field__article() - * - THEMENAME_field__body() - * - THEMENAME_field() - * - * Theme developers who prefer to customize templates instead of overriding - * functions may copy the "field.tpl.php" from the "modules/field/theme" folder - * of the Drupal installation to somewhere within the theme's folder and - * customize it, just like customizing other Drupal templates such as - * page.tpl.php or node.tpl.php. However, it takes longer for the server to - * process templates than to call a function, so for websites with many fields - * displayed on a page, this can result in a noticeable slowdown of the website. - * For these websites, developers are discouraged from placing a field.tpl.php - * file into the theme's folder, but may customize templates for specific - * fields. For example, for a field named 'body' displayed on the 'article' - * content type, any of the following templates will override this default - * implementation. The first of these templates that exists is used: - * - field--body--article.tpl.php - * - field--article.tpl.php - * - field--body.tpl.php - * - field.tpl.php - * So, if the body field on the article content type needs customization, a - * field--body--article.tpl.php file can be added within the theme's folder. - * Because it's a template, it will result in slightly more time needed to - * display that field, but it will not impact other fields, and therefore, - * is unlikely to cause a noticeable change in website performance. A very rough - * guideline is that if a page is being displayed with more than 100 fields and - * they are all themed with a template instead of a function, it can add up to - * 5% to the time it takes to display that page. This is a guideline only and - * the exact performance impact depends on the server configuration and the - * details of the website. - * - * @param $variables - * An associative array containing: - * - label_hidden: A boolean indicating to show or hide the field label. - * - title_attributes: A string containing the attributes for the title. - * - label: The label for the field. - * - content_attributes: A string containing the attributes for the content's - * div. - * - items: An array of field items. - * - item_attributes: An array of attributes for each item. - * - classes: A string containing the classes for the wrapping div. - * - attributes: A string containing the attributes for the wrapping div. - * - * @see template_preprocess_field() - * @see template_process_field() - * @see field.tpl.php - * - * @ingroup themeable - */ -function theme_field($variables) { - $output = ''; - - // Render the label, if it's not hidden. - if (!$variables['label_hidden']) { - $output .= '<div class="field-label"' . $variables['title_attributes'] . '>' . $variables['label'] . ': </div>'; - } - - // Render the items. - $output .= '<div class="field-items"' . $variables['content_attributes'] . '>'; - foreach ($variables['items'] as $delta => $item) { - $classes = 'field-item ' . ($delta % 2 ? 'odd' : 'even'); - $output .= '<div class="' . $classes . '"' . $variables['item_attributes'][$delta] . '>' . drupal_render($item) . '</div>'; - } - $output .= '</div>'; - - // Render the top-level DIV. - $output = '<div class="' . $variables['classes'] . '"' . $variables['attributes'] . '>' . $output . '</div>'; - - return $output; -} diff --git a/modules/field/field.multilingual.inc b/modules/field/field.multilingual.inc deleted file mode 100644 index 44970741e98..00000000000 --- a/modules/field/field.multilingual.inc +++ /dev/null @@ -1,312 +0,0 @@ -<?php - -/** - * @file - * Functions implementing Field API multilingual support. - */ - -/** - * @defgroup field_language Field Language API - * @{ - * Handling of multilingual fields. - * - * Fields natively implement multilingual support, and all fields use the - * following structure: - * @code - * $entity->{$field_name}[$langcode][$delta][$column_name] - * @endcode - * Every field can hold a single or multiple value for each language belonging - * to the available languages set: - * - For untranslatable fields this set only contains LANGUAGE_NONE. - * - For translatable fields this set can contain any language code. By default - * it is the list returned by field_content_languages(), which contains all - * installed languages with the addition of LANGUAGE_NONE. This default can be - * altered by modules implementing hook_field_available_languages_alter(). - * - * The available languages for a particular field are returned by - * field_available_languages(). Whether a field is translatable is determined by - * calling field_is_translatable(), which checks the $field['translatable'] - * property returned by field_info_field(), and whether there is at least one - * translation handler available for the field. A translation handler is a - * module registering itself via hook_entity_info() to handle field - * translations. - * - * By default, _field_invoke() and _field_invoke_multiple() are processing a - * field in all available languages, unless they are given a language - * suggestion. Based on that suggestion, _field_language_suggestion() determines - * the languages to act on. - * - * Most field_attach_*() functions act on all available languages, except for - * the following: - * - field_attach_form() only takes a single language code, specifying which - * language the field values will be submitted in. - * - field_attach_view() requires the language the entity will be displayed in. - * Since it is unknown whether a field translation exists for the requested - * language, the translation handler is responsible for performing one of the - * following actions: - * - Ignore missing translations, i.e. do not show any field values for the - * requested language. For example, see locale_field_language_alter(). - * - Provide a value in a different language as fallback. By default, the - * fallback logic is applied separately to each field to ensure that there - * is a value for each field to display. - * The field language fallback logic relies on the global language fallback - * configuration. Therefore, the displayed field values can be in the - * requested language, but may be different if no values for the requested - * language are available. The default language fallback rules inspect all the - * enabled languages ordered by their weight. This behavior can be altered or - * even disabled by modules implementing hook_field_language_alter(), making - * it possible to choose the first approach. The display language for each - * field is returned by field_language(). - */ - -/** - * Implements hook_locale_language_insert(). - */ -function field_locale_language_insert() { - field_info_cache_clear(); -} - -/** - * Implements hook_locale_language_update(). - */ -function field_locale_language_update() { - field_info_cache_clear(); -} - -/** - * Implements hook_locale_language_delete(). - */ -function field_locale_language_delete() { - field_info_cache_clear(); -} - -/** - * Collects the available languages for the given entity type and field. - * - * If the given field has language support enabled, an array of available - * languages will be returned, otherwise only LANGUAGE_NONE will be returned. - * Since the default value for a 'translatable' entity property is FALSE, we - * ensure that only entities that are able to handle translations actually get - * translatable fields. - * - * @param $entity_type - * The type of the entity the field is attached to, e.g. 'node' or 'user'. - * @param $field - * A field structure. - * - * @return - * An array of valid language codes. - */ -function field_available_languages($entity_type, $field) { - static $drupal_static_fast; - if (!isset($drupal_static_fast)) { - $drupal_static_fast['field_languages'] = &drupal_static(__FUNCTION__); - } - $field_languages = &$drupal_static_fast['field_languages']; - $field_name = $field['field_name']; - - if (!isset($field_languages[$entity_type][$field_name])) { - // If the field has language support enabled we retrieve an (alterable) list - // of enabled languages, otherwise we return just LANGUAGE_NONE. - if (field_is_translatable($entity_type, $field)) { - $languages = field_content_languages(); - // Let other modules alter the available languages. - $context = array('entity_type' => $entity_type, 'field' => $field); - drupal_alter('field_available_languages', $languages, $context); - $field_languages[$entity_type][$field_name] = $languages; - } - else { - $field_languages[$entity_type][$field_name] = array(LANGUAGE_NONE); - } - } - - return $field_languages[$entity_type][$field_name]; -} - -/** - * Process the given language suggestion based on the available languages. - * - * If a non-empty language suggestion is provided it must appear among the - * available languages, otherwise it will be ignored. - * - * @param $available_languages - * An array of valid language codes. - * @param $language_suggestion - * A language code or an array of language codes keyed by field name. - * @param $field_name - * The name of the field being processed. - * - * @return - * An array of valid language codes. - */ -function _field_language_suggestion($available_languages, $language_suggestion, $field_name) { - // Handle possible language suggestions. - if (!empty($language_suggestion)) { - // We might have an array of language suggestions keyed by field name. - if (is_array($language_suggestion) && isset($language_suggestion[$field_name])) { - $language_suggestion = $language_suggestion[$field_name]; - } - - // If we have a language suggestion and the suggested language is available, - // we return only it. - if (in_array($language_suggestion, $available_languages)) { - $available_languages = array($language_suggestion); - } - } - - return $available_languages; -} - -/** - * Returns available content languages. - * - * The languages that may be associated to fields include LANGUAGE_NONE. - * - * @return - * An array of language codes. - */ -function field_content_languages() { - return array_keys(language_list() + array(LANGUAGE_NONE => NULL)); -} - -/** - * Checks whether a field has language support. - * - * A field has language support enabled if its 'translatable' property is set to - * TRUE, and its entity type has at least one translation handler registered. - * - * @param $entity_type - * The type of the entity the field is attached to. - * @param $field - * A field data structure. - * - * @return - * TRUE if the field can be translated. - */ -function field_is_translatable($entity_type, $field) { - return $field['translatable'] && field_has_translation_handler($entity_type); -} - -/** - * Checks if a module is registered as a translation handler for a given entity. - * - * If no handler is passed, simply check if there is any translation handler - * enabled for the given entity type. - * - * @param $entity_type - * The type of the entity whose fields are to be translated. - * @param $handler - * (optional) The name of the handler to be checked. Defaults to NULL. - * - * @return - * TRUE, if the given handler is allowed to manage field translations. If no - * handler is passed, TRUE means there is at least one registered translation - * handler. - */ -function field_has_translation_handler($entity_type, $handler = NULL) { - $entity_info = entity_get_info($entity_type); - - if (isset($handler)) { - return !empty($entity_info['translation'][$handler]); - } - elseif (isset($entity_info['translation'])) { - foreach ($entity_info['translation'] as $handler_info) { - // The translation handler must use a non-empty data structure. - if (!empty($handler_info)) { - return TRUE; - } - } - } - - return FALSE; -} - -/** - * Ensures that a given language code is valid. - * - * Checks whether the given language is one of the enabled languages. Otherwise, - * it returns the current, global language; or the site's default language, if - * the additional parameter $default is TRUE. - * - * @param $langcode - * The language code to validate. - * @param $default - * Whether to return the default language code or the current language code in - * case $langcode is invalid. - * @return - * A valid language code. - */ -function field_valid_language($langcode, $default = TRUE) { - $enabled_languages = field_content_languages(); - if (in_array($langcode, $enabled_languages)) { - return $langcode; - } - global $language_content; - return $default ? language_default()->language : $language_content->language; -} - -/** - * Returns the display language for the fields attached to the given entity. - * - * The actual language for each given field is determined based on the requested - * language and the actual data available in the fields themselves. - * If there is no registered translation handler for the given entity type, the - * display language to be used is just LANGUAGE_NONE, as no other language code - * is allowed by field_available_languages(). - * If translation handlers are found, we let modules provide alternative display - * languages for fields not having the requested language available. - * Core language fallback rules are provided by locale_field_language_fallback() - * which is called by locale_field_language_alter(). - * - * @param $entity_type - * The type of $entity. - * @param $entity - * The entity to be displayed. - * @param $field_name - * (optional) The name of the field to be displayed. Defaults to NULL. If - * no value is specified, the display languages for every field attached to - * the given entity will be returned. - * @param $langcode - * (optional) The language code $entity has to be displayed in. Defaults to - * NULL. If no value is given the current language will be used. - * - * @return - * A language code if a field name is specified, an array of language codes - * keyed by field name otherwise. - */ -function field_language($entity_type, $entity, $field_name = NULL, $langcode = NULL) { - $display_languages = &drupal_static(__FUNCTION__, array()); - list($id, , $bundle) = entity_extract_ids($entity_type, $entity); - $langcode = field_valid_language($langcode, FALSE); - - if (!isset($display_languages[$entity_type][$id][$langcode])) { - $display_language = array(); - - // By default display language is set to LANGUAGE_NONE if the field - // translation is not available. It is up to translation handlers to - // implement language fallback rules. - foreach (field_info_instances($entity_type, $bundle) as $instance) { - $display_language[$instance['field_name']] = isset($entity->{$instance['field_name']}[$langcode]) ? $langcode : LANGUAGE_NONE; - } - - if (field_has_translation_handler($entity_type)) { - $context = array( - 'entity_type' => $entity_type, - 'entity' => $entity, - 'language' => $langcode, - ); - drupal_alter('field_language', $display_language, $context); - } - - $display_languages[$entity_type][$id][$langcode] = $display_language; - } - - $display_language = $display_languages[$entity_type][$id][$langcode]; - - // Single-field mode. - if (isset($field_name)) { - return isset($display_language[$field_name]) ? $display_language[$field_name] : FALSE; - } - - return $display_language; -} diff --git a/modules/field/modules/field_sql_storage/field_sql_storage.info b/modules/field/modules/field_sql_storage/field_sql_storage.info deleted file mode 100644 index ee1ae571267..00000000000 --- a/modules/field/modules/field_sql_storage/field_sql_storage.info +++ /dev/null @@ -1,8 +0,0 @@ -name = Field SQL storage -description = Stores field data in an SQL database. -package = Core -version = VERSION -core = 8.x -dependencies[] = field -files[] = field_sql_storage.test -required = TRUE diff --git a/modules/field/modules/field_sql_storage/field_sql_storage.install b/modules/field/modules/field_sql_storage/field_sql_storage.install deleted file mode 100644 index 4a3a00e577f..00000000000 --- a/modules/field/modules/field_sql_storage/field_sql_storage.install +++ /dev/null @@ -1,79 +0,0 @@ -<?php - -/** - * @file - * Install, update and uninstall functions for the field_sql_storage module. - */ - -/** - * Implements hook_schema(). - */ -function field_sql_storage_schema() { - $schema = array(); - - // Dynamic (data) tables. - if (db_table_exists('field_config')) { - $fields = field_read_fields(array(), array('include_deleted' => TRUE, 'include_inactive' => TRUE)); - drupal_load('module', 'field_sql_storage'); - foreach ($fields as $field) { - if ($field['storage']['type'] == 'field_sql_storage') { - $schema += _field_sql_storage_schema($field); - } - } - } - return $schema; -} - -/** - * Utility function: write field data directly to SQL storage. - * - * @ingroup update-api-7.x-to-8.x - */ -function _update_7000_field_sql_storage_write($entity_type, $bundle, $entity_id, $revision_id, $field_name, $data) { - $table_name = "field_data_{$field_name}"; - $revision_name = "field_revision_{$field_name}"; - - db_delete($table_name) - ->condition('entity_type', $entity_type) - ->condition('entity_id', $entity_id) - ->execute(); - db_delete($revision_name) - ->condition('entity_type', $entity_type) - ->condition('entity_id', $entity_id) - ->condition('revision_id', $revision_id) - ->execute(); - - $columns = array(); - foreach ($data as $langcode => $items) { - foreach ($items as $delta => $item) { - $record = array( - 'entity_type' => $entity_type, - 'entity_id' => $entity_id, - 'revision_id' => $revision_id, - 'bundle' => $bundle, - 'delta' => $delta, - 'language' => $langcode, - ); - foreach ($item as $column => $value) { - $record[_field_sql_storage_columnname($field_name, $column)] = $value; - } - - $records[] = $record; - // Record the columns used. - $columns += $record; - } - } - - if ($columns) { - $query = db_insert($table_name)->fields(array_keys($columns)); - $revision_query = db_insert($revision_name)->fields(array_keys($columns)); - foreach ($records as $record) { - $query->values($record); - if ($revision_id) { - $revision_query->values($record); - } - } - $query->execute(); - $revision_query->execute(); - } -} diff --git a/modules/field/modules/field_sql_storage/field_sql_storage.module b/modules/field/modules/field_sql_storage/field_sql_storage.module deleted file mode 100644 index 92d244a9fba..00000000000 --- a/modules/field/modules/field_sql_storage/field_sql_storage.module +++ /dev/null @@ -1,743 +0,0 @@ -<?php - -/** - * @file - * Default implementation of the field storage API. - */ - -/** - * Implements hook_help(). - */ -function field_sql_storage_help($path, $arg) { - switch ($path) { - case 'admin/help#field_sql_storage': - $output = ''; - $output .= '<h3>' . t('About') . '</h3>'; - $output .= '<p>' . t('The Field SQL storage module stores field data in the database. It is the default field storage module; other field storage mechanisms may be available as contributed modules. See the <a href="@field-help">Field module help page</a> for more information about fields.', array('@field-help' => url('admin/help/field'))) . '</p>'; - return $output; - } -} - -/** - * Implements hook_field_storage_info(). - */ -function field_sql_storage_field_storage_info() { - return array( - 'field_sql_storage' => array( - 'label' => t('Default SQL storage'), - 'description' => t('Stores fields in the local SQL database, using per-field tables.'), - ), - ); -} - -/** - * Generate a table name for a field data table. - * - * @param $field - * The field structure. - * @return - * A string containing the generated name for the database table - */ -function _field_sql_storage_tablename($field) { - if ($field['deleted']) { - return "field_deleted_data_{$field['id']}"; - } - else { - return "field_data_{$field['field_name']}"; - } -} - -/** - * Generate a table name for a field revision archive table. - * - * @param $name - * The field structure. - * @return - * A string containing the generated name for the database table - */ -function _field_sql_storage_revision_tablename($field) { - if ($field['deleted']) { - return "field_deleted_revision_{$field['id']}"; - } - else { - return "field_revision_{$field['field_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; -} - -/** - * Generate an index name for a field data table. - * - * @param $name - * The name of the field - * @param $column - * The name of the index - * @return - * A string containing a generated index name for a field data - * table that is unique among all other fields. - */ -function _field_sql_storage_indexname($name, $index) { - return $name . '_' . $index; -} - -/** - * 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 the $field's 'columns' and 'indexes' keys - * 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) { - $deleted = $field['deleted'] ? 'deleted ' : ''; - $current = array( - 'description' => "Data storage for {$deleted}field {$field['id']} ({$field['field_name']})", - 'fields' => array( - 'entity_type' => array( - 'type' => 'varchar', - 'length' => 128, - 'not null' => TRUE, - 'default' => '', - 'description' => 'The entity type this data is attached to', - ), - 'bundle' => array( - 'type' => 'varchar', - 'length' => 128, - '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', - ), - // @todo Consider storing language as integer. - 'language' => array( - 'type' => 'varchar', - 'length' => 32, - 'not null' => TRUE, - 'default' => '', - 'description' => 'The language for this data item.', - ), - '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('entity_type', 'entity_id', 'deleted', 'delta', 'language'), - 'indexes' => array( - 'entity_type' => array('entity_type'), - 'bundle' => array('bundle'), - 'deleted' => array('deleted'), - 'entity_id' => array('entity_id'), - 'revision_id' => array('revision_id'), - 'language' => array('language'), - ), - ); - - $field += array('columns' => array(), 'indexes' => array(), 'foreign keys' => array()); - // Add field columns. - foreach ($field['columns'] as $column_name => $attributes) { - $real_name = _field_sql_storage_columnname($field['field_name'], $column_name); - $current['fields'][$real_name] = $attributes; - } - - // Add indexes. - foreach ($field['indexes'] as $index_name => $columns) { - $real_name = _field_sql_storage_indexname($field['field_name'], $index_name); - foreach ($columns as $column_name) { - $current['indexes'][$real_name][] = _field_sql_storage_columnname($field['field_name'], $column_name); - } - } - - // Add foreign keys. - foreach ($field['foreign keys'] as $specifier => $specification) { - $real_name = _field_sql_storage_indexname($field['field_name'], $specifier); - $current['foreign keys'][$real_name]['table'] = $specification['table']; - foreach ($specification['columns'] as $column => $referenced) { - $sql_storage_column = _field_sql_storage_columnname($field['field_name'], $column_name); - $current['foreign keys'][$real_name]['columns'][$sql_storage_column] = $referenced; - } - } - - // Construct the revision table. - $revision = $current; - $revision['description'] = "Revision archive storage for {$deleted}field {$field['id']} ({$field['field_name']})"; - $revision['primary key'] = array('entity_type', 'entity_id', 'revision_id', 'deleted', 'delta', 'language'); - $revision['fields']['revision_id']['not null'] = TRUE; - $revision['fields']['revision_id']['description'] = 'The entity revision id this data is attached to'; - - return array( - _field_sql_storage_tablename($field) => $current, - _field_sql_storage_revision_tablename($field) => $revision, - ); -} - -/** - * Implements hook_field_storage_create_field(). - */ -function field_sql_storage_field_storage_create_field($field) { - $schema = _field_sql_storage_schema($field); - foreach ($schema as $name => $table) { - db_create_table($name, $table); - } - drupal_get_schema(NULL, TRUE); -} - -/** - * Implements hook_field_update_forbid(). - * - * Forbid any field update that changes column definitions if there is - * any data. - */ -function field_sql_storage_field_update_forbid($field, $prior_field, $has_data) { - if ($has_data && $field['columns'] != $prior_field['columns']) { - throw new FieldUpdateForbiddenException("field_sql_storage cannot change the schema for an existing field with data."); - } -} - -/** - * Implements hook_field_storage_update_field(). - */ -function field_sql_storage_field_storage_update_field($field, $prior_field, $has_data) { - if (! $has_data) { - // There is no data. Re-create the tables completely. - - if (Database::getConnection()->supportsTransactionalDDL()) { - // If the database supports transactional DDL, we can go ahead and rely - // on it. If not, we will have to rollback manually if something fails. - $transaction = db_transaction(); - } - - try { - $prior_schema = _field_sql_storage_schema($prior_field); - foreach ($prior_schema as $name => $table) { - db_drop_table($name, $table); - } - $schema = _field_sql_storage_schema($field); - foreach ($schema as $name => $table) { - db_create_table($name, $table); - } - } - catch (Exception $e) { - if (Database::getConnection()->supportsTransactionalDDL()) { - $transaction->rollback(); - } - else { - // Recreate tables. - $prior_schema = _field_sql_storage_schema($prior_field); - foreach ($prior_schema as $name => $table) { - if (!db_table_exists($name)) { - db_create_table($name, $table); - } - } - } - throw $e; - } - } - else { - // There is data, so there are no column changes. Drop all the - // prior indexes and create all the new ones, except for all the - // priors that exist unchanged. - $table = _field_sql_storage_tablename($prior_field); - $revision_table = _field_sql_storage_revision_tablename($prior_field); - foreach ($prior_field['indexes'] as $name => $columns) { - if (!isset($field['indexes'][$name]) || $columns != $field['indexes'][$name]) { - $real_name = _field_sql_storage_indexname($field['field_name'], $name); - db_drop_index($table, $real_name); - db_drop_index($revision_table, $real_name); - } - } - $table = _field_sql_storage_tablename($field); - $revision_table = _field_sql_storage_revision_tablename($field); - foreach ($field['indexes'] as $name => $columns) { - if (!isset($prior_field['indexes'][$name]) || $columns != $prior_field['indexes'][$name]) { - $real_name = _field_sql_storage_indexname($field['field_name'], $name); - $real_columns = array(); - foreach ($columns as $column_name) { - $real_columns[] = _field_sql_storage_columnname($field['field_name'], $column_name); - } - db_add_index($table, $real_name, $real_columns); - db_add_index($revision_table, $real_name, $real_columns); - } - } - } - drupal_get_schema(NULL, TRUE); -} - -/** - * Implements hook_field_storage_delete_field(). - */ -function field_sql_storage_field_storage_delete_field($field) { - // Mark all data associated with the field for deletion. - $field['deleted'] = 0; - $table = _field_sql_storage_tablename($field); - $revision_table = _field_sql_storage_revision_tablename($field); - db_update($table) - ->fields(array('deleted' => 1)) - ->execute(); - - // Move the table to a unique name while the table contents are being deleted. - $field['deleted'] = 1; - $new_table = _field_sql_storage_tablename($field); - $revision_new_table = _field_sql_storage_revision_tablename($field); - db_rename_table($table, $new_table); - db_rename_table($revision_table, $revision_new_table); - drupal_get_schema(NULL, TRUE); -} - -/** - * Implements hook_field_storage_load(). - */ -function field_sql_storage_field_storage_load($entity_type, $entities, $age, $fields, $options) { - $field_info = field_info_field_by_ids(); - $load_current = $age == FIELD_LOAD_CURRENT; - - foreach ($fields as $field_id => $ids) { - $field = $field_info[$field_id]; - $field_name = $field['field_name']; - $table = $load_current ? _field_sql_storage_tablename($field) : _field_sql_storage_revision_tablename($field); - - $query = db_select($table, 't') - ->fields('t') - ->condition('entity_type', $entity_type) - ->condition($load_current ? 'entity_id' : 'revision_id', $ids, 'IN') - ->condition('language', field_available_languages($entity_type, $field), 'IN') - ->orderBy('delta'); - - if (empty($options['deleted'])) { - $query->condition('deleted', 0); - } - - $results = $query->execute(); - - $delta_count = array(); - foreach ($results as $row) { - if (!isset($delta_count[$row->entity_id][$row->language])) { - $delta_count[$row->entity_id][$row->language] = 0; - } - - if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || $delta_count[$row->entity_id][$row->language] < $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) { - $column_name = _field_sql_storage_columnname($field_name, $column); - $item[$column] = $row->$column_name; - } - - // Add the item to the field values for the entity. - $entities[$row->entity_id]->{$field_name}[$row->language][] = $item; - $delta_count[$row->entity_id][$row->language]++; - } - } - } -} - -/** - * Implements hook_field_storage_write(). - */ -function field_sql_storage_field_storage_write($entity_type, $entity, $op, $fields) { - list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); - if (!isset($vid)) { - $vid = $id; - } - - foreach ($fields as $field_id) { - $field = field_info_field_by_id($field_id); - $field_name = $field['field_name']; - $table_name = _field_sql_storage_tablename($field); - $revision_name = _field_sql_storage_revision_tablename($field); - - $all_languages = field_available_languages($entity_type, $field); - $field_languages = array_intersect($all_languages, array_keys((array) $entity->$field_name)); - - // Delete and insert, rather than update, in case a value was added. - if ($op == FIELD_STORAGE_UPDATE) { - // Delete languages present in the incoming $entity->$field_name. - // Delete all languages if $entity->$field_name is empty. - $languages = !empty($entity->$field_name) ? $field_languages : $all_languages; - if ($languages) { - db_delete($table_name) - ->condition('entity_type', $entity_type) - ->condition('entity_id', $id) - ->condition('language', $languages, 'IN') - ->execute(); - db_delete($revision_name) - ->condition('entity_type', $entity_type) - ->condition('entity_id', $id) - ->condition('revision_id', $vid) - ->condition('language', $languages, 'IN') - ->execute(); - } - } - - // Prepare the multi-insert query. - $do_insert = FALSE; - $columns = array('entity_type', 'entity_id', 'revision_id', 'bundle', 'delta', 'language'); - foreach ($field['columns'] as $column => $attributes) { - $columns[] = _field_sql_storage_columnname($field_name, $column); - } - $query = db_insert($table_name)->fields($columns); - $revision_query = db_insert($revision_name)->fields($columns); - - foreach ($field_languages as $langcode) { - $items = (array) $entity->{$field_name}[$langcode]; - $delta_count = 0; - foreach ($items as $delta => $item) { - // We now know we have someting to insert. - $do_insert = TRUE; - $record = array( - 'entity_type' => $entity_type, - 'entity_id' => $id, - 'revision_id' => $vid, - 'bundle' => $bundle, - 'delta' => $delta, - 'language' => $langcode, - ); - 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 query if we have values to insert. - if ($do_insert) { - $query->execute(); - $revision_query->execute(); - } - } -} - -/** - * Implements hook_field_storage_delete(). - * - * This function deletes data for all fields for an entity from the database. - */ -function field_sql_storage_field_storage_delete($entity_type, $entity, $fields) { - list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); - - foreach (field_info_instances($entity_type, $bundle) as $instance) { - if (isset($fields[$instance['field_id']])) { - $field = field_info_field_by_id($instance['field_id']); - field_sql_storage_field_storage_purge($entity_type, $entity, $field, $instance); - } - } -} - -/** - * Implements hook_field_storage_purge(). - * - * This function deletes data from the database for a single field on - * an entity. - */ -function field_sql_storage_field_storage_purge($entity_type, $entity, $field, $instance) { - list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); - - $table_name = _field_sql_storage_tablename($field); - $revision_name = _field_sql_storage_revision_tablename($field); - db_delete($table_name) - ->condition('entity_type', $entity_type) - ->condition('entity_id', $id) - ->execute(); - db_delete($revision_name) - ->condition('entity_type', $entity_type) - ->condition('entity_id', $id) - ->execute(); -} - -/** - * Implements hook_field_storage_query(). - */ -function field_sql_storage_field_storage_query(EntityFieldQuery $query) { - if ($query->age == FIELD_LOAD_CURRENT) { - $tablename_function = '_field_sql_storage_tablename'; - $id_key = 'entity_id'; - } - else { - $tablename_function = '_field_sql_storage_revision_tablename'; - $id_key = 'revision_id'; - } - $table_aliases = array(); - // Add tables for the fields used. - foreach ($query->fields as $key => $field) { - $tablename = $tablename_function($field); - // Every field needs a new table. - $table_alias = $tablename . $key; - $table_aliases[$key] = $table_alias; - if ($key) { - $select_query->join($tablename, $table_alias, "$table_alias.entity_type = $field_base_table.entity_type AND $table_alias.$id_key = $field_base_table.$id_key"); - } - else { - $select_query = db_select($tablename, $table_alias); - $select_query->addTag('entity_field_access'); - $select_query->addMetaData('base_table', $tablename); - $select_query->fields($table_alias, array('entity_type', 'entity_id', 'revision_id', 'bundle')); - $field_base_table = $table_alias; - } - if ($field['cardinality'] != 1 || $field['translatable']) { - $select_query->distinct(); - } - } - - // Add field conditions. We need a fresh grouping cache. - drupal_static_reset('_field_sql_storage_query_field_conditions'); - _field_sql_storage_query_field_conditions($query, $select_query, $query->fieldConditions, $table_aliases, '_field_sql_storage_columnname'); - - // Add field meta conditions. - _field_sql_storage_query_field_conditions($query, $select_query, $query->fieldMetaConditions, $table_aliases, function ($field_name, $column) { return $column; }); - - if (isset($query->deleted)) { - $select_query->condition("$field_base_table.deleted", (int) $query->deleted); - } - - // Is there a need to sort the query by property? - $has_property_order = FALSE; - foreach ($query->order as $order) { - if ($order['type'] == 'property') { - $has_property_order = TRUE; - } - } - - if ($query->propertyConditions || $has_property_order) { - if (empty($query->entityConditions['entity_type']['value'])) { - throw new EntityFieldQueryException('Property conditions and orders must have an entity type defined.'); - } - $entity_type = $query->entityConditions['entity_type']['value']; - $entity_base_table = _field_sql_storage_query_join_entity($select_query, $entity_type, $field_base_table); - $query->entityConditions['entity_type']['operator'] = '='; - foreach ($query->propertyConditions as $property_condition) { - $query->addCondition($select_query, "$entity_base_table." . $property_condition['column'], $property_condition); - } - } - foreach ($query->entityConditions as $key => $condition) { - $query->addCondition($select_query, "$field_base_table.$key", $condition); - } - - // Order the query. - foreach ($query->order as $order) { - if ($order['type'] == 'entity') { - $key = $order['specifier']; - $select_query->orderBy("$field_base_table.$key", $order['direction']); - } - elseif ($order['type'] == 'field') { - $specifier = $order['specifier']; - $field = $specifier['field']; - $table_alias = $table_aliases[$specifier['index']]; - $sql_field = "$table_alias." . _field_sql_storage_columnname($field['field_name'], $specifier['column']); - $select_query->orderBy($sql_field, $order['direction']); - } - elseif ($order['type'] == 'property') { - $select_query->orderBy("$entity_base_table." . $order['specifier'], $order['direction']); - } - } - - return $query->finishQuery($select_query, $id_key); -} - -/** - * Adds the base entity table to a field query object. - * - * @param SelectQuery $select_query - * A SelectQuery containing at least one table as specified by - * _field_sql_storage_tablename(). - * @param $entity_type - * The entity type for which the base table should be joined. - * @param $field_base_table - * Name of a table in $select_query. As only INNER JOINs are used, it does - * not matter which. - * - * @return - * The name of the entity base table joined in. - */ -function _field_sql_storage_query_join_entity(SelectQuery $select_query, $entity_type, $field_base_table) { - $entity_info = entity_get_info($entity_type); - $entity_base_table = $entity_info['base table']; - $entity_field = $entity_info['entity keys']['id']; - $select_query->join($entity_base_table, $entity_base_table, "$entity_base_table.$entity_field = $field_base_table.entity_id"); - return $entity_base_table; -} - -/** - * Adds field (meta) conditions to the given query objects respecting groupings. - * - * @param EntityFieldQuery $query - * The field query object to be processed. - * @param SelectQuery $select_query - * The SelectQuery that should get grouping conditions. - * @param condtions - * The conditions to be added. - * @param $table_aliases - * An associative array of table aliases keyed by field index. - * @param $column_callback - * A callback that should return the column name to be used for the field - * conditions. Accepts a field name and a field column name as parameters. - */ -function _field_sql_storage_query_field_conditions(EntityFieldQuery $query, SelectQuery $select_query, $conditions, $table_aliases, $column_callback) { - $groups = &drupal_static(__FUNCTION__, array()); - foreach ($conditions as $key => $condition) { - $table_alias = $table_aliases[$key]; - $field = $condition['field']; - // Add the specified condition. - $sql_field = "$table_alias." . $column_callback($field['field_name'], $condition['column']); - $query->addCondition($select_query, $sql_field, $condition); - // Add delta / language group conditions. - foreach (array('delta', 'language') as $column) { - if (isset($condition[$column . '_group'])) { - $group_name = $condition[$column . '_group']; - if (!isset($groups[$column][$group_name])) { - $groups[$column][$group_name] = $table_alias; - } - else { - $select_query->where("$table_alias.$column = " . $groups[$column][$group_name] . ".$column"); - } - } - } - } -} - -/** - * Implements hook_field_storage_delete_revision(). - * - * This function actually deletes the data from the database. - */ -function field_sql_storage_field_storage_delete_revision($entity_type, $entity, $fields) { - list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); - - if (isset($vid)) { - foreach ($fields as $field_id) { - $field = field_info_field_by_id($field_id); - $revision_name = _field_sql_storage_revision_tablename($field); - db_delete($revision_name) - ->condition('entity_type', $entity_type) - ->condition('entity_id', $id) - ->condition('revision_id', $vid) - ->execute(); - } - } -} - -/** - * Implements hook_field_storage_delete_instance(). - * - * This function simply marks for deletion all data associated with the field. - */ -function field_sql_storage_field_storage_delete_instance($instance) { - $field = field_info_field($instance['field_name']); - $table_name = _field_sql_storage_tablename($field); - $revision_name = _field_sql_storage_revision_tablename($field); - db_update($table_name) - ->fields(array('deleted' => 1)) - ->condition('entity_type', $instance['entity_type']) - ->condition('bundle', $instance['bundle']) - ->execute(); - db_update($revision_name) - ->fields(array('deleted' => 1)) - ->condition('entity_type', $instance['entity_type']) - ->condition('bundle', $instance['bundle']) - ->execute(); -} - -/** - * Implements hook_field_attach_rename_bundle(). - */ -function field_sql_storage_field_attach_rename_bundle($entity_type, $bundle_old, $bundle_new) { - // We need to account for deleted or inactive fields and instances. - $instances = field_read_instances(array('entity_type' => $entity_type, 'bundle' => $bundle_new), array('include_deleted' => TRUE, 'include_inactive' => TRUE)); - foreach ($instances as $instance) { - $field = field_info_field_by_id($instance['field_id']); - if ($field['storage']['type'] == 'field_sql_storage') { - $table_name = _field_sql_storage_tablename($field); - $revision_name = _field_sql_storage_revision_tablename($field); - db_update($table_name) - ->fields(array('bundle' => $bundle_new)) - ->condition('entity_type', $entity_type) - ->condition('bundle', $bundle_old) - ->execute(); - db_update($revision_name) - ->fields(array('bundle' => $bundle_new)) - ->condition('entity_type', $entity_type) - ->condition('bundle', $bundle_old) - ->execute(); - } - } -} - -/** - * Implements hook_field_storage_purge_field(). - * - * All field data items and instances have already been purged, so all - * that is left is to delete the table. - */ -function field_sql_storage_field_storage_purge_field($field) { - $table_name = _field_sql_storage_tablename($field); - $revision_name = _field_sql_storage_revision_tablename($field); - db_drop_table($table_name); - db_drop_table($revision_name); -} - -/** - * Implements hook_field_storage_details(). - */ -function field_sql_storage_field_storage_details($field) { - $details = array(); - if (!empty($field['columns'])) { - // Add field columns. - foreach ($field['columns'] as $column_name => $attributes) { - $real_name = _field_sql_storage_columnname($field['field_name'], $column_name); - $columns[$column_name] = $real_name; - } - return array( - 'sql' => array( - FIELD_LOAD_CURRENT => array( - _field_sql_storage_tablename($field) => $columns, - ), - FIELD_LOAD_REVISION => array( - _field_sql_storage_revision_tablename($field) => $columns, - ), - ), - ); - } -} diff --git a/modules/field/modules/field_sql_storage/field_sql_storage.test b/modules/field/modules/field_sql_storage/field_sql_storage.test deleted file mode 100644 index 773de3d072d..00000000000 --- a/modules/field/modules/field_sql_storage/field_sql_storage.test +++ /dev/null @@ -1,427 +0,0 @@ -<?php - -/** - * @file - * Tests for field_sql_storage.module. - * - * Field_sql_storage.module implements the default back-end storage plugin - * for the Field Strage API. - */ - -/** - * Tests field storage. - */ -class FieldSqlStorageTestCase extends DrupalWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Field SQL storage tests', - 'description' => "Test field SQL storage module.", - 'group' => 'Field API' - ); - } - - function setUp() { - parent::setUp('field_sql_storage', 'field', 'field_test', 'text'); - $this->field_name = strtolower($this->randomName()); - $this->field = array('field_name' => $this->field_name, 'type' => 'test_field', 'cardinality' => 4); - $this->field = field_create_field($this->field); - $this->instance = array( - 'field_name' => $this->field_name, - 'entity_type' => 'test_entity', - 'bundle' => 'test_bundle' - ); - $this->instance = field_create_instance($this->instance); - $this->table = _field_sql_storage_tablename($this->field); - $this->revision_table = _field_sql_storage_revision_tablename($this->field); - - } - - /** - * Uses the mysql tables and records to verify - * field_load_revision works correctly. - */ - function testFieldAttachLoad() { - $entity_type = 'test_entity'; - $eid = 0; - $langcode = LANGUAGE_NONE; - - $columns = array('entity_type', 'entity_id', 'revision_id', 'delta', 'language', $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(1, 127); - $values[$evid][] = $value; - $query->values(array($entity_type, $eid, $evid, $delta, $langcode, $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($entity_type, $eid, 0, $delta, $langcode, $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}[$langcode][$delta]['value'], $value, "Value $delta is loaded correctly for current revision"); - } - else { - $this->assertFalse(array_key_exists($delta, $entity->{$this->field_name}[$langcode]), "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}[$langcode][$delta]['value'], $value, "Value $delta for revision $evid is loaded correctly"); - } - else { - $this->assertFalse(array_key_exists($delta, $entity->{$this->field_name}[$langcode]), "No extraneous value gets loaded for revision $evid."); - } - } - } - - // Add a translation in an unavailable language and verify it is not loaded. - $eid = $evid = 1; - $unavailable_language = 'xx'; - $entity = field_test_create_stub_entity($eid, $evid, $this->instance['bundle']); - $values = array($entity_type, $eid, $evid, 0, $unavailable_language, mt_rand(1, 127)); - db_insert($this->table)->fields($columns)->values($values)->execute(); - db_insert($this->revision_table)->fields($columns)->values($values)->execute(); - field_attach_load($entity_type, array($eid => $entity)); - $this->assertFalse(array_key_exists($unavailable_language, $entity->{$this->field_name}), 'Field translation in an unavailable language ignored'); - } - - /** - * Reads mysql to verify correct data is - * written when using insert and update. - */ - function testFieldAttachInsertAndUpdate() { - $entity_type = 'test_entity'; - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - $langcode = LANGUAGE_NONE; - - // 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(1, 127); - } - $entity->{$this->field_name}[$langcode] = $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(1, 127); - } - $entity->{$this->field_name}[$langcode] = $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 - // $entity->{$field_name} is absent. - 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 $entity->$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.")); - } - - /** - * Tests insert and update with missing or NULL fields. - */ - function testFieldAttachSaveMissingData() { - $entity_type = 'test_entity'; - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - $langcode = LANGUAGE_NONE; - - // Insert: Field is missing - field_attach_insert($entity_type, $entity); - $count = db_select($this->table) - ->countQuery() - ->execute() - ->fetchField(); - $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_select($this->table) - ->countQuery() - ->execute() - ->fetchField(); - $this->assertEqual($count, 0, 'NULL field results in no inserts'); - - // Add some real data - $entity->{$this->field_name}[$langcode] = array(0 => array('value' => 1)); - field_attach_insert($entity_type, $entity); - $count = db_select($this->table) - ->countQuery() - ->execute() - ->fetchField(); - $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_select($this->table) - ->countQuery() - ->execute() - ->fetchField(); - $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_select($this->table) - ->countQuery() - ->execute() - ->fetchField(); - $this->assertEqual($count, 0, 'NULL field leaves no data in table'); - - // Add a translation in an unavailable language. - $unavailable_language = 'xx'; - db_insert($this->table) - ->fields(array('entity_type', 'bundle', 'deleted', 'entity_id', 'revision_id', 'delta', 'language')) - ->values(array($entity_type, $this->instance['bundle'], 0, 0, 0, 0, $unavailable_language)) - ->execute(); - $count = db_select($this->table) - ->countQuery() - ->execute() - ->fetchField(); - $this->assertEqual($count, 1, 'Field translation in an unavailable language saved.'); - - // Again add some real data. - $entity->{$this->field_name}[$langcode] = array(0 => array('value' => 1)); - field_attach_insert($entity_type, $entity); - $count = db_select($this->table) - ->countQuery() - ->execute() - ->fetchField(); - $this->assertEqual($count, 2, 'Field data saved.'); - - // Update: Field translation is missing but field is not empty. Translation - // data should survive. - $entity->{$this->field_name}[$unavailable_language] = array(mt_rand(1, 127)); - unset($entity->{$this->field_name}[$langcode]); - field_attach_update($entity_type, $entity); - $count = db_select($this->table) - ->countQuery() - ->execute() - ->fetchField(); - $this->assertEqual($count, 2, 'Missing field translation leaves data in table.'); - - // Update: Field translation is NULL but field is not empty. Translation - // data should be wiped. - $entity->{$this->field_name}[$langcode] = NULL; - field_attach_update($entity_type, $entity); - $count = db_select($this->table) - ->countQuery() - ->execute() - ->fetchField(); - $this->assertEqual($count, 1, 'NULL field translation is wiped.'); - } - - /** - * Test trying to update a field with data. - */ - function testUpdateFieldSchemaWithData() { - // Create a decimal 5.2 field and add some data. - $field = array('field_name' => 'decimal52', 'type' => 'number_decimal', 'settings' => array('precision' => 5, 'scale' => 2)); - $field = field_create_field($field); - $instance = array('field_name' => 'decimal52', 'entity_type' => 'test_entity', 'bundle' => 'test_bundle'); - $instance = field_create_instance($instance); - $entity = field_test_create_stub_entity(0, 0, $instance['bundle']); - $entity->decimal52[LANGUAGE_NONE][0]['value'] = '1.235'; - field_attach_insert('test_entity', $entity); - - // Attempt to update the field in a way that would work without data. - $field['settings']['scale'] = 3; - try { - field_update_field($field); - $this->fail(t('Cannot update field schema with data.')); - } - catch (FieldException $e) { - $this->pass(t('Cannot update field schema with data.')); - } - } - - /** - * Test that failure to create fields is handled gracefully. - */ - function testFieldUpdateFailure() { - // Create a text field. - $field = array('field_name' => 'test_text', 'type' => 'text', 'settings' => array('max_length' => 255)); - $field = field_create_field($field); - - // Attempt to update the field in a way that would break the storage. - $prior_field = $field; - $field['settings']['max_length'] = -1; - try { - field_update_field($field); - $this->fail(t('Update succeeded.')); - } - catch (Exception $e) { - $this->pass(t('Update properly failed.')); - } - - // Ensure that the field tables are still there. - foreach (_field_sql_storage_schema($prior_field) as $table_name => $table_info) { - $this->assertTrue(db_table_exists($table_name), t('Table %table exists.', array('%table' => $table_name))); - } - } - - /** - * Test adding and removing indexes while data is present. - */ - function testFieldUpdateIndexesWithData() { - - // Create a decimal field. - $field_name = 'testfield'; - $field = array('field_name' => $field_name, 'type' => 'text'); - $field = field_create_field($field); - $instance = array('field_name' => $field_name, 'entity_type' => 'test_entity', 'bundle' => 'test_bundle'); - $instance = field_create_instance($instance); - $tables = array(_field_sql_storage_tablename($field), _field_sql_storage_revision_tablename($field)); - - // Verify the indexes we will create do not exist yet. - foreach ($tables as $table) { - $this->assertFalse(Database::getConnection()->schema()->indexExists($table, 'value'), t("No index named value exists in $table")); - $this->assertFalse(Database::getConnection()->schema()->indexExists($table, 'value_format'), t("No index named value_format exists in $table")); - } - - // Add data so the table cannot be dropped. - $entity = field_test_create_stub_entity(0, 0, $instance['bundle']); - $entity->{$field_name}[LANGUAGE_NONE][0]['value'] = 'field data'; - field_attach_insert('test_entity', $entity); - - // Add an index - $field = array('field_name' => $field_name, 'indexes' => array('value' => array('value'))); - field_update_field($field); - foreach ($tables as $table) { - $this->assertTrue(Database::getConnection()->schema()->indexExists($table, "{$field_name}_value"), t("Index on value created in $table")); - } - - // Add a different index, removing the existing custom one. - $field = array('field_name' => $field_name, 'indexes' => array('value_format' => array('value', 'format'))); - field_update_field($field); - foreach ($tables as $table) { - $this->assertTrue(Database::getConnection()->schema()->indexExists($table, "{$field_name}_value_format"), t("Index on value_format created in $table")); - $this->assertFalse(Database::getConnection()->schema()->indexExists($table, "{$field_name}_value"), t("Index on value removed in $table")); - } - - // Verify that the tables were not dropped. - $entity = field_test_create_stub_entity(0, 0, $instance['bundle']); - field_attach_load('test_entity', array(0 => $entity)); - $this->assertEqual($entity->{$field_name}[LANGUAGE_NONE][0]['value'], 'field data', t("Index changes performed without dropping the tables")); - } - - /** - * Test the storage details. - */ - function testFieldStorageDetails() { - $current = _field_sql_storage_tablename($this->field); - $revision = _field_sql_storage_revision_tablename($this->field); - - // Retrieve the field and instance with field_info so the storage details are attached. - $field = field_info_field($this->field['field_name']); - $instance = field_info_instance($this->instance['entity_type'], $this->instance['field_name'], $this->instance['bundle']); - - // The storage details are indexed by a storage engine type. - $this->assertTrue(array_key_exists('sql', $field['storage']['details']), t('The storage type is SQL.')); - - // The SQL details are indexed by table name. - $details = $field['storage']['details']['sql']; - $this->assertTrue(array_key_exists($current, $details[FIELD_LOAD_CURRENT]), t('Table name is available in the instance array.')); - $this->assertTrue(array_key_exists($revision, $details[FIELD_LOAD_REVISION]), t('Revision table name is available in the instance array.')); - - // Test current and revision storage details together because the columns - // are the same. - foreach ((array) $this->field['columns'] as $column_name => $attributes) { - $storage_column_name = _field_sql_storage_columnname($this->field['field_name'], $column_name); - $this->assertEqual($details[FIELD_LOAD_CURRENT][$current][$column_name], $storage_column_name, t('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => $current))); - $this->assertEqual($details[FIELD_LOAD_REVISION][$revision][$column_name], $storage_column_name, t('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => $revision))); - } - } - - /** - * Test foreign key support. - */ - function testFieldSqlStorageForeignKeys() { - // Create a decimal field. - $field_name = 'testfield'; - $field = array('field_name' => $field_name, 'type' => 'text'); - $field = field_create_field($field); - // Retrieve the field and instance with field_info and verify the foreign - // keys are in place. - $field = field_info_field($field_name); - $this->assertEqual($field['foreign keys']['format']['table'], 'filter_format', t('Foreign key table name preserved through CRUD')); - $this->assertEqual($field['foreign keys']['format']['columns']['format'], 'format', t('Foreign key column name preserved through CRUD')); - // Now grab the SQL schema and verify that too. - $schema = drupal_get_schema(_field_sql_storage_tablename($field)); - $this->assertEqual(count($schema['foreign keys']), 1, t("There is 1 foreign key in the schema")); - $foreign_key = reset($schema['foreign keys']); - $filter_column = _field_sql_storage_columnname($field['field_name'], 'format'); - $this->assertEqual($foreign_key['table'], 'filter_format', t('Foreign key table name preserved in the schema')); - $this->assertEqual($foreign_key['columns'][$filter_column], 'format', t('Foreign key column name preserved in the schema')); - } -} diff --git a/modules/field/modules/list/list.info b/modules/field/modules/list/list.info deleted file mode 100644 index 6eb711799b7..00000000000 --- a/modules/field/modules/list/list.info +++ /dev/null @@ -1,8 +0,0 @@ -name = List -description = Defines list field types. Use with Options to create selection lists. -package = Core -version = VERSION -core = 8.x -dependencies[] = field -dependencies[] = options -files[] = tests/list.test diff --git a/modules/field/modules/list/list.install b/modules/field/modules/list/list.install deleted file mode 100644 index c86a21919c1..00000000000 --- a/modules/field/modules/list/list.install +++ /dev/null @@ -1,46 +0,0 @@ -<?php - -/** - * @file - * Install, update and uninstall functions for the list module. - */ - -/** - * Implements hook_field_schema(). - */ -function list_field_schema($field) { - switch ($field['type']) { - case 'list_text': - $columns = array( - 'value' => array( - 'type' => 'varchar', - 'length' => 255, - 'not null' => FALSE, - ), - ); - break; - case 'list_float': - $columns = array( - 'value' => array( - 'type' => 'float', - 'not null' => FALSE, - ), - ); - break; - case 'list_integer': - case 'list_boolean': - $columns = array( - 'value' => array( - 'type' => 'int', - 'not null' => FALSE, - ), - ); - break; - } - return array( - 'columns' => $columns, - 'indexes' => array( - 'value' => array('value'), - ), - ); -} diff --git a/modules/field/modules/list/list.module b/modules/field/modules/list/list.module deleted file mode 100644 index 65235514278..00000000000 --- a/modules/field/modules/list/list.module +++ /dev/null @@ -1,471 +0,0 @@ -<?php - -/** - * @file - * Defines list field types that can be used with the Options module. - */ - -/** - * Implements hook_help(). - */ -function list_help($path, $arg) { - switch ($path) { - case 'admin/help#list': - $output = ''; - $output .= '<h3>' . t('About') . '</h3>'; - $output .= '<p>' . t('The List module defines various fields for storing a list of items, for use with the Field module. Usually these items are entered through a select list, checkboxes, or radio buttons. See the <a href="@field-help">Field module help page</a> for more information about fields.', array('@field-help' => url('admin/help/field'))) . '</p>'; - return $output; - } -} - -/** - * Implements hook_field_info(). - */ -function list_field_info() { - return array( - 'list_integer' => array( - 'label' => t('List (integer)'), - 'description' => t("This field stores integer values from a list of allowed 'value => label' pairs, i.e. 'Lifetime in days': 1 => 1 day, 7 => 1 week, 31 => 1 month."), - 'settings' => array('allowed_values' => array(), 'allowed_values_function' => ''), - 'default_widget' => 'options_select', - 'default_formatter' => 'list_default', - ), - 'list_float' => array( - 'label' => t('List (float)'), - 'description' => t("This field stores float values from a list of allowed 'value => label' pairs, i.e. 'Fraction': 0 => 0, .25 => 1/4, .75 => 3/4, 1 => 1."), - 'settings' => array('allowed_values' => array(), 'allowed_values_function' => ''), - 'default_widget' => 'options_select', - 'default_formatter' => 'list_default', - ), - 'list_text' => array( - 'label' => t('List (text)'), - 'description' => t("This field stores text values from a list of allowed 'value => label' pairs, i.e. 'US States': IL => Illinois, IA => Iowa, IN => Indiana."), - 'settings' => array('allowed_values' => 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' => array(), 'allowed_values_function' => ''), - 'default_widget' => 'options_buttons', - 'default_formatter' => 'list_default', - ), - ); -} - -/** - * Implements hook_field_settings_form(). - */ -function list_field_settings_form($field, $instance, $has_data) { - $settings = $field['settings']; - - switch ($field['type']) { - case 'list_integer': - case 'list_float': - case 'list_text': - $form['allowed_values'] = array( - '#type' => 'textarea', - '#title' => t('Allowed values list'), - '#default_value' => list_allowed_values_string($settings['allowed_values']), - '#rows' => 10, - '#element_validate' => array('list_allowed_values_setting_validate'), - '#field_has_data' => $has_data, - '#field' => $field, - '#field_type' => $field['type'], - '#access' => empty($settings['allowed_values_function']), - ); - - $description = '<p>' . t('The possible values this field can contain. Enter one value per line, in the format key|label.'); - if ($field['type'] == 'list_integer' || $field['type'] == 'list_float') { - $description .= '<br/>' . t('The key is the stored value, and must be numeric. The label will be used in displayed values and edit forms.'); - $description .= '<br/>' . t('The label is optional: if a line contains a single number, it will be used as key and label.'); - $description .= '<br/>' . t('Lists of labels are also accepted (one label per line), only if the field does not hold any values yet. Numeric keys will be automatically generated from the positions in the list.'); - } - else { - $description .= '<br/>' . t('The key is the stored value. The label will be used in displayed values and edit forms.'); - $description .= '<br/>' . t('The label is optional: if a line contains a single string, it will be used as key and label.'); - } - $description .= '</p>'; - $form['allowed_values']['#description'] = $description; - - break; - - case 'list_boolean': - $values = $settings['allowed_values']; - $off_value = array_shift($values); - $on_value = array_shift($values); - - $form['allowed_values'] = array( - '#type' => 'value', - '#description' => '', - '#value_callback' => 'list_boolean_allowed_values_callback', - '#access' => empty($settings['allowed_values_function']), - ); - $form['allowed_values']['on'] = array( - '#type' => 'textfield', - '#title' => t('On value'), - '#default_value' => $on_value, - '#required' => FALSE, - '#description' => t('If left empty, "1" will be used.'), - // Change #parents to make sure the element is not saved into field - // settings. - '#parents' => array('on'), - ); - $form['allowed_values']['off'] = array( - '#type' => 'textfield', - '#title' => t('Off value'), - '#default_value' => $off_value, - '#required' => FALSE, - '#description' => t('If left empty, "0" will be used.'), - // Change #parents to make sure the element is not saved into field - // settings. - '#parents' => array('off'), - ); - - // Link the allowed value to the on / off elements to prepare for the rare - // case of an alter changing #parents. - $form['allowed_values']['#on_parents'] = &$form['allowed_values']['on']['#parents']; - $form['allowed_values']['#off_parents'] = &$form['allowed_values']['off']['#parents']; - - break; - } - - // Alter the description for allowed values depending on the widget type. - if ($instance['widget']['type'] == 'options_onoff') { - $form['allowed_values']['#description'] .= '<p>' . t("For a 'single on/off checkbox' widget, define the 'off' value first, then the 'on' value in the <strong>Allowed values</strong> section. Note that the checkbox will be labeled with the label of the 'on' value.") . '</p>'; - } - elseif ($instance['widget']['type'] == 'options_buttons') { - $form['allowed_values']['#description'] .= '<p>' . t("The 'checkboxes/radio buttons' widget will display checkboxes if the <em>Number of values</em> option is greater than 1 for this field, otherwise radios will be displayed.") . '</p>'; - } - $form['allowed_values']['#description'] .= '<p>' . t('Allowed HTML tags in labels: @tags', array('@tags' => _field_filter_xss_display_allowed_tags())) . '</p>'; - - $form['allowed_values_function'] = array( - '#type' => 'value', - '#value' => $settings['allowed_values_function'], - ); - $form['allowed_values_function_display'] = array( - '#type' => 'item', - '#title' => t('Allowed values list'), - '#markup' => t('The value of this field is being determined by the %function function and may not be changed.', array('%function' => $settings['allowed_values_function'])), - '#access' => !empty($settings['allowed_values_function']), - ); - - return $form; -} - -/** - * Element validate callback; check that the entered values are valid. - */ -function list_allowed_values_setting_validate($element, &$form_state) { - $field = $element['#field']; - $has_data = $element['#field_has_data']; - $field_type = $field['type']; - $generate_keys = ($field_type == 'list_integer' || $field_type == 'list_float') && !$has_data; - - $values = list_extract_allowed_values($element['#value'], $field['type'], $generate_keys); - - if (!is_array($values)) { - form_error($element, t('Allowed values list: invalid input.')); - } - else { - // Check that keys are valid for the field type. - foreach ($values as $key => $value) { - if ($field_type == 'list_integer' && !preg_match('/^-?\d+$/', $key)) { - form_error($element, t('Allowed values list: keys must be integers.')); - break; - } - if ($field_type == 'list_float' && !is_numeric($key)) { - form_error($element, t('Allowed values list: each key must be a valid integer or decimal.')); - break; - } - elseif ($field_type == 'list_text' && drupal_strlen($key) > 255) { - form_error($element, t('Allowed values list: each key must be a string at most 255 characters long.')); - break; - } - } - - // Prevent removing values currently in use. - if ($has_data) { - $lost_keys = array_diff(array_keys($field['settings']['allowed_values']), array_keys($values)); - if (_list_values_in_use($field, $lost_keys)) { - form_error($element, t('Allowed values list: some values are being removed while currently in use.')); - } - } - - form_set_value($element, $values, $form_state); - } -} - -/** -* Form element #value_callback: assembles the allowed values for 'boolean' fields. -*/ -function list_boolean_allowed_values_callback($element, $input, $form_state) { - $on = drupal_array_get_nested_value($form_state['input'], $element['#on_parents']); - $off = drupal_array_get_nested_value($form_state['input'], $element['#off_parents']); - return array($off, $on); -} - -/** - * Implements hook_field_update_field(). - */ -function list_field_update_field($field, $prior_field, $has_data) { - drupal_static_reset('list_allowed_values'); -} - -/** - * Returns the array of allowed values for a list field. - * - * The strings are not safe for output. Keys and values of the array should be - * sanitized through field_filter_xss() before being displayed. - * - * @param $field - * The field definition. - * - * @return - * The array of allowed values. Keys of the array are the raw stored values - * (number or text), values of the array are the display labels. - */ -function list_allowed_values($field) { - $allowed_values = &drupal_static(__FUNCTION__, array()); - - if (!isset($allowed_values[$field['id']])) { - $function = $field['settings']['allowed_values_function']; - if (!empty($function) && function_exists($function)) { - $values = $function($field); - } - else { - $values = $field['settings']['allowed_values']; - } - - $allowed_values[$field['id']] = $values; - } - - return $allowed_values[$field['id']]; -} - -/** - * Parses a string of 'allowed values' into an array. - * - * @param $string - * The list of allowed values in string format described in - * list_allowed_values_string(). - * @param $field_type - * The field type. Either 'list_number' or 'list_text'. - * @param $generate_keys - * Boolean value indicating whether to generate keys based on the position of - * the value if a key is not manually specified, and if the value cannot be - * used as a key. This should only be TRUE for fields of type 'list_number'. - * - * @return - * The array of extracted key/value pairs, or NULL if the string is invalid. - * - * @see list_allowed_values_string() - */ -function list_extract_allowed_values($string, $field_type, $generate_keys) { - $values = array(); - - $list = explode("\n", $string); - $list = array_map('trim', $list); - $list = array_filter($list, 'strlen'); - - $generated_keys = $explicit_keys = FALSE; - foreach ($list as $position => $text) { - $value = $key = FALSE; - - // Check for an explicit key. - $matches = array(); - if (preg_match('/(.*)\|(.*)/', $text, $matches)) { - $key = $matches[1]; - $value = $matches[2]; - $explicit_keys = TRUE; - } - // Otherwise see if we can use the value as the key. Detecting true integer - // strings takes a little trick. - elseif ($field_type == 'list_text' - || ($field_type == 'list_float' && is_numeric($text)) - || ($field_type == 'list_integer' && is_numeric($text) && (float) $text == intval($text))) { - $key = $value = $text; - $explicit_keys = TRUE; - } - // Otherwise see if we can generate a key from the position. - elseif ($generate_keys) { - $key = (string) $position; - $value = $text; - $generated_keys = TRUE; - } - else { - return; - } - - // Float keys are represented as strings and need to be disambiguated - // ('.5' is '0.5'). - if ($field_type == 'list_float' && is_numeric($key)) { - $key = (string) (float) $key; - } - - $values[$key] = $value; - } - - // We generate keys only if the list contains no explicit key at all. - if ($explicit_keys && $generated_keys) { - return; - } - - return $values; -} - -/** - * Generates a string representation of an array of 'allowed values'. - * - * This string format is suitable for edition in a textarea. - * - * @param $values - * An array of values, where array keys are values and array values are - * labels. - * - * @return - * The string representation of the $values array: - * - Values are separated by a carriage return. - * - Each value is in the format "value|label" or "value". - */ -function list_allowed_values_string($values) { - $lines = array(); - foreach ($values as $key => $value) { - $lines[] = "$key|$value"; - } - return implode("\n", $lines); -} - -/** - * Implements hook_field_update_forbid(). - */ -function list_field_update_forbid($field, $prior_field, $has_data) { - if ($field['module'] == 'list' && $has_data) { - // Forbid any update that removes allowed values with actual data. - $lost_keys = array_diff(array_keys($prior_field['settings']['allowed_values']), array_keys($field['settings']['allowed_values'])); - if (_list_values_in_use($field, $lost_keys)) { - throw new FieldUpdateForbiddenException(t('Cannot update a list field to not include keys with existing data.')); - } - } -} - -/** - * Checks if a list of values are being used in actual field values. - */ -function _list_values_in_use($field, $values) { - if ($values) { - $query = new EntityFieldQuery(); - $found = $query - ->fieldCondition($field['field_name'], 'value', $values) - ->range(0, 1) - ->execute(); - return !empty($found); - } - - return FALSE; -} - -/** - * Implements hook_field_validate(). - * - * Possible error codes: - * - 'list_illegal_value': The value is not part of the list of allowed values. - */ -function list_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) { - $allowed_values = list_allowed_values($field); - foreach ($items as $delta => $item) { - if (!empty($item['value'])) { - if (!empty($allowed_values) && !isset($allowed_values[$item['value']])) { - $errors[$field['field_name']][$langcode][$delta][] = array( - 'error' => 'list_illegal_value', - 'message' => t('%name: illegal value.', array('%name' => $instance['label'])), - ); - } - } - } -} - -/** - * Implements hook_field_is_empty(). - */ -function list_field_is_empty($item, $field) { - if (empty($item['value']) && (string) $item['value'] !== '0') { - return TRUE; - } - return FALSE; -} - -/** - * Implements hook_field_widget_info_alter(). - * - * The List module does not implement widgets of its own, but reuses the - * widgets defined in options.module. - * - * @see list_options_list() - */ -function list_field_widget_info_alter(&$info) { - $widgets = array( - 'options_select' => array('list_integer', 'list_float', 'list_text'), - 'options_buttons' => array('list_integer', 'list_float', 'list_text', 'list_boolean'), - 'options_onoff' => array('list_boolean'), - ); - - foreach ($widgets as $widget => $field_types) { - $info[$widget]['field types'] = array_merge($info[$widget]['field types'], $field_types); - } -} - -/** - * Implements hook_options_list(). - */ -function list_options_list($field, $instance) { - return list_allowed_values($field); -} - -/** - * Implements hook_field_formatter_info(). - */ -function list_field_formatter_info() { - return array( - 'list_default' => array( - 'label' => t('Default'), - 'field types' => array('list_integer', 'list_float', 'list_text', 'list_boolean'), - ), - 'list_key' => array( - 'label' => t('Key'), - 'field types' => array('list_integer', 'list_float', 'list_text', 'list_boolean'), - ), - ); -} - -/** - * Implements hook_field_formatter_view(). - */ -function list_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) { - $element = array(); - - switch ($display['type']) { - case 'list_default': - $allowed_values = list_allowed_values($field); - foreach ($items as $delta => $item) { - if (isset($allowed_values[$item['value']])) { - $output = field_filter_xss($allowed_values[$item['value']]); - } - else { - // If no match was found in allowed values, fall back to the key. - $output = field_filter_xss($item['value']); - } - $element[$delta] = array('#markup' => $output); - } - break; - - case 'list_key': - foreach ($items as $delta => $item) { - $element[$delta] = array('#markup' => field_filter_xss($item['value'])); - } - break; - } - - return $element; -} diff --git a/modules/field/modules/list/tests/list.test b/modules/field/modules/list/tests/list.test deleted file mode 100644 index 765901a8499..00000000000 --- a/modules/field/modules/list/tests/list.test +++ /dev/null @@ -1,374 +0,0 @@ -<?php - -/** - * @file - * Tests for list.module. - */ - -/** - * Tests for the 'List' field types. - */ -class ListFieldTestCase extends FieldTestCase { - public static function getInfo() { - return array( - 'name' => 'List field', - 'description' => 'Test the List field type.', - 'group' => 'Field types', - ); - } - - function setUp() { - parent::setUp('field_test'); - - $this->field_name = 'test_list'; - $this->field = array( - 'field_name' => $this->field_name, - 'type' => 'list_integer', - 'cardinality' => 1, - 'settings' => array( - 'allowed_values' => array(1 => 'One', 2 => 'Two', 3 => 'Three'), - ), - ); - $this->field = field_create_field($this->field); - - $this->instance = array( - 'field_name' => $this->field_name, - 'entity_type' => 'test_entity', - 'bundle' => 'test_bundle', - 'widget' => array( - 'type' => 'options_buttons', - ), - ); - $this->instance = field_create_instance($this->instance); - } - - /** - * Test that allowed values can be updated. - */ - function testUpdateAllowedValues() { - $langcode = LANGUAGE_NONE; - - // All three options appear. - $entity = field_test_create_stub_entity(); - $form = drupal_get_form('field_test_entity_form', $entity); - $this->assertTrue(!empty($form[$this->field_name][$langcode][1]), t('Option 1 exists')); - $this->assertTrue(!empty($form[$this->field_name][$langcode][2]), t('Option 2 exists')); - $this->assertTrue(!empty($form[$this->field_name][$langcode][3]), t('Option 3 exists')); - - // Use one of the values in an actual entity, and check that this value - // cannot be removed from the list. - $entity = field_test_create_stub_entity(); - $entity->{$this->field_name}[$langcode][0] = array('value' => 1); - field_test_entity_save($entity); - $this->field['settings']['allowed_values'] = array(2 => 'Two'); - try { - field_update_field($this->field); - $this->fail(t('Cannot update a list field to not include keys with existing data.')); - } - catch (FieldException $e) { - $this->pass(t('Cannot update a list field to not include keys with existing data.')); - } - // Empty the value, so that we can actually remove the option. - $entity->{$this->field_name}[$langcode] = array(); - field_test_entity_save($entity); - - // Removed options do not appear. - $this->field['settings']['allowed_values'] = array(2 => 'Two'); - field_update_field($this->field); - $entity = field_test_create_stub_entity(); - $form = drupal_get_form('field_test_entity_form', $entity); - $this->assertTrue(empty($form[$this->field_name][$langcode][1]), t('Option 1 does not exist')); - $this->assertTrue(!empty($form[$this->field_name][$langcode][2]), t('Option 2 exists')); - $this->assertTrue(empty($form[$this->field_name][$langcode][3]), t('Option 3 does not exist')); - - // Completely new options appear. - $this->field['settings']['allowed_values'] = array(10 => 'Update', 20 => 'Twenty'); - field_update_field($this->field); - $form = drupal_get_form('field_test_entity_form', $entity); - $this->assertTrue(empty($form[$this->field_name][$langcode][1]), t('Option 1 does not exist')); - $this->assertTrue(empty($form[$this->field_name][$langcode][2]), t('Option 2 does not exist')); - $this->assertTrue(empty($form[$this->field_name][$langcode][3]), t('Option 3 does not exist')); - $this->assertTrue(!empty($form[$this->field_name][$langcode][10]), t('Option 10 exists')); - $this->assertTrue(!empty($form[$this->field_name][$langcode][20]), t('Option 20 exists')); - - // Options are reset when a new field with the same name is created. - field_delete_field($this->field_name); - unset($this->field['id']); - $this->field['settings']['allowed_values'] = array(1 => 'One', 2 => 'Two', 3 => 'Three'); - $this->field = field_create_field($this->field); - $this->instance = array( - 'field_name' => $this->field_name, - 'entity_type' => 'test_entity', - 'bundle' => 'test_bundle', - 'widget' => array( - 'type' => 'options_buttons', - ), - ); - $this->instance = field_create_instance($this->instance); - $entity = field_test_create_stub_entity(); - $form = drupal_get_form('field_test_entity_form', $entity); - $this->assertTrue(!empty($form[$this->field_name][$langcode][1]), t('Option 1 exists')); - $this->assertTrue(!empty($form[$this->field_name][$langcode][2]), t('Option 2 exists')); - $this->assertTrue(!empty($form[$this->field_name][$langcode][3]), t('Option 3 exists')); - } -} - -/** - * List module UI tests. - */ -class ListFieldUITestCase extends FieldTestCase { - public static function getInfo() { - return array( - 'name' => 'List field UI', - 'description' => 'Test the List field UI functionality.', - 'group' => 'Field types', - ); - } - - function setUp() { - parent::setUp('field_test', 'field_ui'); - - // Create test user. - $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer taxonomy')); - $this->drupalLogin($admin_user); - - // Create content type, with underscores. - $type_name = 'test_' . strtolower($this->randomName()); - $type = $this->drupalCreateContentType(array('name' => $type_name, 'type' => $type_name)); - $this->type = $type->type; - // Store a valid URL name, with hyphens instead of underscores. - $this->hyphen_type = str_replace('_', '-', $this->type); - } - - /** - * List (integer) : test 'allowed values' input. - */ - function testListAllowedValuesInteger() { - $this->field_name = 'field_list_integer'; - $this->createListField('list_integer'); - - // Flat list of textual values. - $string = "Zero\nOne"; - $array = array('0' => 'Zero', '1' => 'One'); - $this->assertAllowedValuesInput($string, $array, t('Unkeyed lists are accepted.')); - // Explicit integer keys. - $string = "0|Zero\n2|Two"; - $array = array('0' => 'Zero', '2' => 'Two'); - $this->assertAllowedValuesInput($string, $array, t('Integer keys are accepted.')); - // Check that values can be added and removed. - $string = "0|Zero\n1|One"; - $array = array('0' => 'Zero', '1' => 'One'); - $this->assertAllowedValuesInput($string, $array, t('Values can be added and removed.')); - // Non-integer keys. - $this->assertAllowedValuesInput("1.1|One", 'keys must be integers', t('Non integer keys are rejected.')); - $this->assertAllowedValuesInput("abc|abc", 'keys must be integers', t('Non integer keys are rejected.')); - // Mixed list of keyed and unkeyed values. - $this->assertAllowedValuesInput("Zero\n1|One", 'invalid input', t('Mixed lists are rejected.')); - - // Create a node with actual data for the field. - $settings = array( - 'type' => $this->type, - $this->field_name => array(LANGUAGE_NONE => array(array('value' => 1))), - ); - $node = $this->drupalCreateNode($settings); - - // Check that a flat list of values is rejected once the field has data. - $this->assertAllowedValuesInput( "Zero\nOne", 'invalid input', t('Unkeyed lists are rejected once the field has data.')); - - // Check that values can be added but values in use cannot be removed. - $string = "0|Zero\n1|One\n2|Two"; - $array = array('0' => 'Zero', '1' => 'One', '2' => 'Two'); - $this->assertAllowedValuesInput($string, $array, t('Values can be added.')); - $string = "0|Zero\n1|One"; - $array = array('0' => 'Zero', '1' => 'One'); - $this->assertAllowedValuesInput($string, $array, t('Values not in use can be removed.')); - $this->assertAllowedValuesInput("0|Zero", 'some values are being removed while currently in use', t('Values in use cannot be removed.')); - - // Delete the node, remove the value. - node_delete($node->nid); - $string = "0|Zero"; - $array = array('0' => 'Zero'); - $this->assertAllowedValuesInput($string, $array, t('Values not in use can be removed.')); - } - - /** - * List (float) : test 'allowed values' input. - */ - function testListAllowedValuesFloat() { - $this->field_name = 'field_list_float'; - $this->createListField('list_float'); - - // Flat list of textual values. - $string = "Zero\nOne"; - $array = array('0' => 'Zero', '1' => 'One'); - $this->assertAllowedValuesInput($string, $array, t('Unkeyed lists are accepted.')); - // Explicit numeric keys. - $string = "0|Zero\n.5|Point five"; - $array = array('0' => 'Zero', '0.5' => 'Point five'); - $this->assertAllowedValuesInput($string, $array, t('Integer keys are accepted.')); - // Check that values can be added and removed. - $string = "0|Zero\n.5|Point five\n1.0|One"; - $array = array('0' => 'Zero', '0.5' => 'Point five', '1' => 'One'); - $this->assertAllowedValuesInput($string, $array, t('Values can be added and removed.')); - // Non-numeric keys. - $this->assertAllowedValuesInput("abc|abc\n", 'each key must be a valid integer or decimal', t('Non numeric keys are rejected.')); - // Mixed list of keyed and unkeyed values. - $this->assertAllowedValuesInput("Zero\n1|One\n", 'invalid input', t('Mixed lists are rejected.')); - - // Create a node with actual data for the field. - $settings = array( - 'type' => $this->type, - $this->field_name => array(LANGUAGE_NONE => array(array('value' => .5))), - ); - $node = $this->drupalCreateNode($settings); - - // Check that a flat list of values is rejected once the field has data. - $this->assertAllowedValuesInput("Zero\nOne", 'invalid input', t('Unkeyed lists are rejected once the field has data.')); - - // Check that values can be added but values in use cannot be removed. - $string = "0|Zero\n.5|Point five\n2|Two"; - $array = array('0' => 'Zero', '0.5' => 'Point five', '2' => 'Two'); - $this->assertAllowedValuesInput($string, $array, t('Values can be added.')); - $string = "0|Zero\n.5|Point five"; - $array = array('0' => 'Zero', '0.5' => 'Point five'); - $this->assertAllowedValuesInput($string, $array, t('Values not in use can be removed.')); - $this->assertAllowedValuesInput("0|Zero", 'some values are being removed while currently in use', t('Values in use cannot be removed.')); - - // Delete the node, remove the value. - node_delete($node->nid); - $string = "0|Zero"; - $array = array('0' => 'Zero'); - $this->assertAllowedValuesInput($string, $array, t('Values not in use can be removed.')); - } - - /** - * List (text) : test 'allowed values' input. - */ - function testListAllowedValuesText() { - $this->field_name = 'field_list_text'; - $this->createListField('list_text'); - - // Flat list of textual values. - $string = "Zero\nOne"; - $array = array('Zero' => 'Zero', 'One' => 'One'); - $this->assertAllowedValuesInput($string, $array, t('Unkeyed lists are accepted.')); - // Explicit keys. - $string = "zero|Zero\none|One"; - $array = array('zero' => 'Zero', 'one' => 'One'); - $this->assertAllowedValuesInput($string, $array, t('Explicit keys are accepted.')); - // Check that values can be added and removed. - $string = "zero|Zero\ntwo|Two"; - $array = array('zero' => 'Zero', 'two' => 'Two'); - $this->assertAllowedValuesInput($string, $array, t('Values can be added and removed.')); - // Mixed list of keyed and unkeyed values. - $string = "zero|Zero\nOne\n"; - $array = array('zero' => 'Zero', 'One' => 'One'); - $this->assertAllowedValuesInput($string, $array, t('Mixed lists are accepted.')); - // Overly long keys. - $this->assertAllowedValuesInput("zero|Zero\n" . $this->randomName(256) . "|One", 'each key must be a string at most 255 characters long', t('Overly long keys are rejected.')); - - // Create a node with actual data for the field. - $settings = array( - 'type' => $this->type, - $this->field_name => array(LANGUAGE_NONE => array(array('value' => 'One'))), - ); - $node = $this->drupalCreateNode($settings); - - // Check that flat lists of values are still accepted once the field has - // data. - $string = "Zero\nOne"; - $array = array('Zero' => 'Zero', 'One' => 'One'); - $this->assertAllowedValuesInput($string, $array, t('Unkeyed lists are still accepted once the field has data.')); - - // Check that values can be added but values in use cannot be removed. - $string = "Zero\nOne\nTwo"; - $array = array('Zero' => 'Zero', 'One' => 'One', 'Two' => 'Two'); - $this->assertAllowedValuesInput($string, $array, t('Values can be added.')); - $string = "Zero\nOne"; - $array = array('Zero' => 'Zero', 'One' => 'One'); - $this->assertAllowedValuesInput($string, $array, t('Values not in use can be removed.')); - $this->assertAllowedValuesInput("Zero", 'some values are being removed while currently in use', t('Values in use cannot be removed.')); - - // Delete the node, remove the value. - node_delete($node->nid); - $string = "Zero"; - $array = array('Zero' => 'Zero'); - $this->assertAllowedValuesInput($string, $array, t('Values not in use can be removed.')); - } - - /** - * List (boolen) : test 'On/Off' values input. - */ - function testListAllowedValuesBoolean() { - $this->field_name = 'field_list_boolean'; - $this->createListField('list_boolean'); - - // Check that the separate 'On' and 'Off' form fields work. - $on = $this->randomName(); - $off = $this->randomName(); - $allowed_values = array(1 => $on, 0 => $off); - $edit = array( - 'on' => $on, - 'off' => $off, - ); - $this->drupalPost($this->admin_path, $edit, t('Save settings')); - $this->assertText("Saved field_list_boolean configuration.", t("The 'On' and 'Off' form fields work for boolean fields.")); - // Test the allowed_values on the field settings form. - $this->drupalGet($this->admin_path); - $this->assertFieldByName('on', $on, t("The 'On' value is stored correctly.")); - $this->assertFieldByName('off', $off, t("The 'Off' value is stored correctly.")); - $field = field_info_field($this->field_name); - $this->assertEqual($field['settings']['allowed_values'], $allowed_values, t('The allowed value is correct')); - $this->assertFalse(isset($field['settings']['on']), t('The on value is not saved into settings')); - $this->assertFalse(isset($field['settings']['off']), t('The off value is not saved into settings')); - } - - /** - * Helper function to create list field of a given type. - * - * @param string $type - * 'list_integer', 'list_float', 'list_text' or 'list_boolean' - */ - protected function createListField($type) { - // Create a test field and instance. - $field = array( - 'field_name' => $this->field_name, - 'type' => $type, - ); - field_create_field($field); - $instance = array( - 'field_name' => $this->field_name, - 'entity_type' => 'node', - 'bundle' => $this->type, - ); - field_create_instance($instance); - - $this->admin_path = 'admin/structure/types/manage/' . $this->hyphen_type . '/fields/' . $this->field_name; - } - - /** - * Tests a string input for the 'allowed values' form element. - * - * @param $input_string - * The input string, in the pipe-linefeed format expected by the form - * element. - * @param $result - * Either an expected resulting array in - * $field['settings']['allowed_values'], or an expected error message. - * @param $message - * Message to display. - */ - function assertAllowedValuesInput($input_string, $result, $message) { - $edit = array('field[settings][allowed_values]' => $input_string); - $this->drupalPost($this->admin_path, $edit, t('Save settings')); - - if (is_string($result)) { - $this->assertText($result, $message); - } - else { - field_info_cache_clear(); - $field = field_info_field($this->field_name); - $this->assertIdentical($field['settings']['allowed_values'], $result, $message); - } - } -} diff --git a/modules/field/modules/list/tests/list_test.info b/modules/field/modules/list/tests/list_test.info deleted file mode 100644 index 32c1d694f02..00000000000 --- a/modules/field/modules/list/tests/list_test.info +++ /dev/null @@ -1,6 +0,0 @@ -name = "List test" -description = "Support module for the List module tests." -core = 8.x -package = Testing -version = VERSION -hidden = TRUE diff --git a/modules/field/modules/list/tests/list_test.module b/modules/field/modules/list/tests/list_test.module deleted file mode 100644 index 8d5340412da..00000000000 --- a/modules/field/modules/list/tests/list_test.module +++ /dev/null @@ -1,23 +0,0 @@ -<?php - -/** - * @file - * Helper module for the List module tests. - */ - -/** - * Allowed values callback. - */ -function list_test_allowed_values_callback($field) { - $values = array( - 'Group 1' => array( - 0 => 'Zero', - ), - 1 => 'One', - 'Group 2' => array( - 2 => 'Some <script>dangerous</script> & unescaped <strong>markup</strong>', - ), - ); - - return $values; -} diff --git a/modules/field/modules/number/number.info b/modules/field/modules/number/number.info deleted file mode 100644 index f38cbb4be77..00000000000 --- a/modules/field/modules/number/number.info +++ /dev/null @@ -1,7 +0,0 @@ -name = Number -description = Defines numeric field types. -package = Core -version = VERSION -core = 8.x -dependencies[] = field -files[] = number.test diff --git a/modules/field/modules/number/number.install b/modules/field/modules/number/number.install deleted file mode 100644 index 02c7a305750..00000000000 --- a/modules/field/modules/number/number.install +++ /dev/null @@ -1,45 +0,0 @@ -<?php - -/** - * @file - * Install, update and uninstall functions for the number module. - */ - -/** - * Implements hook_field_schema(). - */ -function number_field_schema($field) { - switch ($field['type']) { - case 'number_integer' : - $columns = array( - 'value' => array( - 'type' => 'int', - 'not null' => FALSE - ), - ); - break; - - case 'number_float' : - $columns = array( - 'value' => array( - 'type' => 'float', - 'not null' => FALSE - ), - ); - break; - - case 'number_decimal' : - $columns = array( - 'value' => array( - 'type' => 'numeric', - 'precision' => $field['settings']['precision'], - 'scale' => $field['settings']['scale'], - 'not null' => FALSE - ), - ); - break; - } - return array( - 'columns' => $columns, - ); -} diff --git a/modules/field/modules/number/number.module b/modules/field/modules/number/number.module deleted file mode 100644 index 87e2d3a9313..00000000000 --- a/modules/field/modules/number/number.module +++ /dev/null @@ -1,419 +0,0 @@ -<?php - -/** - * @file - * Defines numeric field types. - */ - -/** - * Implements hook_help(). - */ -function number_help($path, $arg) { - switch ($path) { - case 'admin/help#number': - $output = ''; - $output .= '<h3>' . t('About') . '</h3>'; - $output .= '<p>' . t('The Number module defines various numeric field types for the Field module. Numbers can be in integer, decimal, or floating-point form, and they can be formatted when displayed. Number fields can be limited to a specific set of input values or to a range of values. See the <a href="@field-help">Field module help page</a> for more information about fields.', array('@field-help' => url('admin/help/field'))) . '</p>'; - return $output; - } -} - -/** - * Implements 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_separator' => '.'), - 'instance_settings' => array('min' => '', 'max' => '', 'prefix' => '', 'suffix' => ''), - 'default_widget' => 'number', - 'default_formatter' => 'number_decimal', - ), - 'number_float' => array( - 'label' => t('Float'), - 'description' => t('This field stores a number in the database in a floating point format.'), - 'settings' => array('decimal_separator' => '.'), - 'instance_settings' => array('min' => '', 'max' => '', 'prefix' => '', 'suffix' => ''), - 'default_widget' => 'number', - 'default_formatter' => 'number_decimal', - ), - ); -} - -/** - * Implements hook_field_settings_form(). - */ -function number_field_settings_form($field, $instance, $has_data) { - $settings = $field['settings']; - $form = array(); - - if ($field['type'] == 'number_decimal') { - $form['precision'] = array( - '#type' => 'select', - '#title' => t('Precision'), - '#options' => drupal_map_assoc(range(10, 32)), - '#default_value' => $settings['precision'], - '#description' => t('The total number of digits to store in the database, including those to the right of the decimal.'), - '#disabled' => $has_data, - ); - $form['scale'] = array( - '#type' => 'select', - '#title' => t('Scale'), - '#options' => drupal_map_assoc(range(0, 10)), - '#default_value' => $settings['scale'], - '#description' => t('The number of digits to the right of the decimal.'), - '#disabled' => $has_data, - ); - } - if ($field['type'] == 'number_decimal' || $field['type'] == 'number_float') { - $form['decimal_separator'] = array( - '#type' => 'select', - '#title' => t('Decimal marker'), - '#options' => array('.' => t('Decimal point'), ',' => t('Comma')), - '#default_value' => $settings['decimal_separator'], - '#description' => t('The character users will input to mark the decimal point in forms.'), - ); - } - - return $form; -} - -/** - * Implements hook_field_instance_settings_form(). - */ -function number_field_instance_settings_form($field, $instance) { - $settings = $instance['settings']; - - $form['min'] = array( - '#type' => 'textfield', - '#title' => t('Minimum'), - '#default_value' => $settings['min'], - '#description' => t('The minimum value that should be allowed in this field. Leave blank for no minimum.'), - '#element_validate' => array('element_validate_number'), - ); - $form['max'] = array( - '#type' => 'textfield', - '#title' => t('Maximum'), - '#default_value' => $settings['max'], - '#description' => t('The maximum value that should be allowed in this field. Leave blank for no maximum.'), - '#element_validate' => array('element_validate_number'), - ); - $form['prefix'] = array( - '#type' => 'textfield', - '#title' => t('Prefix'), - '#default_value' => $settings['prefix'], - '#size' => 60, - '#description' => t("Define a string that should be prefixed to the value, like '$ ' or '€ '. Leave blank for none. Separate singular and plural values with a pipe ('pound|pounds')."), - ); - $form['suffix'] = array( - '#type' => 'textfield', - '#title' => t('Suffix'), - '#default_value' => $settings['suffix'], - '#size' => 60, - '#description' => t("Define a string that should be suffixed to the value, like ' m', ' kb/s'. Leave blank for none. Separate singular and plural values with a pipe ('pound|pounds')."), - ); - - return $form; -} - -/** - * Implements hook_field_validate(). - * - * Possible error codes: - * - 'number_min': The value is less than the allowed minimum value. - * - 'number_max': The value is greater than the allowed maximum value. - */ -function number_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) { - foreach ($items as $delta => $item) { - if ($item['value'] != '') { - if (is_numeric($instance['settings']['min']) && $item['value'] < $instance['settings']['min']) { - $errors[$field['field_name']][$langcode][$delta][] = array( - 'error' => 'number_min', - 'message' => t('%name: the value may be no less than %min.', array('%name' => $instance['label'], '%min' => $instance['settings']['min'])), - ); - } - if (is_numeric($instance['settings']['max']) && $item['value'] > $instance['settings']['max']) { - $errors[$field['field_name']][$langcode][$delta][] = array( - 'error' => 'number_max', - 'message' => t('%name: the value may be no greater than %max.', array('%name' => $instance['label'], '%max' => $instance['settings']['max'])), - ); - } - } - } -} - -/** - * Implements hook_field_presave(). - */ -function number_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) { - if ($field['type'] == 'number_decimal') { - // Let PHP round the value to ensure consistent behavior across storage - // backends. - foreach ($items as $delta => $item) { - if (isset($item['value'])) { - $items[$delta]['value'] = round($item['value'], $field['settings']['scale']); - } - } - } -} - -/** - * Implements hook_field_is_empty(). - */ -function number_field_is_empty($item, $field) { - if (empty($item['value']) && (string) $item['value'] !== '0') { - return TRUE; - } - return FALSE; -} - -/** - * Implements hook_field_formatter_info(). - */ -function number_field_formatter_info() { - return array( - // The 'Default' formatter is different for integer fields on the one hand, - // and for decimal and float fields on the other hand, in order to be able - // to use different default values for the settings. - 'number_integer' => array( - 'label' => t('Default'), - 'field types' => array('number_integer'), - 'settings' => array( - 'thousand_separator' => '', - // The 'decimal_separator' and 'scale' settings are not configurable - // through the UI, and will therefore keep their default values. They - // are only present so that the 'number_integer' and 'number_decimal' - // formatters can use the same code. - 'decimal_separator' => '.', - 'scale' => 0, - 'prefix_suffix' => TRUE, - ), - ), - 'number_decimal' => array( - 'label' => t('Default'), - 'field types' => array('number_decimal', 'number_float'), - 'settings' => array( - 'thousand_separator' => '', - 'decimal_separator' => '.', - 'scale' => 2, - 'prefix_suffix' => TRUE, - ), - ), - 'number_unformatted' => array( - 'label' => t('Unformatted'), - 'field types' => array('number_integer', 'number_decimal', 'number_float'), - ), - ); -} - -/** - * Implements hook_field_formatter_settings_form(). - */ -function number_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) { - $display = $instance['display'][$view_mode]; - $settings = $display['settings']; - - if ($display['type'] == 'number_decimal' || $display['type'] == 'number_integer') { - $options = array( - '' => t('<none>'), - '.' => t('Decimal point'), - ',' => t('Comma'), - ' ' => t('Space'), - ); - $element['thousand_separator'] = array( - '#type' => 'select', - '#title' => t('Thousand marker'), - '#options' => $options, - '#default_value' => $settings['thousand_separator'], - ); - - if ($display['type'] == 'number_decimal') { - $element['decimal_separator'] = array( - '#type' => 'select', - '#title' => t('Decimal marker'), - '#options' => array('.' => t('Decimal point'), ',' => t('Comma')), - '#default_value' => $settings['decimal_separator'], - ); - $element['scale'] = array( - '#type' => 'select', - '#title' => t('Scale'), - '#options' => drupal_map_assoc(range(0, 10)), - '#default_value' => $settings['scale'], - '#description' => t('The number of digits to the right of the decimal.'), - ); - } - - $element['prefix_suffix'] = array( - '#type' => 'checkbox', - '#title' => t('Display prefix and suffix.'), - '#default_value' => $settings['prefix_suffix'], - ); - } - - return $element; -} - -/** - * Implements hook_field_formatter_settings_summary(). - */ -function number_field_formatter_settings_summary($field, $instance, $view_mode) { - $display = $instance['display'][$view_mode]; - $settings = $display['settings']; - - $summary = array(); - if ($display['type'] == 'number_decimal' || $display['type'] == 'number_integer') { - $summary[] = number_format(1234.1234567890, $settings['scale'], $settings['decimal_separator'], $settings['thousand_separator']); - if ($settings['prefix_suffix']) { - $summary[] = t('Display with prefix and suffix.'); - } - } - - return implode('<br />', $summary); -} - -/** - * Implements hook_field_formatter_view(). - */ -function number_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) { - $element = array(); - $settings = $display['settings']; - - switch ($display['type']) { - case 'number_integer': - case 'number_decimal': - foreach ($items as $delta => $item) { - $output = number_format($item['value'], $settings['scale'], $settings['decimal_separator'], $settings['thousand_separator']); - if ($settings['prefix_suffix']) { - $prefixes = isset($instance['settings']['prefix']) ? array_map('field_filter_xss', explode('|', $instance['settings']['prefix'])) : array(''); - $suffixes = isset($instance['settings']['suffix']) ? array_map('field_filter_xss', explode('|', $instance['settings']['suffix'])) : array(''); - $prefix = (count($prefixes) > 1) ? format_plural($item['value'], $prefixes[0], $prefixes[1]) : $prefixes[0]; - $suffix = (count($suffixes) > 1) ? format_plural($item['value'], $suffixes[0], $suffixes[1]) : $suffixes[0]; - $output = $prefix . $output . $suffix; - } - $element[$delta] = array('#markup' => $output); - } - break; - - case 'number_unformatted': - foreach ($items as $delta => $item) { - $element[$delta] = array('#markup' => $item['value']); - } - break; - } - - return $element; -} - -/** - * Implements hook_field_widget_info(). - */ -function number_field_widget_info() { - return array( - 'number' => array( - 'label' => t('Text field'), - 'field types' => array('number_integer', 'number_decimal', 'number_float'), - ), - ); -} - -/** - * Implements hook_field_widget_form(). - */ -function number_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { - $value = isset($items[$delta]['value']) ? $items[$delta]['value'] : ''; - // Substitute the decimal separator. - if ($field['type'] == 'number_decimal' || $field['type'] == 'number_float') { - $value = strtr($value, '.', $field['settings']['decimal_separator']); - } - - $element += array( - '#type' => 'textfield', - '#default_value' => $value, - // 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'] + 4 : 12, - // Allow two extra characters for signed values and decimal separator. - '#maxlength' => $field['type'] == 'number_decimal' ? $field['settings']['precision'] + 2 : 10, - // Extract the number type from the field type name for easier validation. - '#number_type' => str_replace('number_', '', $field['type']), - ); - - // Add prefix and suffix. - if (!empty($instance['settings']['prefix'])) { - $prefixes = explode('|', $instance['settings']['prefix']); - $element['#field_prefix'] = field_filter_xss(array_pop($prefixes)); - } - if (!empty($instance['settings']['suffix'])) { - $suffixes = explode('|', $instance['settings']['suffix']); - $element['#field_suffix'] = field_filter_xss(array_pop($suffixes)); - } - - $element['#element_validate'][] = 'number_field_widget_validate'; - - return array('value' => $element); -} - -/** - * FAPI validation of an individual number element. - */ -function number_field_widget_validate($element, &$form_state) { - $field = field_widget_field($element, $form_state); - $instance = field_widget_instance($element, $form_state); - - $type = $element['#number_type']; - $value = $element['#value']; - - // Reject invalid characters. - if (!empty($value)) { - switch ($type) { - case 'float': - case 'decimal': - $regexp = '@[^-0-9\\' . $field['settings']['decimal_separator'] . ']@'; - $message = t('Only numbers and the decimal separator (@separator) allowed in %field.', array('%field' => $instance['label'], '@separator' => $field['settings']['decimal_separator'])); - break; - - case 'integer': - $regexp = '@[^-0-9]@'; - $message = t('Only numbers are allowed in %field.', array('%field' => $instance['label'])); - break; - } - if ($value != preg_replace($regexp, '', $value)) { - form_error($element, $message); - } - else { - if ($type == 'decimal' || $type == 'float') { - // Verify that only one decimal separator exists in the field. - if (substr_count($value, $field['settings']['decimal_separator']) > 1) { - $message = t('%field: There should only be one decimal separator (@separator).', - array( - '%field' => t($instance['label']), - '@separator' => $field['settings']['decimal_separator'], - ) - ); - form_error($element, $message); - } - else { - // Substitute the decimal separator; things should be fine. - $value = strtr($value, $field['settings']['decimal_separator'], '.'); - } - } - form_set_value($element, $value, $form_state); - } - } -} - -/** - * Implements hook_field_widget_error(). - */ -function number_field_widget_error($element, $error, $form, &$form_state) { - form_error($element['value'], $error['message']); -} diff --git a/modules/field/modules/number/number.test b/modules/field/modules/number/number.test deleted file mode 100644 index e96be42a70e..00000000000 --- a/modules/field/modules/number/number.test +++ /dev/null @@ -1,133 +0,0 @@ -<?php - -/** - * @file - * Tests for number.module. - */ - -/** - * Tests for number field types. - */ -class NumberFieldTestCase extends DrupalWebTestCase { - protected $field; - protected $instance; - protected $web_user; - - public static function getInfo() { - return array( - 'name' => 'Number field', - 'description' => 'Test the creation of number fields.', - 'group' => 'Field types' - ); - } - - function setUp() { - parent::setUp('field_test'); - $this->web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content', 'administer content types')); - $this->drupalLogin($this->web_user); - } - - /** - * Test number_decimal field. - */ - function testNumberDecimalField() { - // Create a field with settings to validate. - $this->field = array( - 'field_name' => drupal_strtolower($this->randomName()), - 'type' => 'number_decimal', - 'settings' => array( - 'precision' => 8, 'scale' => 4, 'decimal_separator' => '.', - ) - ); - field_create_field($this->field); - $this->instance = array( - 'field_name' => $this->field['field_name'], - 'entity_type' => 'test_entity', - 'bundle' => 'test_bundle', - 'widget' => array( - 'type' => 'number', - ), - 'display' => array( - 'default' => array( - 'type' => 'number_decimal', - ), - ), - ); - field_create_instance($this->instance); - - // Display creation form. - $this->drupalGet('test-entity/add/test-bundle'); - $langcode = LANGUAGE_NONE; - $this->assertFieldByName("{$this->field['field_name']}[$langcode][0][value]", '', t('Widget is displayed')); - - // Submit a signed decimal value within the allowed precision and scale. - $value = '-1234.5678'; - $edit = array( - "{$this->field['field_name']}[$langcode][0][value]" => $value, - ); - $this->drupalPost(NULL, $edit, t('Save')); - preg_match('|test-entity/manage/(\d+)/edit|', $this->url, $match); - $id = $match[1]; - $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), t('Entity was created')); - $this->assertRaw(round($value, 2), t('Value is displayed.')); - - // Try to create entries with more than one decimal separator; assert fail. - $wrong_entries = array( - '3.14.159', - '0..45469', - '..4589', - '6.459.52', - '6.3..25', - ); - - foreach ($wrong_entries as $wrong_entry) { - $this->drupalGet('test-entity/add/test-bundle'); - $edit = array( - "{$this->field['field_name']}[$langcode][0][value]" => $wrong_entry, - ); - $this->drupalPost(NULL, $edit, t('Save')); - $this->assertText( - t('There should only be one decimal separator (@separator)', - array('@separator' => $this->field['settings']['decimal_separator'])), - t('Correctly failed to save decimal value with more than one decimal point.') - ); - } - } - - /** - * Test number_integer field. - */ - function testNumberIntegerField() { - // Display the "Add content type" form. - $this->drupalGet('admin/structure/types/add'); - - // Add a content type. - $name = $this->randomName(); - $type = drupal_strtolower($name); - $edit = array('name' => $name, 'type' => $type); - $this->drupalPost(NULL, $edit, t('Save and add fields')); - - // Add an integer field to the newly-created type. - $label = $this->randomName(); - $field_name = drupal_strtolower($label); - $edit = array( - 'fields[_add_new_field][label]'=> $label, - 'fields[_add_new_field][field_name]' => $field_name, - 'fields[_add_new_field][type]' => 'number_integer', - 'fields[_add_new_field][widget_type]' => 'number', - ); - $this->drupalPost(NULL, $edit, t('Save')); - - // Set the formatter to "number_integer" and to "unformatted", and just - // check that the settings summary does not generate warnings. - $this->drupalGet("admin/structure/types/manage/$type/display"); - $edit = array( - "fields[field_$field_name][type]" => 'number_integer', - ); - $this->drupalPost(NULL, $edit, t('Save')); - $edit = array( - "fields[field_$field_name][type]" => 'number_unformatted', - ); - $this->drupalPost(NULL, $edit, t('Save')); - } -} diff --git a/modules/field/modules/options/options.api.php b/modules/field/modules/options/options.api.php deleted file mode 100644 index d1ac0db1e4e..00000000000 --- a/modules/field/modules/options/options.api.php +++ /dev/null @@ -1,68 +0,0 @@ -<?php - -/** - * @file - * Hooks provided by the Options module. - */ - -/** - * Returns the list of options to be displayed for a field. - * - * Field types willing to enable one or several of the widgets defined in - * options.module (select, radios/checkboxes, on/off checkbox) need to - * implement this hook to specify the list of options to display in the - * widgets. - * - * @param $field - * The field definition. - * @param $instance - * The instance definition. It is recommended to only use instance level - * properties to filter out values from a list defined by field level - * properties. - * - * @return - * The array of options for the field. Array keys are the values to be - * stored, and should be of the data type (string, number...) expected by - * the first 'column' for the field type. Array values are the labels to - * display within the widgets. The labels should NOT be sanitized, - * options.module takes care of sanitation according to the needs of each - * widget. The HTML tags defined in _field_filter_xss_allowed_tags() are - * allowed, other tags will be filtered. - */ -function hook_options_list($field, $instance) { - // Sample structure. - $options = array( - 0 => t('Zero'), - 1 => t('One'), - 2 => t('Two'), - 3 => t('Three'), - ); - - // Sample structure with groups. Only one level of nesting is allowed. This - // is only supported by the 'options_select' widget. Other widgets will - // flatten the array. - $options = array( - t('First group') => array( - 0 => t('Zero'), - ), - t('Second group') => array( - 1 => t('One'), - 2 => t('Two'), - ), - 3 => t('Three'), - ); - - // In actual implementations, the array of options will most probably depend - // on properties of the field. Example from taxonomy.module: - $options = array(); - foreach ($field['settings']['allowed_values'] as $tree) { - $terms = taxonomy_get_tree($tree['vid'], $tree['parent']); - if ($terms) { - foreach ($terms as $term) { - $options[$term->tid] = str_repeat('-', $term->depth) . $term->name; - } - } - } - - return $options; -} diff --git a/modules/field/modules/options/options.info b/modules/field/modules/options/options.info deleted file mode 100644 index 1cc6faf814f..00000000000 --- a/modules/field/modules/options/options.info +++ /dev/null @@ -1,7 +0,0 @@ -name = Options -description = Defines selection, check box and radio button widgets for text and numeric fields. -package = Core -version = VERSION -core = 8.x -dependencies[] = field -files[] = options.test diff --git a/modules/field/modules/options/options.module b/modules/field/modules/options/options.module deleted file mode 100644 index d4d05eca294..00000000000 --- a/modules/field/modules/options/options.module +++ /dev/null @@ -1,406 +0,0 @@ -<?php - -/** - * @file - * Defines selection, check box and radio button widgets for text and numeric fields. - */ - -/** - * Implements hook_help(). - */ -function options_help($path, $arg) { - switch ($path) { - case 'admin/help#options': - $output = ''; - $output .= '<h3>' . t('About') . '</h3>'; - $output .= '<p>' . t('The Options module defines checkbox, selection, and other input widgets for the Field module. See the <a href="@field-help">Field module help page</a> for more information about fields.', array('@field-help' => url('admin/help/field'))) . '</p>'; - return $output; - } -} - -/** - * Implements hook_theme(). - */ -function options_theme() { - return array( - 'options_none' => array( - 'variables' => array('instance' => NULL, 'option' => NULL), - ), - ); -} - -/** - * Implements hook_field_widget_info(). - * - * Field type modules willing to use those widgets should: - * - Use hook_field_widget_info_alter() to append their field own types to the - * list of types supported by the widgets, - * - Implement hook_options_list() to provide the list of options. - * See list.module. - */ -function options_field_widget_info() { - return array( - 'options_select' => array( - 'label' => t('Select list'), - 'field types' => array(), - 'behaviors' => array( - 'multiple values' => FIELD_BEHAVIOR_CUSTOM, - ), - ), - 'options_buttons' => array( - 'label' => t('Check boxes/radio buttons'), - 'field types' => array(), - 'behaviors' => array( - 'multiple values' => FIELD_BEHAVIOR_CUSTOM, - ), - ), - 'options_onoff' => array( - 'label' => t('Single on/off checkbox'), - 'field types' => array(), - 'behaviors' => array( - 'multiple values' => FIELD_BEHAVIOR_CUSTOM, - ), - 'settings' => array('display_label' => 0), - ), - ); -} - -/** - * Implements hook_field_widget_form(). - */ -function options_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { - // Abstract over the actual field columns, to allow different field types to - // reuse those widgets. - $value_key = key($field['columns']); - - $type = str_replace('options_', '', $instance['widget']['type']); - $multiple = $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED; - $required = $element['#required']; - $has_value = isset($items[0][$value_key]); - $properties = _options_properties($type, $multiple, $required, $has_value); - - // Prepare the list of options. - $options = _options_get_options($field, $instance, $properties); - - // Put current field values in shape. - $default_value = _options_storage_to_form($items, $options, $value_key, $properties); - - switch ($type) { - case 'select': - $element += array( - '#type' => 'select', - '#default_value' => $default_value, - // Do not display a 'multiple' select box if there is only one option. - '#multiple' => $multiple && count($options) > 1, - '#options' => $options, - ); - break; - - case 'buttons': - // If required and there is one single option, preselect it. - if ($required && count($options) == 1) { - reset($options); - $default_value = array(key($options)); - } - $element += array( - '#type' => $multiple ? 'checkboxes' : 'radios', - // Radio buttons need a scalar value. - '#default_value' => $multiple ? $default_value : reset($default_value), - '#options' => $options, - ); - break; - - case 'onoff': - $keys = array_keys($options); - $off_value = array_shift($keys); - $on_value = array_shift($keys); - $element += array( - '#type' => 'checkbox', - '#default_value' => (isset($default_value[0]) && $default_value[0] == $on_value) ? 1 : 0, - '#on_value' => $on_value, - '#off_value' => $off_value, - ); - // Override the title from the incoming $element. - $element['#title'] = isset($options[$on_value]) ? $options[$on_value] : ''; - - if ($instance['widget']['settings']['display_label']) { - $element['#title'] = $instance['label']; - } - break; - } - - $element += array( - '#value_key' => $value_key, - '#element_validate' => array('options_field_widget_validate'), - '#properties' => $properties, - ); - - return $element; -} - -/** - * Implements hook_field_widget_settings_form(). - */ -function options_field_widget_settings_form($field, $instance) { - $form = array(); - if ($instance['widget']['type'] == 'options_onoff') { - $form['display_label'] = array( - '#type' => 'checkbox', - '#title' => t('Use field label instead of the "On value" as label'), - '#default_value' => $instance['widget']['settings']['display_label'], - '#weight' => -1, - ); - } - return $form; -} - -/** - * Form element validation handler for options element. - */ -function options_field_widget_validate($element, &$form_state) { - if ($element['#required'] && $element['#value'] == '_none') { - form_error($element, t('!name field is required.', array('!name' => $element['#title']))); - } - // Transpose selections from field => delta to delta => field, turning - // multiple selected options into multiple parent elements. - $items = _options_form_to_storage($element); - form_set_value($element, $items, $form_state); -} - -/** - * Describes the preparation steps required by each widget. - */ -function _options_properties($type, $multiple, $required, $has_value) { - $base = array( - 'filter_xss' => FALSE, - 'strip_tags' => FALSE, - 'empty_option' => FALSE, - 'optgroups' => FALSE, - ); - - $properties = array(); - - switch ($type) { - case 'select': - $properties = array( - // Select boxes do not support any HTML tag. - 'strip_tags' => TRUE, - 'optgroups' => TRUE, - ); - if ($multiple) { - // Multiple select: add a 'none' option for non-required fields. - if (!$required) { - $properties['empty_option'] = 'option_none'; - } - } - else { - // Single select: add a 'none' option for non-required fields, - // and a 'select a value' option for required fields that do not come - // with a value selected. - if (!$required) { - $properties['empty_option'] = 'option_none'; - } - else if (!$has_value) { - $properties['empty_option'] = 'option_select'; - } - } - break; - - case 'buttons': - $properties = array( - 'filter_xss' => TRUE, - ); - // Add a 'none' option for non-required radio buttons. - if (!$required && !$multiple) { - $properties['empty_option'] = 'option_none'; - } - break; - - case 'onoff': - $properties = array( - 'filter_xss' => TRUE, - ); - break; - } - - return $properties + $base; -} - -/** - * Collects the options for a field. - */ -function _options_get_options($field, $instance, $properties) { - // Get the list of options. - $options = (array) module_invoke($field['module'], 'options_list', $field, $instance); - - // Sanitize the options. - _options_prepare_options($options, $properties); - - if (!$properties['optgroups']) { - $options = options_array_flatten($options); - } - - if ($properties['empty_option']) { - $label = theme('options_none', array('instance' => $instance, 'option' => $properties['empty_option'])); - $options = array('_none' => $label) + $options; - } - - return $options; -} - -/** - * Sanitizes the options. - * - * The function is recursive to support optgroups. - */ -function _options_prepare_options(&$options, $properties) { - foreach ($options as $value => $label) { - // Recurse for optgroups. - if (is_array($label)) { - _options_prepare_options($options[$value], $properties); - } - else { - if ($properties['strip_tags']) { - $options[$value] = strip_tags($label); - } - if ($properties['filter_xss']) { - $options[$value] = field_filter_xss($label); - } - } - } -} - -/** - * Transforms stored field values into the format the widgets need. - */ -function _options_storage_to_form($items, $options, $column, $properties) { - $items_transposed = options_array_transpose($items); - $values = (isset($items_transposed[$column]) && is_array($items_transposed[$column])) ? $items_transposed[$column] : array(); - - // Discard values that are not in the current list of options. Flatten the - // array if needed. - if ($properties['optgroups']) { - $options = options_array_flatten($options); - } - $values = array_values(array_intersect($values, array_keys($options))); - return $values; -} - -/** - * Transforms submitted form values into field storage format. - */ -function _options_form_to_storage($element) { - $values = array_values((array) $element['#value']); - $properties = $element['#properties']; - - // On/off checkbox: transform '0 / 1' into the 'on / off' values. - if ($element['#type'] == 'checkbox') { - $values = array($values[0] ? $element['#on_value'] : $element['#off_value']); - } - - // Filter out the 'none' option. Use a strict comparison, because - // 0 == 'any string'. - if ($properties['empty_option']) { - $index = array_search('_none', $values, TRUE); - if ($index !== FALSE) { - unset($values[$index]); - } - } - - // Make sure we populate at least an empty value. - if (empty($values)) { - $values = array(NULL); - } - - $result = options_array_transpose(array($element['#value_key'] => $values)); - return $result; -} - -/** - * Manipulates 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_array_transpose($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; -} - -/** - * Flattens an array of allowed values. - * - * @param $array - * A single or multidimensional array. - * @return - * A flattened array. - */ -function options_array_flatten($array) { - $result = array(); - if (is_array($array)) { - foreach ($array as $key => $value) { - if (is_array($value)) { - $result += options_array_flatten($value); - } - else { - $result[$key] = $value; - } - } - } - return $result; -} - -/** - * Implements hook_field_widget_error(). - */ -function options_field_widget_error($element, $error, $form, &$form_state) { - form_error($element, $error['message']); -} - -/** - * Returns HTML for the label for the empty value for options that are not required. - * - * The default theme will display N/A for a radio list and '- None -' for a select. - * - * @param $variables - * An associative array containing: - * - instance: An array representing the widget requesting the options. - * - * @ingroup themeable - */ -function theme_options_none($variables) { - $instance = $variables['instance']; - $option = $variables['option']; - - $output = ''; - switch ($instance['widget']['type']) { - case 'options_buttons': - $output = t('N/A'); - break; - - case 'options_select': - $output = ($option == 'option_none' ? t('- None -') : t('- Select a value -')); - break; - } - - return $output; -} diff --git a/modules/field/modules/options/options.test b/modules/field/modules/options/options.test deleted file mode 100644 index ea58f27ff2e..00000000000 --- a/modules/field/modules/options/options.test +++ /dev/null @@ -1,518 +0,0 @@ -<?php - -/** - * @file - * Tests for options.module. - */ - -class OptionsWidgetsTestCase extends FieldTestCase { - public static function getInfo() { - return array( - 'name' => 'Options widgets', - 'description' => "Test the Options widgets.", - 'group' => 'Field types' - ); - } - - function setUp() { - parent::setUp('field_test', 'list_test'); - - // Field with cardinality 1. - $this->card_1 = array( - 'field_name' => 'card_1', - 'type' => 'list_integer', - 'cardinality' => 1, - 'settings' => array( - // Make sure that 0 works as an option. - 'allowed_values' => array(0 => 'Zero', 1 => 'One', 2 => 'Some <script>dangerous</script> & unescaped <strong>markup</strong>'), - ), - ); - $this->card_1 = field_create_field($this->card_1); - - // Field with cardinality 2. - $this->card_2 = array( - 'field_name' => 'card_2', - 'type' => 'list_integer', - 'cardinality' => 2, - 'settings' => array( - // Make sure that 0 works as an option. - 'allowed_values' => array(0 => 'Zero', 1 => 'One', 2 => 'Some <script>dangerous</script> & unescaped <strong>markup</strong>'), - ), - ); - $this->card_2 = field_create_field($this->card_2); - - // Boolean field. - $this->bool = array( - 'field_name' => 'bool', - 'type' => 'list_boolean', - 'cardinality' => 1, - 'settings' => array( - // Make sure that 0 works as a 'on' value'. - 'allowed_values' => array(1 => 'Zero', 0 => 'Some <script>dangerous</script> & unescaped <strong>markup</strong>'), - ), - ); - $this->bool = field_create_field($this->bool); - - // Create a web user. - $this->web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content')); - $this->drupalLogin($this->web_user); - } - - /** - * Tests the 'options_buttons' widget (single select). - */ - function testRadioButtons() { - // Create an instance of the 'single value' field. - $instance = array( - 'field_name' => $this->card_1['field_name'], - 'entity_type' => 'test_entity', - 'bundle' => 'test_bundle', - 'widget' => array( - 'type' => 'options_buttons', - ), - ); - $instance = field_create_instance($instance); - $langcode = LANGUAGE_NONE; - - // Create an entity. - $entity_init = field_test_create_stub_entity(); - $entity = clone $entity_init; - $entity->is_new = TRUE; - field_test_entity_save($entity); - - // With no field data, no buttons are checked. - $this->drupalGet('test-entity/manage/' . $entity->ftid . '/edit'); - $this->assertNoFieldChecked("edit-card-1-$langcode-0"); - $this->assertNoFieldChecked("edit-card-1-$langcode-1"); - $this->assertNoFieldChecked("edit-card-1-$langcode-2"); - $this->assertRaw('Some dangerous & unescaped <strong>markup</strong>', t('Option text was properly filtered.')); - - // Select first option. - $edit = array("card_1[$langcode]" => 0); - $this->drupalPost(NULL, $edit, t('Save')); - $this->assertFieldValues($entity_init, 'card_1', $langcode, array(0)); - - // Check that the selected button is checked. - $this->drupalGet('test-entity/manage/' . $entity->ftid . '/edit'); - $this->assertFieldChecked("edit-card-1-$langcode-0"); - $this->assertNoFieldChecked("edit-card-1-$langcode-1"); - $this->assertNoFieldChecked("edit-card-1-$langcode-2"); - - // Unselect option. - $edit = array("card_1[$langcode]" => '_none'); - $this->drupalPost(NULL, $edit, t('Save')); - $this->assertFieldValues($entity_init, 'card_1', $langcode, array()); - - // Check that required radios with one option is auto-selected. - $this->card_1['settings']['allowed_values'] = array(99 => 'Only allowed value'); - field_update_field($this->card_1); - $instance['required'] = TRUE; - field_update_instance($instance); - $this->drupalGet('test-entity/manage/' . $entity->ftid . '/edit'); - $this->assertFieldChecked("edit-card-1-$langcode-99"); - } - - /** - * Tests the 'options_buttons' widget (multiple select). - */ - function testCheckBoxes() { - // Create an instance of the 'multiple values' field. - $instance = array( - 'field_name' => $this->card_2['field_name'], - 'entity_type' => 'test_entity', - 'bundle' => 'test_bundle', - 'widget' => array( - 'type' => 'options_buttons', - ), - ); - $instance = field_create_instance($instance); - $langcode = LANGUAGE_NONE; - - // Create an entity. - $entity_init = field_test_create_stub_entity(); - $entity = clone $entity_init; - $entity->is_new = TRUE; - field_test_entity_save($entity); - - // Display form: with no field data, nothing is checked. - $this->drupalGet('test-entity/manage/' . $entity->ftid . '/edit'); - $this->assertNoFieldChecked("edit-card-2-$langcode-0"); - $this->assertNoFieldChecked("edit-card-2-$langcode-1"); - $this->assertNoFieldChecked("edit-card-2-$langcode-2"); - $this->assertRaw('Some dangerous & unescaped <strong>markup</strong>', t('Option text was properly filtered.')); - - // Submit form: select first and third options. - $edit = array( - "card_2[$langcode][0]" => TRUE, - "card_2[$langcode][1]" => FALSE, - "card_2[$langcode][2]" => TRUE, - ); - $this->drupalPost(NULL, $edit, t('Save')); - $this->assertFieldValues($entity_init, 'card_2', $langcode, array(0, 2)); - - // Display form: check that the right options are selected. - $this->drupalGet('test-entity/manage/' . $entity->ftid . '/edit'); - $this->assertFieldChecked("edit-card-2-$langcode-0"); - $this->assertNoFieldChecked("edit-card-2-$langcode-1"); - $this->assertFieldChecked("edit-card-2-$langcode-2"); - - // Submit form: select only first option. - $edit = array( - "card_2[$langcode][0]" => TRUE, - "card_2[$langcode][1]" => FALSE, - "card_2[$langcode][2]" => FALSE, - ); - $this->drupalPost(NULL, $edit, t('Save')); - $this->assertFieldValues($entity_init, 'card_2', $langcode, array(0)); - - // Display form: check that the right options are selected. - $this->drupalGet('test-entity/manage/' . $entity->ftid . '/edit'); - $this->assertFieldChecked("edit-card-2-$langcode-0"); - $this->assertNoFieldChecked("edit-card-2-$langcode-1"); - $this->assertNoFieldChecked("edit-card-2-$langcode-2"); - - // Submit form: select the three options while the field accepts only 2. - $edit = array( - "card_2[$langcode][0]" => TRUE, - "card_2[$langcode][1]" => TRUE, - "card_2[$langcode][2]" => TRUE, - ); - $this->drupalPost(NULL, $edit, t('Save')); - $this->assertText('this field cannot hold more than 2 values', t('Validation error was displayed.')); - - // Submit form: uncheck all options. - $edit = array( - "card_2[$langcode][0]" => FALSE, - "card_2[$langcode][1]" => FALSE, - "card_2[$langcode][2]" => FALSE, - ); - $this->drupalPost(NULL, $edit, t('Save')); - // Check that the value was saved. - $this->assertFieldValues($entity_init, 'card_2', $langcode, array()); - - // Required checkbox with one option is auto-selected. - $this->card_2['settings']['allowed_values'] = array(99 => 'Only allowed value'); - field_update_field($this->card_2); - $instance['required'] = TRUE; - field_update_instance($instance); - $this->drupalGet('test-entity/manage/' . $entity->ftid . '/edit'); - $this->assertFieldChecked("edit-card-2-$langcode-99"); - } - - /** - * Tests the 'options_select' widget (single select). - */ - function testSelectListSingle() { - // Create an instance of the 'single value' field. - $instance = array( - 'field_name' => $this->card_1['field_name'], - 'entity_type' => 'test_entity', - 'bundle' => 'test_bundle', - 'required' => TRUE, - 'widget' => array( - 'type' => 'options_select', - ), - ); - $instance = field_create_instance($instance); - $langcode = LANGUAGE_NONE; - - // Create an entity. - $entity_init = field_test_create_stub_entity(); - $entity = clone $entity_init; - $entity->is_new = TRUE; - field_test_entity_save($entity); - - // Display form. - $this->drupalGet('test-entity/manage/' . $entity->ftid . '/edit'); - // A required field without any value has a "none" option. - $this->assertTrue($this->xpath('//select[@id=:id]//option[@value="_none" and text()=:label]', array(':id' => 'edit-card-1-' . $langcode, ':label' => t('- Select a value -'))), t('A required select list has a "Select a value" choice.')); - - // With no field data, nothing is selected. - $this->assertNoOptionSelected("edit-card-1-$langcode", '_none'); - $this->assertNoOptionSelected("edit-card-1-$langcode", 0); - $this->assertNoOptionSelected("edit-card-1-$langcode", 1); - $this->assertNoOptionSelected("edit-card-1-$langcode", 2); - $this->assertRaw('Some dangerous & unescaped markup', t('Option text was properly filtered.')); - - // Submit form: select invalid 'none' option. - $edit = array("card_1[$langcode]" => '_none'); - $this->drupalPost(NULL, $edit, t('Save')); - $this->assertRaw(t('!title field is required.', array('!title' => $instance['field_name'])), t('Cannot save a required field when selecting "none" from the select list.')); - - // Submit form: select first option. - $edit = array("card_1[$langcode]" => 0); - $this->drupalPost(NULL, $edit, t('Save')); - $this->assertFieldValues($entity_init, 'card_1', $langcode, array(0)); - - // Display form: check that the right options are selected. - $this->drupalGet('test-entity/manage/' . $entity->ftid . '/edit'); - // A required field with a value has no 'none' option. - $this->assertFalse($this->xpath('//select[@id=:id]//option[@value="_none"]', array(':id' => 'edit-card-1-' . $langcode)), t('A required select list with an actual value has no "none" choice.')); - $this->assertOptionSelected("edit-card-1-$langcode", 0); - $this->assertNoOptionSelected("edit-card-1-$langcode", 1); - $this->assertNoOptionSelected("edit-card-1-$langcode", 2); - - // Make the field non required. - $instance['required'] = FALSE; - field_update_instance($instance); - - // Display form. - $this->drupalGet('test-entity/manage/' . $entity->ftid . '/edit'); - // A non-required field has a 'none' option. - $this->assertTrue($this->xpath('//select[@id=:id]//option[@value="_none" and text()=:label]', array(':id' => 'edit-card-1-' . $langcode, ':label' => t('- None -'))), t('A non-required select list has a "None" choice.')); - // Submit form: Unselect the option. - $edit = array("card_1[$langcode]" => '_none'); - $this->drupalPost('test-entity/manage/' . $entity->ftid . '/edit', $edit, t('Save')); - $this->assertFieldValues($entity_init, 'card_1', $langcode, array()); - - // Test optgroups. - - $this->card_1['settings']['allowed_values'] = array(); - $this->card_1['settings']['allowed_values_function'] = 'list_test_allowed_values_callback'; - field_update_field($this->card_1); - - // Display form: with no field data, nothing is selected - $this->drupalGet('test-entity/manage/' . $entity->ftid . '/edit'); - $this->assertNoOptionSelected("edit-card-1-$langcode", 0); - $this->assertNoOptionSelected("edit-card-1-$langcode", 1); - $this->assertNoOptionSelected("edit-card-1-$langcode", 2); - $this->assertRaw('Some dangerous & unescaped markup', t('Option text was properly filtered.')); - $this->assertRaw('Group 1', t('Option groups are displayed.')); - - // Submit form: select first option. - $edit = array("card_1[$langcode]" => 0); - $this->drupalPost(NULL, $edit, t('Save')); - $this->assertFieldValues($entity_init, 'card_1', $langcode, array(0)); - - // Display form: check that the right options are selected. - $this->drupalGet('test-entity/manage/' . $entity->ftid . '/edit'); - $this->assertOptionSelected("edit-card-1-$langcode", 0); - $this->assertNoOptionSelected("edit-card-1-$langcode", 1); - $this->assertNoOptionSelected("edit-card-1-$langcode", 2); - - // Submit form: Unselect the option. - $edit = array("card_1[$langcode]" => '_none'); - $this->drupalPost('test-entity/manage/' . $entity->ftid . '/edit', $edit, t('Save')); - $this->assertFieldValues($entity_init, 'card_1', $langcode, array()); - } - - /** - * Tests the 'options_select' widget (multiple select). - */ - function testSelectListMultiple() { - // Create an instance of the 'multiple values' field. - $instance = array( - 'field_name' => $this->card_2['field_name'], - 'entity_type' => 'test_entity', - 'bundle' => 'test_bundle', - 'widget' => array( - 'type' => 'options_select', - ), - ); - $instance = field_create_instance($instance); - $langcode = LANGUAGE_NONE; - - // Create an entity. - $entity_init = field_test_create_stub_entity(); - $entity = clone $entity_init; - $entity->is_new = TRUE; - field_test_entity_save($entity); - - // Display form: with no field data, nothing is selected. - $this->drupalGet('test-entity/manage/' . $entity->ftid . '/edit'); - $this->assertNoOptionSelected("edit-card-2-$langcode", 0); - $this->assertNoOptionSelected("edit-card-2-$langcode", 1); - $this->assertNoOptionSelected("edit-card-2-$langcode", 2); - $this->assertRaw('Some dangerous & unescaped markup', t('Option text was properly filtered.')); - - // Submit form: select first and third options. - $edit = array("card_2[$langcode][]" => array(0 => 0, 2 => 2)); - $this->drupalPost(NULL, $edit, t('Save')); - $this->assertFieldValues($entity_init, 'card_2', $langcode, array(0, 2)); - - // Display form: check that the right options are selected. - $this->drupalGet('test-entity/manage/' . $entity->ftid . '/edit'); - $this->assertOptionSelected("edit-card-2-$langcode", 0); - $this->assertNoOptionSelected("edit-card-2-$langcode", 1); - $this->assertOptionSelected("edit-card-2-$langcode", 2); - - // Submit form: select only first option. - $edit = array("card_2[$langcode][]" => array(0 => 0)); - $this->drupalPost(NULL, $edit, t('Save')); - $this->assertFieldValues($entity_init, 'card_2', $langcode, array(0)); - - // Display form: check that the right options are selected. - $this->drupalGet('test-entity/manage/' . $entity->ftid . '/edit'); - $this->assertOptionSelected("edit-card-2-$langcode", 0); - $this->assertNoOptionSelected("edit-card-2-$langcode", 1); - $this->assertNoOptionSelected("edit-card-2-$langcode", 2); - - // Submit form: select the three options while the field accepts only 2. - $edit = array("card_2[$langcode][]" => array(0 => 0, 1 => 1, 2 => 2)); - $this->drupalPost(NULL, $edit, t('Save')); - $this->assertText('this field cannot hold more than 2 values', t('Validation error was displayed.')); - - // Submit form: uncheck all options. - $edit = array("card_2[$langcode][]" => array()); - $this->drupalPost(NULL, $edit, t('Save')); - $this->assertFieldValues($entity_init, 'card_2', $langcode, array()); - - // Test the 'None' option. - - // Check that the 'none' option has no efect if actual options are selected - // as well. - $edit = array("card_2[$langcode][]" => array('_none' => '_none', 0 => 0)); - $this->drupalPost('test-entity/manage/' . $entity->ftid . '/edit', $edit, t('Save')); - $this->assertFieldValues($entity_init, 'card_2', $langcode, array(0)); - - // Check that selecting the 'none' option empties the field. - $edit = array("card_2[$langcode][]" => array('_none' => '_none')); - $this->drupalPost('test-entity/manage/' . $entity->ftid . '/edit', $edit, t('Save')); - $this->assertFieldValues($entity_init, 'card_2', $langcode, array()); - - // A required select list does not have an empty key. - $instance['required'] = TRUE; - field_update_instance($instance); - $this->drupalGet('test-entity/manage/' . $entity->ftid . '/edit'); - $this->assertFalse($this->xpath('//select[@id=:id]//option[@value=""]', array(':id' => 'edit-card-2-' . $langcode)), t('A required select list does not have an empty key.')); - - // We do not have to test that a required select list with one option is - // auto-selected because the browser does it for us. - - // Test optgroups. - - // Use a callback function defining optgroups. - $this->card_2['settings']['allowed_values'] = array(); - $this->card_2['settings']['allowed_values_function'] = 'list_test_allowed_values_callback'; - field_update_field($this->card_2); - $instance['required'] = FALSE; - field_update_instance($instance); - - // Display form: with no field data, nothing is selected. - $this->drupalGet('test-entity/manage/' . $entity->ftid . '/edit'); - $this->assertNoOptionSelected("edit-card-2-$langcode", 0); - $this->assertNoOptionSelected("edit-card-2-$langcode", 1); - $this->assertNoOptionSelected("edit-card-2-$langcode", 2); - $this->assertRaw('Some dangerous & unescaped markup', t('Option text was properly filtered.')); - $this->assertRaw('Group 1', t('Option groups are displayed.')); - - // Submit form: select first option. - $edit = array("card_2[$langcode][]" => array(0 => 0)); - $this->drupalPost(NULL, $edit, t('Save')); - $this->assertFieldValues($entity_init, 'card_2', $langcode, array(0)); - - // Display form: check that the right options are selected. - $this->drupalGet('test-entity/manage/' . $entity->ftid . '/edit'); - $this->assertOptionSelected("edit-card-2-$langcode", 0); - $this->assertNoOptionSelected("edit-card-2-$langcode", 1); - $this->assertNoOptionSelected("edit-card-2-$langcode", 2); - - // Submit form: Unselect the option. - $edit = array("card_2[$langcode][]" => array('_none' => '_none')); - $this->drupalPost('test-entity/manage/' . $entity->ftid . '/edit', $edit, t('Save')); - $this->assertFieldValues($entity_init, 'card_2', $langcode, array()); - } - - /** - * Tests the 'options_onoff' widget. - */ - function testOnOffCheckbox() { - // Create an instance of the 'boolean' field. - $instance = array( - 'field_name' => $this->bool['field_name'], - 'entity_type' => 'test_entity', - 'bundle' => 'test_bundle', - 'widget' => array( - 'type' => 'options_onoff', - ), - ); - $instance = field_create_instance($instance); - $langcode = LANGUAGE_NONE; - - // Create an entity. - $entity_init = field_test_create_stub_entity(); - $entity = clone $entity_init; - $entity->is_new = TRUE; - field_test_entity_save($entity); - - // Display form: with no field data, option is unchecked. - $this->drupalGet('test-entity/manage/' . $entity->ftid . '/edit'); - $this->assertNoFieldChecked("edit-bool-$langcode"); - $this->assertRaw('Some dangerous & unescaped <strong>markup</strong>', t('Option text was properly filtered.')); - - // Submit form: check the option. - $edit = array("bool[$langcode]" => TRUE); - $this->drupalPost(NULL, $edit, t('Save')); - $this->assertFieldValues($entity_init, 'bool', $langcode, array(0)); - - // Display form: check that the right options are selected. - $this->drupalGet('test-entity/manage/' . $entity->ftid . '/edit'); - $this->assertFieldChecked("edit-bool-$langcode"); - - // Submit form: uncheck the option. - $edit = array("bool[$langcode]" => FALSE); - $this->drupalPost(NULL, $edit, t('Save')); - $this->assertFieldValues($entity_init, 'bool', $langcode, array(1)); - - // Display form: with 'off' value, option is unchecked. - $this->drupalGet('test-entity/manage/' . $entity->ftid . '/edit'); - $this->assertNoFieldChecked("edit-bool-$langcode"); - - // Create admin user. - $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer taxonomy')); - $this->drupalLogin($admin_user); - - // Create a test field instance. - $fieldUpdate = $this->bool; - $fieldUpdate['settings']['allowed_values'] = array(0 => 0, 1 => 'MyOnValue'); - field_update_field($fieldUpdate); - $instance = array( - 'field_name' => $this->bool['field_name'], - 'entity_type' => 'node', - 'bundle' => 'page', - 'widget' => array( - 'type' => 'options_onoff', - 'module' => 'options', - ), - ); - field_create_instance($instance); - - // Go to the edit page and check if the default settings works as expected - $fieldEditUrl = 'admin/structure/types/manage/page/fields/bool'; - $this->drupalGet($fieldEditUrl); - - $this->assertText( - 'Use field label instead of the "On value" as label ', - t('Display setting checkbox available.') - ); - - $this->assertFieldByXPath( - '*//label[@for="edit-' . $this->bool['field_name'] . '-und" and text()="MyOnValue "]', - TRUE, - t('Default case shows "On value"') - ); - - // Enable setting - $edit = array('instance[widget][settings][display_label]' => 1); - // Save the new Settings - $this->drupalPost($fieldEditUrl, $edit, t('Save settings')); - - // Go again to the edit page and check if the setting - // is stored and has the expected effect - $this->drupalGet($fieldEditUrl); - $this->assertText( - 'Use field label instead of the "On value" as label ', - t('Display setting checkbox is available') - ); - $this->assertFieldChecked( - 'edit-instance-widget-settings-display-label', - t('Display settings checkbox checked') - ); - $this->assertFieldByXPath( - '*//label[@for="edit-' . $this->bool['field_name'] . '-und" and text()="' . $this->bool['field_name'] . ' "]', - TRUE, - t('Display label changes label of the checkbox') - ); - } -} - diff --git a/modules/field/modules/text/text.info b/modules/field/modules/text/text.info deleted file mode 100644 index b424d2d452f..00000000000 --- a/modules/field/modules/text/text.info +++ /dev/null @@ -1,8 +0,0 @@ -name = Text -description = Defines simple text field types. -package = Core -version = VERSION -core = 8.x -dependencies[] = field -files[] = text.test -required = TRUE diff --git a/modules/field/modules/text/text.install b/modules/field/modules/text/text.install deleted file mode 100644 index 1b8d00b1e6e..00000000000 --- a/modules/field/modules/text/text.install +++ /dev/null @@ -1,67 +0,0 @@ -<?php - -/** - * @file - * Install, update and uninstall functions for the text module. - */ - -/** - * Implements hook_field_schema(). - */ -function text_field_schema($field) { - switch ($field['type']) { - case 'text': - $columns = array( - 'value' => array( - 'type' => 'varchar', - 'length' => $field['settings']['max_length'], - 'not null' => FALSE, - ), - ); - break; - - case 'text_long': - $columns = array( - 'value' => array( - 'type' => 'text', - 'size' => 'big', - 'not null' => FALSE, - ), - ); - break; - - case 'text_with_summary': - $columns = array( - 'value' => array( - 'type' => 'text', - 'size' => 'big', - 'not null' => FALSE, - ), - 'summary' => array( - 'type' => 'text', - 'size' => 'big', - 'not null' => FALSE, - ), - ); - break; - } - $columns += array( - 'format' => array( - 'type' => 'varchar', - 'length' => 255, - 'not null' => FALSE, - ), - ); - return array( - 'columns' => $columns, - 'indexes' => array( - 'format' => array('format'), - ), - 'foreign keys' => array( - 'format' => array( - 'table' => 'filter_format', - 'columns' => array('format' => 'format'), - ), - ), - ); -} diff --git a/modules/field/modules/text/text.js b/modules/field/modules/text/text.js deleted file mode 100644 index f3ae89430cd..00000000000 --- a/modules/field/modules/text/text.js +++ /dev/null @@ -1,49 +0,0 @@ - -(function ($) { - -/** - * Auto-hide summary textarea if empty and show hide and unhide links. - */ -Drupal.behaviors.textSummary = { - attach: function (context, settings) { - $('.text-summary', context).once('text-summary', function () { - var $widget = $(this).closest('div.field-type-text-with-summary'); - var $summaries = $widget.find('div.text-summary-wrapper'); - - $summaries.once('text-summary-wrapper').each(function(index) { - var $summary = $(this); - var $summaryLabel = $summary.find('label'); - var $full = $widget.find('.text-full').eq(index).closest('.form-item'); - var $fullLabel = $full.find('label'); - - // Create a placeholder label when the field cardinality is - // unlimited or greater than 1. - if ($fullLabel.length == 0) { - $fullLabel = $('<label></label>').prependTo($full); - } - - // Setup the edit/hide summary link. - var $link = $('<span class="field-edit-link">(<a class="link-edit-summary" href="#">' + Drupal.t('Hide summary') + '</a>)</span>').toggle( - function () { - $summary.hide(); - $(this).find('a').html(Drupal.t('Edit summary')).end().appendTo($fullLabel); - return false; - }, - function () { - $summary.show(); - $(this).find('a').html(Drupal.t('Hide summary')).end().appendTo($summaryLabel); - return false; - } - ).appendTo($summaryLabel); - - // If no summary is set, hide the summary field. - if ($(this).find('.text-summary').val() == '') { - $link.click(); - } - return; - }); - }); - } -}; - -})(jQuery); diff --git a/modules/field/modules/text/text.module b/modules/field/modules/text/text.module deleted file mode 100644 index d73814faaaf..00000000000 --- a/modules/field/modules/text/text.module +++ /dev/null @@ -1,611 +0,0 @@ -<?php - -/** - * @file - * Defines simple text field types. - */ - -/** - * Implements hook_help(). - */ -function text_help($path, $arg) { - switch ($path) { - case 'admin/help#text': - $output = ''; - $output .= '<h3>' . t('About') . '</h3>'; - $output .= '<p>' . t("The Text module defines various text field types for the Field module. A text field may contain plain text only, or optionally, may use Drupal's <a href='@filter-help'>text filters</a> to securely manage HTML output. Text input fields may be either a single line (text field), multiple lines (text area), or for greater input control, a select box, checkbox, or radio buttons. If desired, the field can be validated, so that it is limited to a set of allowed values. See the <a href='@field-help'>Field module help page</a> for more information about fields.", array('@field-help' => url('admin/help/field'), '@filter-help' => url('admin/help/filter'))) . '</p>'; - return $output; - } -} - -/** - * Implements hook_field_info(). - * - * Field settings: - * - max_length: the maximum length for a varchar field. - * Instance settings: - * - text_processing: whether text input filters should be used. - * - display_summary: whether the summary field should be displayed. - * When empty and not displayed the summary will take its value from the - * trimmed value of the main text field. - */ -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', - ), - 'text_with_summary' => array( - 'label' => t('Long text and summary'), - 'description' => t('This field stores long text in the database along with optional summary text.'), - 'instance_settings' => array('text_processing' => 1, 'display_summary' => 0), - 'default_widget' => 'text_textarea_with_summary', - 'default_formatter' => 'text_default', - ), - ); -} - -/** - * Implements hook_field_settings_form(). - */ -function text_field_settings_form($field, $instance, $has_data) { - $settings = $field['settings']; - - $form = array(); - - if ($field['type'] == 'text') { - $form['max_length'] = array( - '#type' => 'textfield', - '#title' => t('Maximum length'), - '#default_value' => $settings['max_length'], - '#required' => TRUE, - '#description' => t('The maximum length of the field in characters.'), - '#element_validate' => array('element_validate_integer_positive'), - // @todo: If $has_data, add a validate handler that only allows - // max_length to increase. - '#disabled' => $has_data, - ); - } - - return $form; -} - -/** - * Implements hook_field_instance_settings_form(). - */ -function text_field_instance_settings_form($field, $instance) { - $settings = $instance['settings']; - - $form['text_processing'] = array( - '#type' => 'radios', - '#title' => t('Text processing'), - '#default_value' => $settings['text_processing'], - '#options' => array( - t('Plain text'), - t('Filtered text (user selects text format)'), - ), - ); - if ($field['type'] == 'text_with_summary') { - $form['display_summary'] = array( - '#type' => 'checkbox', - '#title' => t('Summary input'), - '#default_value' => $settings['display_summary'], - '#description' => t('This allows authors to input an explicit summary, to be displayed instead of the automatically trimmed text when using the "Summary or trimmed" display type.'), - ); - } - - return $form; -} - -/** - * Implements hook_field_validate(). - * - * Possible error codes: - * - 'text_value_max_length': The value exceeds the maximum length. - * - 'text_summary_max_length': The summary exceeds the maximum length. - */ -function text_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) { - foreach ($items as $delta => $item) { - // @todo Length is counted separately for summary and value, so the maximum - // length can be exceeded very easily. - foreach (array('value', 'summary') as $column) { - if (!empty($item[$column])) { - if (!empty($field['settings']['max_length']) && drupal_strlen($item[$column]) > $field['settings']['max_length']) { - switch ($column) { - case 'value': - $message = t('%name: the text may not be longer than %max characters.', array('%name' => $instance['label'], '%max' => $field['settings']['max_length'])); - break; - - case 'summary': - $message = t('%name: the summary may not be longer than %max characters.', array('%name' => $instance['label'], '%max' => $field['settings']['max_length'])); - break; - } - $errors[$field['field_name']][$langcode][$delta][] = array( - 'error' => "text_{$column}_length", - 'message' => $message, - ); - } - } - } - } -} - -/** - * Implements hook_field_load(). - * - * Where possible, generate the sanitized version of each field early so that - * it is cached in the field cache. This avoids looking up from the filter cache - * separately. - * - * @see text_field_formatter_view() - */ -function text_field_load($entity_type, $entities, $field, $instances, $langcode, &$items) { - foreach ($entities as $id => $entity) { - foreach ($items[$id] as $delta => $item) { - // Only process items with a cacheable format, the rest will be handled - // by formatters if needed. - if (empty($instances[$id]['settings']['text_processing']) || filter_format_allowcache($item['format'])) { - $items[$id][$delta]['safe_value'] = isset($item['value']) ? _text_sanitize($instances[$id], $langcode, $item, 'value') : ''; - if ($field['type'] == 'text_with_summary') { - $items[$id][$delta]['safe_summary'] = isset($item['summary']) ? _text_sanitize($instances[$id], $langcode, $item, 'summary') : ''; - } - } - } - } -} - -/** - * Implements hook_field_is_empty(). - */ -function text_field_is_empty($item, $field) { - if (!isset($item['value']) || $item['value'] === '') { - return !isset($item['summary']) || $item['summary'] === ''; - } - return FALSE; -} - -/** - * Implements hook_field_formatter_info(). - */ -function text_field_formatter_info() { - return array( - 'text_default' => array( - 'label' => t('Default'), - 'field types' => array('text', 'text_long', 'text_with_summary'), - ), - 'text_plain' => array( - 'label' => t('Plain text'), - 'field types' => array('text', 'text_long', 'text_with_summary'), - ), - - // The text_trimmed formatter displays the trimmed version of the - // full element of the field. It is intended to be used with text - // and text_long fields. It also works with text_with_summary - // fields though the text_summary_or_trimmed formatter makes more - // sense for that field type. - 'text_trimmed' => array( - 'label' => t('Trimmed'), - 'field types' => array('text', 'text_long', 'text_with_summary'), - 'settings' => array('trim_length' => 600), - ), - - // The 'summary or trimmed' field formatter for text_with_summary - // fields displays returns the summary element of the field or, if - // the summary is empty, the trimmed version of the full element - // of the field. - 'text_summary_or_trimmed' => array( - 'label' => t('Summary or trimmed'), - 'field types' => array('text_with_summary'), - 'settings' => array('trim_length' => 600), - ), - ); -} - -/** - * Implements hook_field_formatter_settings_form(). - */ -function text_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) { - $display = $instance['display'][$view_mode]; - $settings = $display['settings']; - - $element = array(); - - if (strpos($display['type'], '_trimmed') !== FALSE) { - $element['trim_length'] = array( - '#title' => t('Trim length'), - '#type' => 'textfield', - '#size' => 10, - '#default_value' => $settings['trim_length'], - '#element_validate' => array('element_validate_integer_positive'), - '#required' => TRUE, - ); - } - - return $element; -} - -/** - * Implements hook_field_formatter_settings_summary(). - */ -function text_field_formatter_settings_summary($field, $instance, $view_mode) { - $display = $instance['display'][$view_mode]; - $settings = $display['settings']; - - $summary = ''; - - if (strpos($display['type'], '_trimmed') !== FALSE) { - $summary = t('Trim length') . ': ' . $settings['trim_length']; - } - - return $summary; -} - -/** - * Implements hook_field_formatter_view(). - */ -function text_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) { - $element = array(); - - switch ($display['type']) { - case 'text_default': - case 'text_trimmed': - foreach ($items as $delta => $item) { - $output = _text_sanitize($instance, $langcode, $item, 'value'); - if ($display['type'] == 'text_trimmed') { - $output = text_summary($output, $instance['settings']['text_processing'] ? $item['format'] : NULL, $display['settings']['trim_length']); - } - $element[$delta] = array('#markup' => $output); - } - break; - - case 'text_summary_or_trimmed': - foreach ($items as $delta => $item) { - if (!empty($item['summary'])) { - $output = _text_sanitize($instance, $langcode, $item, 'summary'); - } - else { - $output = _text_sanitize($instance, $langcode, $item, 'value'); - $output = text_summary($output, $instance['settings']['text_processing'] ? $item['format'] : NULL, $display['settings']['trim_length']); - } - $element[$delta] = array('#markup' => $output); - } - break; - - case 'text_plain': - foreach ($items as $delta => $item) { - $element[$delta] = array('#markup' => strip_tags($item['value'])); - } - break; - } - - return $element; -} - -/** - * Sanitizes the 'value' or 'summary' data of a text value. - * - * Depending on whether the field instance uses text processing, data is run - * through check_plain() or check_markup(). - * - * @param $instance - * The instance definition. - * @param $langcode - * The language associated to $item. - * @param $item - * The field value to sanitize. - * @param $column - * The column to sanitize (either 'value' or 'summary'). - * - * @return - * The sanitized string. - */ -function _text_sanitize($instance, $langcode, $item, $column) { - // If the value uses a cacheable text format, text_field_load() precomputes - // the sanitized string. - if (isset($item["safe_$column"])) { - return $item["safe_$column"]; - } - return $instance['settings']['text_processing'] ? check_markup($item[$column], $item['format'], $langcode) : check_plain($item[$column]); -} - -/** - * Generate a trimmed, formatted version of a text field value. - * - * If the end of the summary is not indicated using the <!--break--> delimiter - * then we generate the summary automatically, trying to end it at a sensible - * place such as the end of a paragraph, a line break, or the end of a - * sentence (in that order of preference). - * - * @param $text - * The content for which a summary will be generated. - * @param $format - * The format of the content. - * If the PHP filter is present and $text contains PHP code, we do not - * split it up to prevent parse errors. - * If the line break filter is present then we treat newlines embedded in - * $text as line breaks. - * If the htmlcorrector filter is present, it will be run on the generated - * summary (if different from the incoming $text). - * @param $size - * The desired character length of the summary. If omitted, the default - * value will be used. Ignored if the special delimiter is present - * in $text. - * @return - * The generated summary. - */ -function text_summary($text, $format = NULL, $size = NULL) { - - if (!isset($size)) { - // What used to be called 'teaser' is now called 'summary', but - // the variable 'teaser_length' is preserved for backwards compatibility. - $size = variable_get('teaser_length', 600); - } - - // Find where the delimiter is in the body - $delimiter = strpos($text, '<!--break-->'); - - // If the size is zero, and there is no delimiter, the entire body is the summary. - if ($size == 0 && $delimiter === FALSE) { - return $text; - } - - // If a valid delimiter has been specified, use it to chop off the summary. - if ($delimiter !== FALSE) { - return substr($text, 0, $delimiter); - } - - // We check for the presence of the PHP evaluator filter in the current - // format. If the body contains PHP code, we do not split it up to prevent - // parse errors. - if (isset($format)) { - $filters = filter_list_format($format); - if (isset($filters['php_code']) && $filters['php_code']->status && strpos($text, '<?') !== FALSE) { - return $text; - } - } - - // If we have a short body, the entire body is the summary. - if (drupal_strlen($text) <= $size) { - return $text; - } - - // If the delimiter has not been specified, try to split at paragraph or - // sentence boundaries. - - // The summary may not be longer than maximum length specified. Initial slice. - $summary = truncate_utf8($text, $size); - - // Store the actual length of the UTF8 string -- which might not be the same - // as $size. - $max_rpos = strlen($summary); - - // How much to cut off the end of the summary so that it doesn't end in the - // middle of a paragraph, sentence, or word. - // Initialize it to maximum in order to find the minimum. - $min_rpos = $max_rpos; - - // Store the reverse of the summary. We use strpos on the reversed needle and - // haystack for speed and convenience. - $reversed = strrev($summary); - - // Build an array of arrays of break points grouped by preference. - $break_points = array(); - - // A paragraph near the end of sliced summary is most preferable. - $break_points[] = array('</p>' => 0); - - // If no complete paragraph then treat line breaks as paragraphs. - $line_breaks = array('<br />' => 6, '<br>' => 4); - // Newline only indicates a line break if line break converter - // filter is present. - if (isset($filters['filter_autop'])) { - $line_breaks["\n"] = 1; - } - $break_points[] = $line_breaks; - - // If the first paragraph is too long, split at the end of a sentence. - $break_points[] = array('. ' => 1, '! ' => 1, '? ' => 1, '。' => 0, 'ØŸ ' => 1); - - // Iterate over the groups of break points until a break point is found. - foreach ($break_points as $points) { - // Look for each break point, starting at the end of the summary. - foreach ($points as $point => $offset) { - // The summary is already reversed, but the break point isn't. - $rpos = strpos($reversed, strrev($point)); - if ($rpos !== FALSE) { - $min_rpos = min($rpos + $offset, $min_rpos); - } - } - - // If a break point was found in this group, slice and stop searching. - if ($min_rpos !== $max_rpos) { - // Don't slice with length 0. Length must be <0 to slice from RHS. - $summary = ($min_rpos === 0) ? $summary : substr($summary, 0, 0 - $min_rpos); - break; - } - } - - // If the htmlcorrector filter is present, apply it to the generated summary. - if (isset($filters['filter_htmlcorrector'])) { - $summary = _filter_htmlcorrector($summary); - } - - return $summary; -} - -/** - * Implements hook_field_widget_info(). - */ -function text_field_widget_info() { - return array( - 'text_textfield' => array( - 'label' => t('Text field'), - 'field types' => array('text'), - 'settings' => array('size' => 60), - ), - 'text_textarea' => array( - 'label' => t('Text area (multiple rows)'), - 'field types' => array('text_long'), - 'settings' => array('rows' => 5), - ), - 'text_textarea_with_summary' => array( - 'label' => t('Text area with a summary'), - 'field types' => array('text_with_summary'), - 'settings' => array('rows' => 20, 'summary_rows' => 5), - ), - ); -} - -/** - * Implements hook_field_widget_settings_form(). - */ -function text_field_widget_settings_form($field, $instance) { - $widget = $instance['widget']; - $settings = $widget['settings']; - - if ($widget['type'] == 'text_textfield') { - $form['size'] = array( - '#type' => 'textfield', - '#title' => t('Size of textfield'), - '#default_value' => $settings['size'], - '#required' => TRUE, - '#element_validate' => array('element_validate_integer_positive'), - ); - } - else { - $form['rows'] = array( - '#type' => 'textfield', - '#title' => t('Rows'), - '#default_value' => $settings['rows'], - '#required' => TRUE, - '#element_validate' => array('element_validate_integer_positive'), - ); - } - - return $form; -} - -/** - * Implements hook_field_widget_form(). - */ -function text_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { - $summary_widget = array(); - $main_widget = array(); - - switch ($instance['widget']['type']) { - case 'text_textfield': - $main_widget = $element + array( - '#type' => 'textfield', - '#default_value' => isset($items[$delta]['value']) ? $items[$delta]['value'] : NULL, - '#size' => $instance['widget']['settings']['size'], - '#maxlength' => $field['settings']['max_length'], - '#attributes' => array('class' => array('text-full')), - ); - break; - - case 'text_textarea_with_summary': - $display = !empty($items[$delta]['summary']) || !empty($instance['settings']['display_summary']); - $summary_widget = array( - '#type' => $display ? 'textarea' : 'value', - '#default_value' => isset($items[$delta]['summary']) ? $items[$delta]['summary'] : NULL, - '#title' => t('Summary'), - '#rows' => $instance['widget']['settings']['summary_rows'], - '#description' => t('Leave blank to use trimmed value of full text as the summary.'), - '#attached' => array( - 'js' => array(drupal_get_path('module', 'text') . '/text.js'), - ), - '#attributes' => array('class' => array('text-summary')), - '#prefix' => '<div class="text-summary-wrapper">', - '#suffix' => '</div>', - '#weight' => -10, - ); - // Fall through to the next case. - - case 'text_textarea': - $main_widget = $element + array( - '#type' => 'textarea', - '#default_value' => isset($items[$delta]['value']) ? $items[$delta]['value'] : NULL, - '#rows' => $instance['widget']['settings']['rows'], - '#attributes' => array('class' => array('text-full')), - ); - break; - } - - if ($main_widget) { - // Conditionally alter the form element's type if text processing is enabled. - if ($instance['settings']['text_processing']) { - $element = $main_widget; - $element['#type'] = 'text_format'; - $element['#format'] = isset($items[$delta]['format']) ? $items[$delta]['format'] : NULL; - $element['#base_type'] = $main_widget['#type']; - } - else { - $element['value'] = $main_widget; - } - } - if ($summary_widget) { - $element['summary'] = $summary_widget; - } - - return $element; -} - -/** - * Implements hook_field_widget_error(). - */ -function text_field_widget_error($element, $error, $form, &$form_state) { - switch ($error['error']) { - case 'text_summary_max_length': - $error_element = $element[$element['#columns'][1]]; - break; - - default: - $error_element = $element[$element['#columns'][0]]; - break; - } - - form_error($error_element, $error['message']); -} - -/** - * Implements hook_field_prepare_translation(). - */ -function text_field_prepare_translation($entity_type, $entity, $field, $instance, $langcode, &$items, $source_entity, $source_langcode) { - // If the translating user is not permitted to use the assigned text format, - // we must not expose the source values. - $field_name = $field['field_name']; - if (!empty($source_entity->{$field_name}[$source_langcode])) { - $formats = filter_formats(); - foreach ($source_entity->{$field_name}[$source_langcode] as $delta => $item) { - $format_id = $item['format']; - if (!empty($format_id) && !filter_access($formats[$format_id])) { - unset($items[$delta]); - } - } - } -} - -/** - * Implements hook_filter_format_update(). - */ -function text_filter_format_update($format) { - field_cache_clear(); -} - -/** - * Implements hook_filter_format_disable(). - */ -function text_filter_format_disable($format) { - field_cache_clear(); -} diff --git a/modules/field/modules/text/text.test b/modules/field/modules/text/text.test deleted file mode 100644 index 59369370efc..00000000000 --- a/modules/field/modules/text/text.test +++ /dev/null @@ -1,517 +0,0 @@ -<?php - -/** - * @file - * Tests for text.module. - */ - -class TextFieldTestCase extends DrupalWebTestCase { - protected $instance; - protected $admin_user; - protected $web_user; - - public static function getInfo() { - return array( - 'name' => 'Text field', - 'description' => "Test the creation of text fields.", - 'group' => 'Field types' - ); - } - - function setUp() { - parent::setUp('field_test'); - - $this->admin_user = $this->drupalCreateUser(array('administer filters')); - $this->web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content')); - $this->drupalLogin($this->web_user); - } - - // Test fields. - - /** - * Test text field validation. - */ - function testTextFieldValidation() { - // Create a field with settings to validate. - $max_length = 3; - $this->field = array( - 'field_name' => drupal_strtolower($this->randomName()), - 'type' => 'text', - 'settings' => array( - 'max_length' => $max_length, - ) - ); - field_create_field($this->field); - $this->instance = array( - 'field_name' => $this->field['field_name'], - 'entity_type' => 'test_entity', - 'bundle' => 'test_bundle', - 'widget' => array( - 'type' => 'text_textfield', - ), - 'display' => array( - 'default' => array( - 'type' => 'text_default', - ), - ), - ); - field_create_instance($this->instance); - // Test valid and invalid values with field_attach_validate(). - $entity = field_test_create_stub_entity(); - $langcode = LANGUAGE_NONE; - for ($i = 0; $i <= $max_length + 2; $i++) { - $entity->{$this->field['field_name']}[$langcode][0]['value'] = str_repeat('x', $i); - try { - field_attach_validate('test_entity', $entity); - $this->assertTrue($i <= $max_length, "Length $i does not cause validation error when max_length is $max_length"); - } - catch (FieldValidationException $e) { - $this->assertTrue($i > $max_length, "Length $i causes validation error when max_length is $max_length"); - } - } - } - - /** - * Test widgets. - */ - function testTextfieldWidgets() { - $this->_testTextfieldWidgets('text', 'text_textfield'); - $this->_testTextfieldWidgets('text_long', 'text_textarea'); - } - - /** - * Helper function for testTextfieldWidgets(). - */ - function _testTextfieldWidgets($field_type, $widget_type) { - // Setup a field and instance - $entity_type = 'test_entity'; - $this->field_name = drupal_strtolower($this->randomName()); - $this->field = array('field_name' => $this->field_name, 'type' => $field_type); - field_create_field($this->field); - $this->instance = array( - 'field_name' => $this->field_name, - 'entity_type' => 'test_entity', - 'bundle' => 'test_bundle', - 'label' => $this->randomName() . '_label', - 'settings' => array( - 'text_processing' => TRUE, - ), - 'widget' => array( - 'type' => $widget_type, - ), - 'display' => array( - 'full' => array( - 'type' => 'text_default', - ), - ), - ); - field_create_instance($this->instance); - $langcode = LANGUAGE_NONE; - - // Display creation form. - $this->drupalGet('test-entity/add/test-bundle'); - $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", '', t('Widget is displayed')); - $this->assertNoFieldByName("{$this->field_name}[$langcode][0][format]", '1', t('Format selector is not displayed')); - - // Submit with some value. - $value = $this->randomName(); - $edit = array( - "{$this->field_name}[$langcode][0][value]" => $value, - ); - $this->drupalPost(NULL, $edit, t('Save')); - preg_match('|test-entity/manage/(\d+)/edit|', $this->url, $match); - $id = $match[1]; - $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), t('Entity was created')); - - // Display the entity. - $entity = field_test_entity_test_load($id); - $entity->content = field_attach_view($entity_type, $entity, 'full'); - $this->content = drupal_render($entity->content); - $this->assertText($value, 'Filtered tags are not displayed'); - } - - /** - * Test widgets + 'formatted_text' setting. - */ - function testTextfieldWidgetsFormatted() { - $this->_testTextfieldWidgetsFormatted('text', 'text_textfield'); - $this->_testTextfieldWidgetsFormatted('text_long', 'text_textarea'); - } - - /** - * Helper function for testTextfieldWidgetsFormatted(). - */ - function _testTextfieldWidgetsFormatted($field_type, $widget_type) { - // Setup a field and instance - $entity_type = 'test_entity'; - $this->field_name = drupal_strtolower($this->randomName()); - $this->field = array('field_name' => $this->field_name, 'type' => $field_type); - field_create_field($this->field); - $this->instance = array( - 'field_name' => $this->field_name, - 'entity_type' => 'test_entity', - 'bundle' => 'test_bundle', - 'label' => $this->randomName() . '_label', - 'settings' => array( - 'text_processing' => TRUE, - ), - 'widget' => array( - 'type' => $widget_type, - ), - 'display' => array( - 'full' => array( - 'type' => 'text_default', - ), - ), - ); - field_create_instance($this->instance); - $langcode = LANGUAGE_NONE; - - // Disable all text formats besides the plain text fallback format. - $this->drupalLogin($this->admin_user); - foreach (filter_formats() as $format) { - if ($format->format != filter_fallback_format()) { - $this->drupalPost('admin/config/content/formats/' . $format->format . '/disable', array(), t('Disable')); - } - } - $this->drupalLogin($this->web_user); - - // Display the creation form. Since the user only has access to one format, - // no format selector will be displayed. - $this->drupalGet('test-entity/add/test-bundle'); - $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", '', t('Widget is displayed')); - $this->assertNoFieldByName("{$this->field_name}[$langcode][0][format]", '', t('Format selector is not displayed')); - - // Submit with data that should be filtered. - $value = '<em>' . $this->randomName() . '</em>'; - $edit = array( - "{$this->field_name}[$langcode][0][value]" => $value, - ); - $this->drupalPost(NULL, $edit, t('Save')); - preg_match('|test-entity/manage/(\d+)/edit|', $this->url, $match); - $id = $match[1]; - $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), t('Entity was created')); - - // Display the entity. - $entity = field_test_entity_test_load($id); - $entity->content = field_attach_view($entity_type, $entity, 'full'); - $this->content = drupal_render($entity->content); - $this->assertNoRaw($value, t('HTML tags are not displayed.')); - $this->assertRaw(check_plain($value), t('Escaped HTML is displayed correctly.')); - - // Create a new text format that does not escape HTML, and grant the user - // access to it. - $this->drupalLogin($this->admin_user); - $edit = array( - 'format' => drupal_strtolower($this->randomName()), - 'name' => $this->randomName(), - ); - $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration')); - filter_formats_reset(); - $this->checkPermissions(array(), TRUE); - $format = filter_format_load($edit['format']); - $format_id = $format->format; - $permission = filter_permission_name($format); - $rid = max(array_keys($this->web_user->roles)); - user_role_grant_permissions($rid, array($permission)); - $this->drupalLogin($this->web_user); - - // Display edition form. - // We should now have a 'text format' selector. - $this->drupalGet('test-entity/manage/' . $id . '/edit'); - $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", NULL, t('Widget is displayed')); - $this->assertFieldByName("{$this->field_name}[$langcode][0][format]", NULL, t('Format selector is displayed')); - - // Edit and change the text format to the new one that was created. - $edit = array( - "{$this->field_name}[$langcode][0][format]" => $format_id, - ); - $this->drupalPost(NULL, $edit, t('Save')); - $this->assertRaw(t('test_entity @id has been updated.', array('@id' => $id)), t('Entity was updated')); - - // Display the entity. - $entity = field_test_entity_test_load($id); - $entity->content = field_attach_view($entity_type, $entity, 'full'); - $this->content = drupal_render($entity->content); - $this->assertRaw($value, t('Value is displayed unfiltered')); - } -} - -class TextSummaryTestCase extends DrupalWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Text summary', - 'description' => 'Test text_summary() with different strings and lengths.', - 'group' => 'Field types', - ); - } - - function setUp() { - parent::setUp(); - $this->article_creator = $this->drupalCreateUser(array('create article content', 'edit own article content')); - } - - /** - * Tests an edge case where the first sentence is a question and - * subsequent sentences are not. This edge case is documented at - * http://drupal.org/node/180425. - */ - function testFirstSentenceQuestion() { - $text = 'A question? A sentence. Another sentence.'; - $expected = 'A question? A sentence.'; - $this->callTextSummary($text, $expected, NULL, 30); - } - - /** - * Test summary with long example. - */ - function testLongSentence() { - $text = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ' . // 125 - 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ' . // 108 - 'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. ' . // 103 - 'Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'; // 110 - $expected = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ' . - 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ' . - 'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.'; - // First three sentences add up to: 336, so add one for space and then 3 to get half-way into next word. - $this->callTextSummary($text, $expected, NULL, 340); - } - - /** - * Test various summary length edge cases. - */ - function testLength() { - // This string tests a number of edge cases. - $text = "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>"; - - // The summaries we expect text_summary() to return when $size is the index - // of each array item. - // Using no text format: - $expected = array( - "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>", - "<", - "<p", - "<p>", - "<p>\n", - "<p>\nH", - "<p>\nHi", - "<p>\nHi\n", - "<p>\nHi\n<", - "<p>\nHi\n</", - "<p>\nHi\n</p", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>", - "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>", - "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>", - ); - - // And using a text format WITH the line-break and htmlcorrector filters. - $expected_lb = array( - "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>", - "", - "<p></p>", - "<p></p>", - "<p></p>", - "<p></p>", - "<p></p>", - "<p>\nHi</p>", - "<p>\nHi</p>", - "<p>\nHi</p>", - "<p>\nHi</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>", - "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>", - "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>", - ); - - // Test text_summary() for different sizes. - for ($i = 0; $i <= 37; $i++) { - $this->callTextSummary($text, $expected[$i], NULL, $i); - $this->callTextSummary($text, $expected_lb[$i], 'plain_text', $i); - $this->callTextSummary($text, $expected_lb[$i], 'filtered_html', $i); - } - } - - /** - * Calls text_summary() and asserts that the expected teaser is returned. - */ - function callTextSummary($text, $expected, $format = NULL, $size = NULL) { - $summary = text_summary($text, $format, $size); - $this->assertIdentical($summary, $expected, t('Generated summary "@summary" matches expected "@expected".', array('@summary' => $summary, '@expected' => $expected))); - } - - /** - * Test sending only summary. - */ - function testOnlyTextSummary() { - // Login as article creator. - $this->drupalLogin($this->article_creator); - // Create article with summary but empty body. - $summary = $this->randomName(); - $edit = array( - "title" => $this->randomName(), - "body[und][0][summary]" => $summary, - ); - $this->drupalPost('node/add/article', $edit, t('Save')); - $node = $this->drupalGetNodeByTitle($edit['title']); - - $this->assertIdentical($node->body['und'][0]['summary'], $summary, t('Article with with summary and no body has been submitted.')); - } -} - -class TextTranslationTestCase extends DrupalWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Text translation', - 'description' => 'Check if the text field is correctly prepared for translation.', - 'group' => 'Field types', - ); - } - - function setUp() { - parent::setUp('locale', 'translation'); - - $full_html_format = filter_format_load('full_html'); - $this->format = $full_html_format->format; - $this->admin = $this->drupalCreateUser(array( - 'administer languages', - 'administer content types', - 'access administration pages', - 'bypass node access', - filter_permission_name($full_html_format), - )); - $this->translator = $this->drupalCreateUser(array('create article content', 'edit own article content', 'translate content')); - - // Enable an additional language. - $this->drupalLogin($this->admin); - $edit = array('langcode' => 'fr'); - $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); - - // Set "Article" content type to use multilingual support with translation. - $edit = array('language_content_type' => 2); - $this->drupalPost('admin/structure/types/manage/article', $edit, t('Save content type')); - $this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Article')), t('Article content type has been updated.')); - } - - /** - * Test that a plaintext textfield widget is correctly populated. - */ - function testTextField() { - // Disable text processing for body. - $edit = array('instance[settings][text_processing]' => 0); - $this->drupalPost('admin/structure/types/manage/article/fields/body', $edit, t('Save settings')); - - // Login as translator. - $this->drupalLogin($this->translator); - - // Create content. - $langcode = LANGUAGE_NONE; - $body = $this->randomName(); - $edit = array( - "title" => $this->randomName(), - "language" => 'en', - "body[$langcode][0][value]" => $body, - ); - - // Translate the article in french. - $this->drupalPost('node/add/article', $edit, t('Save')); - $node = $this->drupalGetNodeByTitle($edit['title']); - $this->drupalGet("node/$node->nid/translate"); - $this->clickLink(t('add translation')); - $this->assertFieldByXPath("//textarea[@name='body[$langcode][0][value]']", $body, t('The textfield widget is populated.')); - } - - /** - * Check that user that does not have access the field format cannot see the - * source value when creating a translation. - */ - function testTextFieldFormatted() { - // Make node body multiple. - $edit = array('field[cardinality]' => -1); - $this->drupalPost('admin/structure/types/manage/article/fields/body', $edit, t('Save settings')); - $this->drupalGet('node/add/article'); - $this->assertFieldByXPath("//input[@name='body_add_more']", t('Add another item'), t('Body field cardinality set to multiple.')); - - $body = array( - $this->randomName(), - $this->randomName(), - ); - - // Create an article with the first body input format set to "Full HTML". - $title = $this->randomName(); - $edit = array( - 'title' => $title, - 'language' => 'en', - ); - $this->drupalPost('node/add/article', $edit, t('Save')); - - // Populate the body field: the first item gets the "Full HTML" input - // format, the second one "Filtered HTML". - $formats = array('full_html', 'filtered_html'); - $langcode = LANGUAGE_NONE; - foreach ($body as $delta => $value) { - $edit = array( - "body[$langcode][$delta][value]" => $value, - "body[$langcode][$delta][format]" => array_shift($formats), - ); - $this->drupalPost('node/1/edit', $edit, t('Save')); - $this->assertText($body[$delta], t('The body field with delta @delta has been saved.', array('@delta' => $delta))); - } - - // Login as translator. - $this->drupalLogin($this->translator); - - // Translate the article in french. - $node = $this->drupalGetNodeByTitle($title); - $this->drupalGet("node/$node->nid/translate"); - $this->clickLink(t('add translation')); - $this->assertNoText($body[0], t('The body field with delta @delta is hidden.', array('@delta' => 0))); - $this->assertText($body[1], t('The body field with delta @delta is shown.', array('@delta' => 1))); - } -} diff --git a/modules/field/tests/field.test b/modules/field/tests/field.test deleted file mode 100644 index f4be7d56416..00000000000 --- a/modules/field/tests/field.test +++ /dev/null @@ -1,3257 +0,0 @@ -<?php - -/** - * @file - * Tests for field.module. - */ - -/** - * Parent class for Field API tests. - */ -class FieldTestCase extends DrupalWebTestCase { - var $default_storage = 'field_sql_storage'; - - /** - * Set the default field storage backend for fields created during tests. - */ - function setUp() { - // Since this is a base class for many test cases, support the same - // flexibility that DrupalWebTestCase::setUp() has for the modules to be - // passed in as either an array or a variable number of string arguments. - $modules = func_get_args(); - if (isset($modules[0]) && is_array($modules[0])) { - $modules = $modules[0]; - } - parent::setUp($modules); - // Set default storage backend. - variable_set('field_storage_default', $this->default_storage); - } - - /** - * Generate random values for a field_test field. - * - * @param $cardinality - * Number of values to generate. - * @return - * An array of random values, in the format expected for field values. - */ - function _generateTestFieldValues($cardinality) { - $values = array(); - for ($i = 0; $i < $cardinality; $i++) { - // field_test fields treat 0 as 'empty value'. - $values[$i]['value'] = mt_rand(1, 127); - } - return $values; - } - - /** - * Assert that a field has the expected values in an entity. - * - * This function only checks a single column in the field values. - * - * @param $entity - * The entity to test. - * @param $field_name - * The name of the field to test - * @param $langcode - * The language code for the values. - * @param $expected_values - * The array of expected values. - * @param $column - * (Optional) the name of the column to check. - */ - function assertFieldValues($entity, $field_name, $langcode, $expected_values, $column = 'value') { - $e = clone $entity; - field_attach_load('test_entity', array($e->ftid => $e)); - $values = isset($e->{$field_name}[$langcode]) ? $e->{$field_name}[$langcode] : array(); - $this->assertEqual(count($values), count($expected_values), t('Expected number of values were saved.')); - foreach ($expected_values as $key => $value) { - $this->assertEqual($values[$key][$column], $value, t('Value @value was saved correctly.', array('@value' => $value))); - } - } -} - -class FieldAttachTestCase extends FieldTestCase { - function setUp($modules = array()) { - // Since this is a base class for many test cases, support the same - // flexibility that DrupalWebTestCase::setUp() has for the modules to be - // passed in as either an array or a variable number of string arguments. - if (!is_array($modules)) { - $modules = func_get_args(); - } - if (!in_array('field_test', $modules)) { - $modules[] = 'field_test'; - } - parent::setUp($modules); - - $this->field_name = drupal_strtolower($this->randomName() . '_field_name'); - $this->field = array('field_name' => $this->field_name, 'type' => 'test_field', 'cardinality' => 4); - $this->field = field_create_field($this->field); - $this->field_id = $this->field['id']; - $this->instance = array( - 'field_name' => $this->field_name, - 'entity_type' => 'test_entity', - '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); - } -} - -/** - * Unit test class for storage-related field_attach_* functions. - * - * All field_attach_* test work with all field_storage plugins and - * all hook_field_attach_pre_{load,insert,update}() hooks. - */ -class FieldAttachStorageTestCase extends FieldAttachTestCase { - public static function getInfo() { - return array( - 'name' => 'Field attach tests (storage-related)', - 'description' => 'Test storage-related Field Attach API functions.', - 'group' => 'Field API', - ); - } - - /** - * Check field values insert, update and load. - * - * Works independently of the underlying field storage backend. Inserts or - * updates random field data and then loads and verifies the data. - */ - function testFieldAttachSaveLoad() { - // Configure the instance so that we test hook_field_load() (see - // field_test_field_load() in field_test.module). - $this->instance['settings']['test_hook_field_load'] = TRUE; - field_update_instance($this->instance); - $langcode = LANGUAGE_NONE; - - $entity_type = 'test_entity'; - $values = array(); - - // TODO : test empty values filtering and "compression" (store consecutive deltas). - - // Preparation: create three revisions and store them in $revision array. - for ($revision_id = 0; $revision_id < 3; $revision_id++) { - $revision[$revision_id] = field_test_create_stub_entity(0, $revision_id, $this->instance['bundle']); - // Note: we try to insert one extra value. - $values[$revision_id] = $this->_generateTestFieldValues($this->field['cardinality'] + 1); - $current_revision = $revision_id; - // If this is the first revision do an insert. - if (!$revision_id) { - $revision[$revision_id]->{$this->field_name}[$langcode] = $values[$revision_id]; - field_attach_insert($entity_type, $revision[$revision_id]); - } - else { - // Otherwise do an update. - $revision[$revision_id]->{$this->field_name}[$langcode] = $values[$revision_id]; - field_attach_update($entity_type, $revision[$revision_id]); - } - } - - // Confirm current revision loads the correct data. - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - field_attach_load($entity_type, array(0 => $entity)); - // Number of values per field loaded equals the field cardinality. - $this->assertEqual(count($entity->{$this->field_name}[$langcode]), $this->field['cardinality'], t('Current revision: expected number of values')); - for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { - // The field value loaded matches the one inserted or updated. - $this->assertEqual($entity->{$this->field_name}[$langcode][$delta]['value'] , $values[$current_revision][$delta]['value'], t('Current revision: expected value %delta was found.', array('%delta' => $delta))); - // The value added in hook_field_load() is found. - $this->assertEqual($entity->{$this->field_name}[$langcode][$delta]['additional_key'], 'additional_value', t('Current revision: extra information for value %delta was found', array('%delta' => $delta))); - } - - // Confirm each revision loads the correct data. - foreach (array_keys($revision) as $revision_id) { - $entity = field_test_create_stub_entity(0, $revision_id, $this->instance['bundle']); - field_attach_load_revision($entity_type, array(0 => $entity)); - // Number of values per field loaded equals the field cardinality. - $this->assertEqual(count($entity->{$this->field_name}[$langcode]), $this->field['cardinality'], t('Revision %revision_id: expected number of values.', array('%revision_id' => $revision_id))); - for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { - // The field value loaded matches the one inserted or updated. - $this->assertEqual($entity->{$this->field_name}[$langcode][$delta]['value'], $values[$revision_id][$delta]['value'], t('Revision %revision_id: expected value %delta was found.', array('%revision_id' => $revision_id, '%delta' => $delta))); - // The value added in hook_field_load() is found. - $this->assertEqual($entity->{$this->field_name}[$langcode][$delta]['additional_key'], 'additional_value', t('Revision %revision_id: extra information for value %delta was found', array('%revision_id' => $revision_id, '%delta' => $delta))); - } - } - } - - /** - * Test the 'multiple' load feature. - */ - function testFieldAttachLoadMultiple() { - $entity_type = 'test_entity'; - $langcode = LANGUAGE_NONE; - - // Define 2 bundles. - $bundles = array( - 1 => 'test_bundle_1', - 2 => 'test_bundle_2', - ); - field_test_create_bundle($bundles[1]); - field_test_create_bundle($bundles[2]); - // Define 3 fields: - // - field_1 is in bundle_1 and bundle_2, - // - field_2 is in bundle_1, - // - field_3 is in bundle_2. - $field_bundles_map = array( - 1 => array(1, 2), - 2 => array(1), - 3 => array(2), - ); - for ($i = 1; $i <= 3; $i++) { - $field_names[$i] = 'field_' . $i; - $field = array('field_name' => $field_names[$i], 'type' => 'test_field'); - $field = field_create_field($field); - $field_ids[$i] = $field['id']; - foreach ($field_bundles_map[$i] as $bundle) { - $instance = array( - 'field_name' => $field_names[$i], - 'entity_type' => 'test_entity', - 'bundle' => $bundles[$bundle], - 'settings' => array( - // Configure the instance so that we test hook_field_load() - // (see field_test_field_load() in field_test.module). - 'test_hook_field_load' => TRUE, - ), - ); - field_create_instance($instance); - } - } - - // Create one test entity per bundle, with random values. - foreach ($bundles as $index => $bundle) { - $entities[$index] = field_test_create_stub_entity($index, $index, $bundle); - $entity = clone($entities[$index]); - $instances = field_info_instances('test_entity', $bundle); - foreach ($instances as $field_name => $instance) { - $values[$index][$field_name] = mt_rand(1, 127); - $entity->$field_name = array($langcode => array(array('value' => $values[$index][$field_name]))); - } - field_attach_insert($entity_type, $entity); - } - - // Check that a single load correctly loads field values for both entities. - field_attach_load($entity_type, $entities); - foreach ($entities as $index => $entity) { - $instances = field_info_instances($entity_type, $bundles[$index]); - foreach ($instances as $field_name => $instance) { - // The field value loaded matches the one inserted. - $this->assertEqual($entity->{$field_name}[$langcode][0]['value'], $values[$index][$field_name], t('Entity %index: expected value was found.', array('%index' => $index))); - // The value added in hook_field_load() is found. - $this->assertEqual($entity->{$field_name}[$langcode][0]['additional_key'], 'additional_value', t('Entity %index: extra information was found', array('%index' => $index))); - } - } - - // Check that the single-field load option works. - $entity = field_test_create_stub_entity(1, 1, $bundles[1]); - field_attach_load($entity_type, array(1 => $entity), FIELD_LOAD_CURRENT, array('field_id' => $field_ids[1])); - $this->assertEqual($entity->{$field_names[1]}[$langcode][0]['value'], $values[1][$field_names[1]], t('Entity %index: expected value was found.', array('%index' => 1))); - $this->assertEqual($entity->{$field_names[1]}[$langcode][0]['additional_key'], 'additional_value', t('Entity %index: extra information was found', array('%index' => 1))); - $this->assert(!isset($entity->{$field_names[2]}), t('Entity %index: field %field_name is not loaded.', array('%index' => 2, '%field_name' => $field_names[2]))); - $this->assert(!isset($entity->{$field_names[3]}), t('Entity %index: field %field_name is not loaded.', array('%index' => 3, '%field_name' => $field_names[3]))); - } - - /** - * Test saving and loading fields using different storage backends. - */ - function testFieldAttachSaveLoadDifferentStorage() { - $entity_type = 'test_entity'; - $langcode = LANGUAGE_NONE; - - // Create two fields using different storage backends, and their instances. - $fields = array( - array( - 'field_name' => 'field_1', - 'type' => 'test_field', - 'cardinality' => 4, - 'storage' => array('type' => 'field_sql_storage') - ), - array( - 'field_name' => 'field_2', - 'type' => 'test_field', - 'cardinality' => 4, - 'storage' => array('type' => 'field_test_storage') - ), - ); - foreach ($fields as $field) { - field_create_field($field); - $instance = array( - 'field_name' => $field['field_name'], - 'entity_type' => 'test_entity', - 'bundle' => 'test_bundle', - ); - field_create_instance($instance); - } - - $entity_init = field_test_create_stub_entity(); - - // Create entity and insert random values. - $entity = clone($entity_init); - $values = array(); - foreach ($fields as $field) { - $values[$field['field_name']] = $this->_generateTestFieldValues($this->field['cardinality']); - $entity->{$field['field_name']}[$langcode] = $values[$field['field_name']]; - } - field_attach_insert($entity_type, $entity); - - // Check that values are loaded as expected. - $entity = clone($entity_init); - field_attach_load($entity_type, array($entity->ftid => $entity)); - foreach ($fields as $field) { - $this->assertEqual($values[$field['field_name']], $entity->{$field['field_name']}[$langcode], t('%storage storage: expected values were found.', array('%storage' => $field['storage']['type']))); - } - } - - /** - * Test storage details alteration. - * - * @see field_test_storage_details_alter() - */ - function testFieldStorageDetailsAlter() { - $field_name = 'field_test_change_my_details'; - $field = array( - 'field_name' => $field_name, - 'type' => 'test_field', - 'cardinality' => 4, - 'storage' => array('type' => 'field_test_storage'), - ); - $field = field_create_field($field); - $instance = array( - 'field_name' => $field_name, - 'entity_type' => 'test_entity', - 'bundle' => 'test_bundle', - ); - field_create_instance($instance); - - $field = field_info_field($instance['field_name']); - $instance = field_info_instance($instance['entity_type'], $instance['field_name'], $instance['bundle']); - - // The storage details are indexed by a storage engine type. - $this->assertTrue(array_key_exists('drupal_variables', $field['storage']['details']), t('The storage type is Drupal variables.')); - - $details = $field['storage']['details']['drupal_variables']; - - // The field_test storage details are indexed by variable name. The details - // are altered, so moon and mars are correct for this test. - $this->assertTrue(array_key_exists('moon', $details[FIELD_LOAD_CURRENT]), t('Moon is available in the instance array.')); - $this->assertTrue(array_key_exists('mars', $details[FIELD_LOAD_REVISION]), t('Mars is available in the instance array.')); - - // Test current and revision storage details together because the columns - // are the same. - foreach ((array) $field['columns'] as $column_name => $attributes) { - $this->assertEqual($details[FIELD_LOAD_CURRENT]['moon'][$column_name], $column_name, t('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => 'moon[FIELD_LOAD_CURRENT]'))); - $this->assertEqual($details[FIELD_LOAD_REVISION]['mars'][$column_name], $column_name, t('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => 'mars[FIELD_LOAD_REVISION]'))); - } - } - - /** - * Tests insert and update with missing or NULL fields. - */ - function testFieldAttachSaveMissingData() { - $entity_type = 'test_entity'; - $entity_init = field_test_create_stub_entity(); - $langcode = LANGUAGE_NONE; - - // Insert: Field is missing. - $entity = clone($entity_init); - field_attach_insert($entity_type, $entity); - - $entity = clone($entity_init); - field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertTrue(empty($entity->{$this->field_name}), t('Insert: missing field results in no value saved')); - - // Insert: Field is NULL. - field_cache_clear(); - $entity = clone($entity_init); - $entity->{$this->field_name} = NULL; - field_attach_insert($entity_type, $entity); - - $entity = clone($entity_init); - field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertTrue(empty($entity->{$this->field_name}), t('Insert: NULL field results in no value saved')); - - // Add some real data. - field_cache_clear(); - $entity = clone($entity_init); - $values = $this->_generateTestFieldValues(1); - $entity->{$this->field_name}[$langcode] = $values; - field_attach_insert($entity_type, $entity); - - $entity = clone($entity_init); - field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Field data saved')); - - // Update: Field is missing. Data should survive. - field_cache_clear(); - $entity = clone($entity_init); - field_attach_update($entity_type, $entity); - - $entity = clone($entity_init); - field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Update: missing field leaves existing values in place')); - - // Update: Field is NULL. Data should be wiped. - field_cache_clear(); - $entity = clone($entity_init); - $entity->{$this->field_name} = NULL; - field_attach_update($entity_type, $entity); - - $entity = clone($entity_init); - field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertTrue(empty($entity->{$this->field_name}), t('Update: NULL field removes existing values')); - - // Re-add some data. - field_cache_clear(); - $entity = clone($entity_init); - $values = $this->_generateTestFieldValues(1); - $entity->{$this->field_name}[$langcode] = $values; - field_attach_update($entity_type, $entity); - - $entity = clone($entity_init); - field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Field data saved')); - - // Update: Field is empty array. Data should be wiped. - field_cache_clear(); - $entity = clone($entity_init); - $entity->{$this->field_name} = array(); - field_attach_update($entity_type, $entity); - - $entity = clone($entity_init); - field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertTrue(empty($entity->{$this->field_name}), t('Update: empty array removes existing values')); - } - - /** - * Test insert with missing or NULL fields, with default value. - */ - function testFieldAttachSaveMissingDataDefaultValue() { - // Add a default value function. - $this->instance['default_value_function'] = 'field_test_default_value'; - field_update_instance($this->instance); - - $entity_type = 'test_entity'; - $entity_init = field_test_create_stub_entity(); - $langcode = LANGUAGE_NONE; - - // Insert: Field is NULL. - $entity = clone($entity_init); - $entity->{$this->field_name}[$langcode] = NULL; - field_attach_insert($entity_type, $entity); - - $entity = clone($entity_init); - field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertTrue(empty($entity->{$this->field_name}[$langcode]), t('Insert: NULL field results in no value saved')); - - // Insert: Field is missing. - field_cache_clear(); - $entity = clone($entity_init); - field_attach_insert($entity_type, $entity); - - $entity = clone($entity_init); - field_attach_load($entity_type, array($entity->ftid => $entity)); - $values = field_test_default_value($entity_type, $entity, $this->field, $this->instance); - $this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Insert: missing field results in default value saved')); - } - - /** - * Test field_attach_delete(). - */ - function testFieldAttachDelete() { - $entity_type = 'test_entity'; - $langcode = LANGUAGE_NONE; - $rev[0] = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - - // Create revision 0 - $values = $this->_generateTestFieldValues($this->field['cardinality']); - $rev[0]->{$this->field_name}[$langcode] = $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}[$langcode] = $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}[$langcode] = $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}[$langcode]), $this->field['cardinality'], "The test entity 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}[$langcode]), $this->field['cardinality'], "The test entity 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}[$langcode]), $this->field['cardinality'], "The test entity 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 entity 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(), t('The test entity current revision is deleted.')); - } - - /** - * Test field_attach_create_bundle() and field_attach_rename_bundle(). - */ - function testFieldAttachCreateRenameBundle() { - // Create a new bundle. This has to be initiated by the module so that its - // hook_entity_info() is consistent. - $new_bundle = 'test_bundle_' . drupal_strtolower($this->randomName()); - field_test_create_bundle($new_bundle); - - // Add an instance to that bundle. - $this->instance['bundle'] = $new_bundle; - field_create_instance($this->instance); - - // Save an entity with data in the field. - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - $langcode = LANGUAGE_NONE; - $values = $this->_generateTestFieldValues($this->field['cardinality']); - $entity->{$this->field_name}[$langcode] = $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}[$langcode]), $this->field['cardinality'], "Data is retrieved for the new bundle"); - - // Rename the bundle. This has to be initiated by the module so that its - // hook_entity_info() is consistent. - $new_bundle = 'test_bundle_' . drupal_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($entity_type, $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, $new_bundle); - field_attach_load($entity_type, array(0 => $entity)); - $this->assertEqual(count($entity->{$this->field_name}[$langcode]), $this->field['cardinality'], "Bundle name has been updated in the field storage"); - } - - /** - * Test field_attach_delete_bundle(). - */ - function testFieldAttachDeleteBundle() { - // Create a new bundle. This has to be initiated by the module so that its - // hook_entity_info() is consistent. - $new_bundle = 'test_bundle_' . drupal_strtolower($this->randomName()); - field_test_create_bundle($new_bundle); - - // 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 = drupal_strtolower($this->randomName() . '_field_name'); - $field = array('field_name' => $field_name, 'type' => 'test_field', 'cardinality' => 1); - field_create_field($field); - $instance = array( - 'field_name' => $field_name, - 'entity_type' => 'test_entity', - '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 entity with data for both fields - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - $langcode = LANGUAGE_NONE; - $values = $this->_generateTestFieldValues($this->field['cardinality']); - $entity->{$this->field_name}[$langcode] = $values; - $entity->{$field_name}[$langcode] = $this->_generateTestFieldValues(1); - field_attach_insert('test_entity', $entity); - - // Verify the fields are present on load - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - field_attach_load('test_entity', array(0 => $entity)); - $this->assertEqual(count($entity->{$this->field_name}[$langcode]), 4, 'First field got loaded'); - $this->assertEqual(count($entity->{$field_name}[$langcode]), 1, 'Second field got loaded'); - - // Delete the bundle. This has to be initiated by the module so that its - // hook_entity_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('test_entity', array(0 => $entity)); - $this->assertFalse(isset($entity->{$this->field_name}[$langcode]), 'No data for first field'); - $this->assertFalse(isset($entity->{$field_name}[$langcode]), 'No data for second field'); - - // Verify that the instances are gone - $this->assertFalse(field_read_instance('test_entity', $this->field_name, $this->instance['bundle']), "First field is deleted"); - $this->assertFalse(field_read_instance('test_entity', $field_name, $instance['bundle']), "Second field is deleted"); - } -} - -/** - * Unit test class for non-storage related field_attach_* functions. - */ -class FieldAttachOtherTestCase extends FieldAttachTestCase { - public static function getInfo() { - return array( - 'name' => 'Field attach tests (other)', - 'description' => 'Test other Field Attach API functions.', - 'group' => 'Field API', - ); - } - - /** - * Test field_attach_view() and field_attach_prepare_view(). - */ - function testFieldAttachView() { - $entity_type = 'test_entity'; - $entity_init = field_test_create_stub_entity(); - $langcode = LANGUAGE_NONE; - - // Populate values to be displayed. - $values = $this->_generateTestFieldValues($this->field['cardinality']); - $entity_init->{$this->field_name}[$langcode] = $values; - - // Simple formatter, label displayed. - $entity = clone($entity_init); - $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); - field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full'); - $entity->content = field_attach_view($entity_type, $entity, 'full'); - $output = drupal_render($entity->content); - $this->content = $output; - $this->assertRaw($this->instance['label'], "Label is displayed."); - foreach ($values as $delta => $value) { - $this->content = $output; - $this->assertRaw("$formatter_setting|{$value['value']}", "Value $delta is displayed, formatter settings are applied."); - } - - // Label hidden. - $entity = clone($entity_init); - $this->instance['display']['full']['label'] = 'hidden'; - field_update_instance($this->instance); - field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full'); - $entity->content = field_attach_view($entity_type, $entity, 'full'); - $output = drupal_render($entity->content); - $this->content = $output; - $this->assertNoRaw($this->instance['label'], "Hidden label: label is not displayed."); - - // Field hidden. - $entity = clone($entity_init); - $this->instance['display'] = array( - 'full' => array( - 'label' => 'above', - 'type' => 'hidden', - ), - ); - field_update_instance($this->instance); - field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full'); - $entity->content = field_attach_view($entity_type, $entity, 'full'); - $output = drupal_render($entity->content); - $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. - $entity = clone($entity_init); - $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); - field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full'); - $entity->content = field_attach_view($entity_type, $entity, 'full'); - $output = drupal_render($entity->content); - $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."); - - // Test a formatter that uses hook_field_formatter_prepare_view(). - $entity = clone($entity_init); - $formatter_setting = $this->randomName(); - $this->instance['display'] = array( - 'full' => array( - 'label' => 'above', - 'type' => 'field_test_with_prepare_view', - 'settings' => array( - 'test_formatter_setting_additional' => $formatter_setting, - ) - ), - ); - field_update_instance($this->instance); - field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full'); - $entity->content = field_attach_view($entity_type, $entity, 'full'); - $output = drupal_render($entity->content); - $this->content = $output; - foreach ($values as $delta => $value) { - $this->content = $output; - $expected = $formatter_setting . '|' . $value['value'] . '|' . ($value['value'] + 1); - $this->assertRaw($expected, "Value $delta is displayed, formatter settings are applied."); - } - - // TODO: - // - check display order with several fields - - // Preprocess template. - $variables = array(); - field_attach_preprocess($entity_type, $entity, $entity->content, $variables); - $result = TRUE; - foreach ($values as $delta => $item) { - if ($variables[$this->field_name][$delta]['value'] !== $item['value']) { - $result = FALSE; - break; - } - } - $this->assertTrue($result, t('Variable $@field_name correctly populated.', array('@field_name' => $this->field_name))); - } - - /** - * Tests the 'multiple entity' behavior of field_attach_prepare_view(). - */ - function testFieldAttachPrepareViewMultiple() { - $entity_type = 'test_entity'; - $langcode = LANGUAGE_NONE; - - // Set the instance to be hidden. - $this->instance['display']['full']['type'] = 'hidden'; - field_update_instance($this->instance); - - // Set up a second instance on another bundle, with a formatter that uses - // hook_field_formatter_prepare_view(). - field_test_create_bundle('test_bundle_2'); - $formatter_setting = $this->randomName(); - $this->instance2 = $this->instance; - $this->instance2['bundle'] = 'test_bundle_2'; - $this->instance2['display']['full'] = array( - 'type' => 'field_test_with_prepare_view', - 'settings' => array( - 'test_formatter_setting_additional' => $formatter_setting, - ) - ); - field_create_instance($this->instance2); - - // Create one entity in each bundle. - $entity1_init = field_test_create_stub_entity(1, 1, 'test_bundle'); - $values1 = $this->_generateTestFieldValues($this->field['cardinality']); - $entity1_init->{$this->field_name}[$langcode] = $values1; - - $entity2_init = field_test_create_stub_entity(2, 2, 'test_bundle_2'); - $values2 = $this->_generateTestFieldValues($this->field['cardinality']); - $entity2_init->{$this->field_name}[$langcode] = $values2; - - // Run prepare_view, and check that the entities come out as expected. - $entity1 = clone($entity1_init); - $entity2 = clone($entity2_init); - field_attach_prepare_view($entity_type, array($entity1->ftid => $entity1, $entity2->ftid => $entity2), 'full'); - $this->assertFalse(isset($entity1->{$this->field_name}[$langcode][0]['additional_formatter_value']), 'Entity 1 did not run through the prepare_view hook.'); - $this->assertTrue(isset($entity2->{$this->field_name}[$langcode][0]['additional_formatter_value']), 'Entity 2 ran through the prepare_view hook.'); - - // Same thing, reversed order. - $entity1 = clone($entity1_init); - $entity2 = clone($entity2_init); - field_attach_prepare_view($entity_type, array($entity2->ftid => $entity2, $entity1->ftid => $entity1), 'full'); - $this->assertFalse(isset($entity1->{$this->field_name}[$langcode][0]['additional_formatter_value']), 'Entity 1 did not run through the prepare_view hook.'); - $this->assertTrue(isset($entity2->{$this->field_name}[$langcode][0]['additional_formatter_value']), 'Entity 2 ran through the prepare_view hook.'); - } - - /** - * Test field cache. - */ - function testFieldAttachCache() { - // Initialize random values and a test entity. - $entity_init = field_test_create_stub_entity(1, 1, $this->instance['bundle']); - $langcode = LANGUAGE_NONE; - $values = $this->_generateTestFieldValues($this->field['cardinality']); - - // Non-cacheable entity type. - $entity_type = 'test_entity'; - $cid = "field:$entity_type:{$entity_init->ftid}"; - - // Check that no initial cache entry is present. - $this->assertFalse(cache('field')->get($cid), t('Non-cached: no initial cache entry')); - - // Save, and check that no cache entry is present. - $entity = clone($entity_init); - $entity->{$this->field_name}[$langcode] = $values; - field_attach_insert($entity_type, $entity); - $this->assertFalse(cache('field')->get($cid), t('Non-cached: no cache entry on insert')); - - // Load, and check that no cache entry is present. - $entity = clone($entity_init); - field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertFalse(cache('field')->get($cid), t('Non-cached: no cache entry on load')); - - - // Cacheable entity type. - $entity_type = 'test_cacheable_entity'; - $cid = "field:$entity_type:{$entity_init->ftid}"; - $instance = $this->instance; - $instance['entity_type'] = $entity_type; - field_create_instance($instance); - - // Check that no initial cache entry is present. - $this->assertFalse(cache('field')->get($cid), t('Cached: no initial cache entry')); - - // Save, and check that no cache entry is present. - $entity = clone($entity_init); - $entity->{$this->field_name}[$langcode] = $values; - field_attach_insert($entity_type, $entity); - $this->assertFalse(cache('field')->get($cid), t('Cached: no cache entry on insert')); - - // Load a single field, and check that no cache entry is present. - $entity = clone($entity_init); - field_attach_load($entity_type, array($entity->ftid => $entity), FIELD_LOAD_CURRENT, array('field_id' => $this->field_id)); - $cache = cache('field')->get($cid); - $this->assertFalse(cache('field')->get($cid), t('Cached: no cache entry on loading a single field')); - - // Load, and check that a cache entry is present with the expected values. - $entity = clone($entity_init); - field_attach_load($entity_type, array($entity->ftid => $entity)); - $cache = cache('field')->get($cid); - $this->assertEqual($cache->data[$this->field_name][$langcode], $values, t('Cached: correct cache entry on load')); - - // Update with different values, and check that the cache entry is wiped. - $values = $this->_generateTestFieldValues($this->field['cardinality']); - $entity = clone($entity_init); - $entity->{$this->field_name}[$langcode] = $values; - field_attach_update($entity_type, $entity); - $this->assertFalse(cache('field')->get($cid), t('Cached: no cache entry on update')); - - // Load, and check that a cache entry is present with the expected values. - $entity = clone($entity_init); - field_attach_load($entity_type, array($entity->ftid => $entity)); - $cache = cache('field')->get($cid); - $this->assertEqual($cache->data[$this->field_name][$langcode], $values, t('Cached: correct cache entry on load')); - - // Create a new revision, and check that the cache entry is wiped. - $entity_init = field_test_create_stub_entity(1, 2, $this->instance['bundle']); - $values = $this->_generateTestFieldValues($this->field['cardinality']); - $entity = clone($entity_init); - $entity->{$this->field_name}[$langcode] = $values; - field_attach_update($entity_type, $entity); - $cache = cache('field')->get($cid); - $this->assertFalse(cache('field')->get($cid), t('Cached: no cache entry on new revision creation')); - - // Load, and check that a cache entry is present with the expected values. - $entity = clone($entity_init); - field_attach_load($entity_type, array($entity->ftid => $entity)); - $cache = cache('field')->get($cid); - $this->assertEqual($cache->data[$this->field_name][$langcode], $values, t('Cached: correct cache entry on load')); - - // Delete, and check that the cache entry is wiped. - field_attach_delete($entity_type, $entity); - $this->assertFalse(cache('field')->get($cid), t('Cached: no cache entry after delete')); - } - - /** - * Test field_attach_validate(). - * - * Verify that field_attach_validate() invokes the correct - * hook_field_validate. - */ - function testFieldAttachValidate() { - $entity_type = 'test_entity'; - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - $langcode = LANGUAGE_NONE; - - // Set up values to generate errors - $values = array(); - for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { - $values[$delta]['value'] = -1; - } - // Arrange for item 1 not to generate an error - $values[1]['value'] = 1; - $entity->{$this->field_name}[$langcode] = $values; - - try { - field_attach_validate($entity_type, $entity); - } - catch (FieldValidationException $e) { - $errors = $e->errors; - } - - foreach ($values as $delta => $value) { - if ($value['value'] != 1) { - $this->assertIdentical($errors[$this->field_name][$langcode][$delta][0]['error'], 'field_test_invalid', "Error set on value $delta"); - $this->assertEqual(count($errors[$this->field_name][$langcode][$delta]), 1, "Only one error set on value $delta"); - unset($errors[$this->field_name][$langcode][$delta]); - } - else { - $this->assertFalse(isset($errors[$this->field_name][$langcode][$delta]), "No error set on value $delta"); - } - } - $this->assertEqual(count($errors[$this->field_name][$langcode]), 0, 'No extraneous errors set'); - - // Check that cardinality is validated. - $entity->{$this->field_name}[$langcode] = $this->_generateTestFieldValues($this->field['cardinality'] + 1); - try { - field_attach_validate($entity_type, $entity); - } - catch (FieldValidationException $e) { - $errors = $e->errors; - } - $this->assertEqual($errors[$this->field_name][$langcode][0][0]['error'], 'field_cardinality', t('Cardinality validation failed.')); - - } - - /** - * Test field_attach_form(). - * - * 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 = array(); - $form_state = form_state_defaults(); - field_attach_form($entity_type, $entity, $form, $form_state); - - $langcode = LANGUAGE_NONE; - $this->assertEqual($form[$this->field_name][$langcode]['#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][$langcode][$delta]['value']['#type'], 'textfield', "Form delta $delta widget is textfield"); - } - } - - /** - * Test field_attach_submit(). - */ - function testFieldAttachSubmit() { - $entity_type = 'test_entity'; - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - - // Build the form. - $form = array(); - $form_state = form_state_defaults(); - 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; - - $langcode = LANGUAGE_NONE; - // Pretend the form has been built. - drupal_prepare_form('field_test_entity_form', $form, $form_state); - drupal_process_form('field_test_entity_form', $form, $form_state); - $form_state['values'][$this->field_name][$langcode] = $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}[$langcode], $expected_values, 'Submit filters empty values'); - } -} - -class FieldInfoTestCase extends FieldTestCase { - - public static function getInfo() { - return array( - 'name' => 'Field info tests', - 'description' => 'Get information about existing fields, instances and bundles.', - 'group' => 'Field API', - ); - } - - function setUp() { - parent::setUp('field_test'); - } - - /** - * Test that field types and field definitions are correcly cached. - */ - function testFieldInfo() { - // Test that field_test module's fields, widgets, and formatters show up. - - $field_test_info = field_test_field_info(); - // We need to account for the existence of user_field_info_alter(). - foreach (array_keys($field_test_info) as $name) { - $field_test_info[$name]['instance_settings']['user_register_form'] = FALSE; - } - $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")); - } - - $formatter_info = field_test_field_formatter_info(); - $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")); - } - - $widget_info = field_test_field_widget_info(); - $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")); - } - - $storage_info = field_test_field_storage_info(); - $info = field_info_storage_types(); - foreach ($storage_info as $s_key => $storage) { - foreach ($storage as $key => $val) { - $this->assertEqual($info[$s_key][$key], $val, t("Storage type $s_key key $key is $val")); - } - $this->assertEqual($info[$s_key]['module'], 'field_test', t("Storage type field_test module appears")); - } - - // Verify that no unexpected instances exist. - $core_fields = field_info_fields(); - $instances = field_info_instances('test_entity', 'test_bundle'); - $this->assertTrue(empty($instances), t('With no instances, info bundles is empty.')); - - // Create a field, verify it shows up. - $field = array( - 'field_name' => drupal_strtolower($this->randomName()), - 'type' => 'test_field', - ); - field_create_field($field); - $fields = field_info_fields(); - $this->assertEqual(count($fields), count($core_fields) + 1, t('One new 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'], - 'entity_type' => 'test_entity', - 'bundle' => '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('test_entity', $instance['bundle']); - $this->assertEqual(count($instances), 1, t('One instance shows up in info when attached to a bundle.')); - $this->assertTrue($instance < $instances[$instance['field_name']], t('Instance appears in info correctly')); - } - - /** - * Test that cached field definitions are ready for current runtime context. - */ - function testFieldPrepare() { - $field_definition = array( - 'field_name' => 'field', - 'type' => 'test_field', - ); - field_create_field($field_definition); - - // Simulate a stored field definition missing a field setting (e.g. a - // third-party module adding a new field setting has been enabled, and - // existing fields do not know the setting yet). - $data = db_query('SELECT data FROM {field_config} WHERE field_name = :field_name', array(':field_name' => $field_definition['field_name']))->fetchField(); - $data = unserialize($data); - $data['settings'] = array(); - db_update('field_config') - ->fields(array('data' => serialize($data))) - ->condition('field_name', $field_definition['field_name']) - ->execute(); - - field_cache_clear(); - - // Read the field back. - $field = field_info_field($field_definition['field_name']); - - // Check that all expected settings are in place. - $field_type = field_info_field_types($field_definition['type']); - $this->assertIdentical($field['settings'], $field_type['settings'], t('All expected default field settings are present.')); - } - - /** - * Test that cached instance definitions are ready for current runtime context. - */ - function testInstancePrepare() { - $field_definition = array( - 'field_name' => 'field', - 'type' => 'test_field', - ); - field_create_field($field_definition); - $instance_definition = array( - 'field_name' => $field_definition['field_name'], - 'entity_type' => 'test_entity', - 'bundle' => 'test_bundle', - ); - field_create_instance($instance_definition); - - // Simulate a stored instance definition missing various settings (e.g. a - // third-party module adding instance, widget or display settings has been - // enabled, but existing instances do not know the new settings). - $data = db_query('SELECT data FROM {field_config_instance} WHERE field_name = :field_name AND bundle = :bundle', array(':field_name' => $instance_definition['field_name'], ':bundle' => $instance_definition['bundle']))->fetchField(); - $data = unserialize($data); - $data['settings'] = array(); - $data['widget']['settings'] = 'unavailable_widget'; - $data['widget']['settings'] = array(); - $data['display']['default']['type'] = 'unavailable_formatter'; - $data['display']['default']['settings'] = array(); - db_update('field_config_instance') - ->fields(array('data' => serialize($data))) - ->condition('field_name', $instance_definition['field_name']) - ->condition('bundle', $instance_definition['bundle']) - ->execute(); - - field_cache_clear(); - - // Read the instance back. - $instance = field_info_instance($instance_definition['entity_type'], $instance_definition['field_name'], $instance_definition['bundle']); - - // Check that all expected instance settings are in place. - $field_type = field_info_field_types($field_definition['type']); - $this->assertIdentical($instance['settings'], $field_type['instance_settings'] , t('All expected instance settings are present.')); - - // Check that the default widget is used and expected settings are in place. - $this->assertIdentical($instance['widget']['type'], $field_type['default_widget'], t('Unavailable widget replaced with default widget.')); - $widget_type = field_info_widget_types($instance['widget']['type']); - $this->assertIdentical($instance['widget']['settings'], $widget_type['settings'] , t('All expected widget settings are present.')); - - // Check that display settings are set for the 'default' mode. - $display = $instance['display']['default']; - $this->assertIdentical($display['type'], $field_type['default_formatter'], t("Formatter is set for the 'default' view mode")); - $formatter_type = field_info_formatter_types($display['type']); - $this->assertIdentical($display['settings'], $formatter_type['settings'] , t("Formatter settings are set for the 'default' view mode")); - } - - /** - * Test that instances on disabled entity types are filtered out. - */ - function testInstanceDisabledEntityType() { - // For this test the field type and the entity type must be exposed by - // different modules. - $field_definition = array( - 'field_name' => 'field', - 'type' => 'test_field', - ); - field_create_field($field_definition); - $instance_definition = array( - 'field_name' => 'field', - 'entity_type' => 'comment', - 'bundle' => 'comment_node_article', - ); - field_create_instance($instance_definition); - - // Disable coment module. This clears field_info cache. - module_disable(array('comment')); - $this->assertNull(field_info_instance('comment', 'field', 'comment_node_article'), t('No instances are returned on disabled entity types.')); - } - - /** - * Test that the field_info settings convenience functions work. - */ - function testSettingsInfo() { - $info = field_test_field_info(); - // We need to account for the existence of user_field_info_alter(). - foreach (array_keys($info) as $name) { - $info[$name]['instance_settings']['user_register_form'] = FALSE; - } - 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 FieldTestCase { - public static function getInfo() { - return array( - 'name' => 'Field form tests', - 'description' => 'Test Field form handling.', - 'group' => 'Field API', - ); - } - - function setUp() { - parent::setUp('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' => 'field_single', 'type' => 'test_field'); - $this->field_multiple = array('field_name' => 'field_multiple', 'type' => 'test_field', 'cardinality' => 4); - $this->field_unlimited = array('field_name' => 'field_unlimited', 'type' => 'test_field', 'cardinality' => FIELD_CARDINALITY_UNLIMITED); - - $this->instance = array( - 'entity_type' => 'test_entity', - '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); - $langcode = LANGUAGE_NONE; - - // Display creation form. - $this->drupalGet('test-entity/add/test-bundle'); - $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", '', 'Widget is displayed'); - $this->assertNoField("{$this->field_name}[$langcode][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}[$langcode][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(1, 127); - $edit = array("{$this->field_name}[$langcode][0][value]" => $value); - $this->drupalPost(NULL, $edit, t('Save')); - preg_match('|test-entity/manage/(\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_test_load($id); - $this->assertEqual($entity->{$this->field_name}[$langcode][0]['value'], $value, 'Field value was saved'); - - // Display edit form. - $this->drupalGet('test-entity/manage/' . $id . '/edit'); - $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", $value, 'Widget is displayed with the correct default value'); - $this->assertNoField("{$this->field_name}[$langcode][1][value]", 'No extraneous widget is displayed'); - - // Update the entity. - $value = mt_rand(1, 127); - $edit = array("{$this->field_name}[$langcode][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_test_load($id); - $this->assertEqual($entity->{$this->field_name}[$langcode][0]['value'], $value, 'Field value was updated'); - - // Empty the field. - $value = ''; - $edit = array("{$this->field_name}[$langcode][0][value]" => $value); - $this->drupalPost('test-entity/manage/' . $id . '/edit', $edit, t('Save')); - $this->assertRaw(t('test_entity @id has been updated.', array('@id' => $id)), 'Entity was updated'); - $entity = field_test_entity_test_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); - $langcode = LANGUAGE_NONE; - - // 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(1, 127); - $edit = array("{$this->field_name}[$langcode][0][value]" => $value); - $this->drupalPost(NULL, $edit, t('Save')); - preg_match('|test-entity/manage/(\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_test_load($id); - $this->assertEqual($entity->{$this->field_name}[$langcode][0]['value'], $value, 'Field value was saved'); - - // Edit with missing required value. - $value = ''; - $edit = array("{$this->field_name}[$langcode][0][value]" => $value); - $this->drupalPost('test-entity/manage/' . $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); - $langcode = LANGUAGE_NONE; - - // Display creation form -> 1 widget. - $this->drupalGet('test-entity/add/test-bundle'); - $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", '', 'Widget 1 is displayed'); - $this->assertNoField("{$this->field_name}[$langcode][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}[$langcode][0][value]", '', 'Widget 1 is displayed'); - $this->assertFieldByName("{$this->field_name}[$langcode][1][value]", '', 'New widget is displayed'); - $this->assertNoField("{$this->field_name}[$langcode][2][value]", 'No extraneous widget is displayed'); - // 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 values and weights. - do { - $value = mt_rand(1, 127); - } while (in_array($value, $values)); - do { - $weight = mt_rand(-$delta_range, $delta_range); - } while (in_array($weight, $weights)); - $edit["$this->field_name[$langcode][$delta][value]"] = $value; - $edit["$this->field_name[$langcode][$delta][_weight]"] = $weight; - // We'll need three slightly different formats to check the values. - $values[$delta] = $value; - $weights[$delta] = $weight; - $field_values[$weight]['value'] = (string) $value; - $pattern[$weight] = "<input [^>]*value=\"$value\" [^>]*"; - } - - // Press 'add more' button -> 4 widgets - $this->drupalPost(NULL, $edit, t('Add another item')); - for ($delta = 0; $delta <= $delta_range; $delta++) { - $this->assertFieldByName("$this->field_name[$langcode][$delta][value]", $values[$delta], "Widget $delta is displayed and has the right value"); - $this->assertFieldByName("$this->field_name[$langcode][$delta][_weight]", $weights[$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[$langcode][$delta][value]", '', "New widget is displayed"); - $this->assertFieldByName("$this->field_name[$langcode][$delta][_weight]", $delta, "New widget has the right weight"); - $this->assertNoField("$this->field_name[$langcode][" . ($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/manage/(\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_test_load($id); - ksort($field_values); - $field_values = array_values($field_values); - $this->assertIdentical($entity->{$this->field_name}[$langcode], $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 - } - - function testFieldFormJSAddMore() { - $this->field = $this->field_unlimited; - $this->field_name = $this->field['field_name']; - $this->instance['field_name'] = $this->field_name; - field_create_field($this->field); - field_create_instance($this->instance); - $langcode = LANGUAGE_NONE; - - // Display creation form -> 1 widget. - $this->drupalGet('test-entity/add/test-bundle'); - - // Press 'add more' button a couple times -> 3 widgets. - // drupalPostAJAX() will not work iteratively, so we add those through - // non-JS submission. - $this->drupalPost(NULL, array(), t('Add another item')); - $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 values and weights. - do { - $value = mt_rand(1, 127); - } while (in_array($value, $values)); - do { - $weight = mt_rand(-$delta_range, $delta_range); - } while (in_array($weight, $weights)); - $edit["$this->field_name[$langcode][$delta][value]"] = $value; - $edit["$this->field_name[$langcode][$delta][_weight]"] = $weight; - // We'll need three slightly different formats to check the values. - $values[$delta] = $value; - $weights[$delta] = $weight; - $field_values[$weight]['value'] = (string) $value; - $pattern[$weight] = "<input [^>]*value=\"$value\" [^>]*"; - } - // Press 'add more' button through Ajax, and place the expected HTML result - // as the tested content. - $commands = $this->drupalPostAJAX(NULL, $edit, $this->field_name . '_add_more'); - $this->content = $commands[1]['data']; - - for ($delta = 0; $delta <= $delta_range; $delta++) { - $this->assertFieldByName("$this->field_name[$langcode][$delta][value]", $values[$delta], "Widget $delta is displayed and has the right value"); - $this->assertFieldByName("$this->field_name[$langcode][$delta][_weight]", $weights[$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[$langcode][$delta][value]", '', "New widget is displayed"); - $this->assertFieldByName("$this->field_name[$langcode][$delta][_weight]", $delta, "New widget has the right weight"); - $this->assertNoField("$this->field_name[$langcode][" . ($delta + 1) . '][value]', 'No extraneous widget is displayed'); - } - - /** - * Tests widgets handling multiple values. - */ - function testFieldFormMultipleWidget() { - // Create a field with fixed cardinality and an instance using a multiple - // widget. - $this->field = $this->field_multiple; - $this->field_name = $this->field['field_name']; - $this->instance['field_name'] = $this->field_name; - $this->instance['widget']['type'] = 'test_field_widget_multiple'; - field_create_field($this->field); - field_create_instance($this->instance); - $langcode = LANGUAGE_NONE; - - // Display creation form. - $this->drupalGet('test-entity/add/test-bundle'); - $this->assertFieldByName("{$this->field_name}[$langcode]", '', t('Widget is displayed.')); - - // Create entity with three values. - $edit = array("{$this->field_name}[$langcode]" => '1, 2, 3'); - $this->drupalPost(NULL, $edit, t('Save')); - preg_match('|test-entity/manage/(\d+)/edit|', $this->url, $match); - $id = $match[1]; - - // Check that the values were saved. - $entity_init = field_test_create_stub_entity($id); - $this->assertFieldValues($entity_init, $this->field_name, $langcode, array(1, 2, 3)); - - // Display the form, check that the values are correctly filled in. - $this->drupalGet('test-entity/manage/' . $id . '/edit'); - $this->assertFieldByName("{$this->field_name}[$langcode]", '1, 2, 3', t('Widget is displayed.')); - - // Submit the form with more values than the field accepts. - $edit = array("{$this->field_name}[$langcode]" => '1, 2, 3, 4, 5'); - $this->drupalPost(NULL, $edit, t('Save')); - $this->assertRaw('this field cannot hold more than 4 values', t('Form validation failed.')); - // Check that the field values were not submitted. - $this->assertFieldValues($entity_init, $this->field_name, $langcode, array(1, 2, 3)); - } - - /** - * Tests fields with no 'edit' access. - */ - function testFieldFormAccess() { - // Create a "regular" field. - $field = $this->field_single; - $field_name = $field['field_name']; - $instance = $this->instance; - $instance['field_name'] = $field_name; - field_create_field($field); - field_create_instance($instance); - - // Create a field with no edit access - see field_test_field_access(). - $field_no_access = array( - 'field_name' => 'field_no_edit_access', - 'type' => 'test_field', - ); - $field_name_no_access = $field_no_access['field_name']; - $instance_no_access = array( - 'field_name' => $field_name_no_access, - 'entity_type' => 'test_entity', - 'bundle' => 'test_bundle', - 'default_value' => array(0 => array('value' => 99)), - ); - field_create_field($field_no_access); - field_create_instance($instance_no_access); - - $langcode = LANGUAGE_NONE; - - // Display creation form. - $this->drupalGet('test-entity/add/test-bundle'); - $this->assertNoFieldByName("{$field_name_no_access}[$langcode][0][value]", '', t('Widget is not displayed if field access is denied.')); - - // Create entity. - $edit = array("{$field_name}[$langcode][0][value]" => 1); - $this->drupalPost(NULL, $edit, t('Save')); - preg_match('|test-entity/manage/(\d+)/edit|', $this->url, $match); - $id = $match[1]; - - // Check that the default value was saved. - $entity = field_test_entity_test_load($id); - $this->assertEqual($entity->{$field_name_no_access}[$langcode][0]['value'], 99, t('Default value was saved for the field with no edit access.')); - $this->assertEqual($entity->{$field_name}[$langcode][0]['value'], 1, t('Entered value vas saved for the field with edit access.')); - - // Create a new revision. - $edit = array("{$field_name}[$langcode][0][value]" => 2, 'revision' => TRUE); - $this->drupalPost('test-entity/manage/' . $id . '/edit', $edit, t('Save')); - - // Check that the new revision has the expected values. - $entity = field_test_entity_test_load($id); - $this->assertEqual($entity->{$field_name_no_access}[$langcode][0]['value'], 99, t('New revision has the expected value for the field with no edit access.')); - $this->assertEqual($entity->{$field_name}[$langcode][0]['value'], 2, t('New revision has the expected value for the field with edit access.')); - - // Check that the revision is also saved in the revisions table. - $entity = field_test_entity_test_load($id, $entity->ftvid); - $this->assertEqual($entity->{$field_name_no_access}[$langcode][0]['value'], 99, t('New revision has the expected value for the field with no edit access.')); - $this->assertEqual($entity->{$field_name}[$langcode][0]['value'], 2, t('New revision has the expected value for the field with edit access.')); - } - - /** - * Tests Field API form integration within a subform. - */ - function testNestedFieldForm() { - // Add two instances on the 'test_bundle' - field_create_field($this->field_single); - field_create_field($this->field_unlimited); - $this->instance['field_name'] = 'field_single'; - $this->instance['label'] = 'Single field'; - field_create_instance($this->instance); - $this->instance['field_name'] = 'field_unlimited'; - $this->instance['label'] = 'Unlimited field'; - field_create_instance($this->instance); - - // Create two entities. - $entity_1 = field_test_create_stub_entity(1, 1); - $entity_1->is_new = TRUE; - $entity_1->field_single[LANGUAGE_NONE][] = array('value' => 0); - $entity_1->field_unlimited[LANGUAGE_NONE][] = array('value' => 1); - field_test_entity_save($entity_1); - - $entity_2 = field_test_create_stub_entity(2, 2); - $entity_2->is_new = TRUE; - $entity_2->field_single[LANGUAGE_NONE][] = array('value' => 10); - $entity_2->field_unlimited[LANGUAGE_NONE][] = array('value' => 11); - field_test_entity_save($entity_2); - - // Display the 'combined form'. - $this->drupalGet('test-entity/nested/1/2'); - $this->assertFieldByName('field_single[und][0][value]', 0, t('Entity 1: field_single value appears correctly is the form.')); - $this->assertFieldByName('field_unlimited[und][0][value]', 1, t('Entity 1: field_unlimited value 0 appears correctly is the form.')); - $this->assertFieldByName('entity_2[field_single][und][0][value]', 10, t('Entity 2: field_single value appears correctly is the form.')); - $this->assertFieldByName('entity_2[field_unlimited][und][0][value]', 11, t('Entity 2: field_unlimited value 0 appears correctly is the form.')); - - // Submit the form and check that the entities are updated accordingly. - $edit = array( - 'field_single[und][0][value]' => 1, - 'field_unlimited[und][0][value]' => 2, - 'field_unlimited[und][1][value]' => 3, - 'entity_2[field_single][und][0][value]' => 11, - 'entity_2[field_unlimited][und][0][value]' => 12, - 'entity_2[field_unlimited][und][1][value]' => 13, - ); - $this->drupalPost(NULL, $edit, t('Save')); - field_cache_clear(); - $entity_1 = field_test_create_stub_entity(1); - $entity_2 = field_test_create_stub_entity(2); - $this->assertFieldValues($entity_1, 'field_single', LANGUAGE_NONE, array(1)); - $this->assertFieldValues($entity_1, 'field_unlimited', LANGUAGE_NONE, array(2, 3)); - $this->assertFieldValues($entity_2, 'field_single', LANGUAGE_NONE, array(11)); - $this->assertFieldValues($entity_2, 'field_unlimited', LANGUAGE_NONE, array(12, 13)); - - // Submit invalid values and check that errors are reported on the - // correct widgets. - $edit = array( - 'field_unlimited[und][1][value]' => -1, - ); - $this->drupalPost('test-entity/nested/1/2', $edit, t('Save')); - $this->assertRaw(t('%label does not accept the value -1', array('%label' => 'Unlimited field')), t('Entity 1: the field validation error was reported.')); - $error_field = $this->xpath('//input[@id=:id and contains(@class, "error")]', array(':id' => 'edit-field-unlimited-und-1-value')); - $this->assertTrue($error_field, t('Entity 1: the error was flagged on the correct element.')); - $edit = array( - 'entity_2[field_unlimited][und][1][value]' => -1, - ); - $this->drupalPost('test-entity/nested/1/2', $edit, t('Save')); - $this->assertRaw(t('%label does not accept the value -1', array('%label' => 'Unlimited field')), t('Entity 2: the field validation error was reported.')); - $error_field = $this->xpath('//input[@id=:id and contains(@class, "error")]', array(':id' => 'edit-entity-2-field-unlimited-und-1-value')); - $this->assertTrue($error_field, t('Entity 2: the error was flagged on the correct element.')); - - // Test that reordering works on both entities. - $edit = array( - 'field_unlimited[und][0][_weight]' => 0, - 'field_unlimited[und][1][_weight]' => -1, - 'entity_2[field_unlimited][und][0][_weight]' => 0, - 'entity_2[field_unlimited][und][1][_weight]' => -1, - ); - $this->drupalPost('test-entity/nested/1/2', $edit, t('Save')); - field_cache_clear(); - $this->assertFieldValues($entity_1, 'field_unlimited', LANGUAGE_NONE, array(3, 2)); - $this->assertFieldValues($entity_2, 'field_unlimited', LANGUAGE_NONE, array(13, 12)); - - // Test the 'add more' buttons. Only Ajax submission is tested, because - // the two 'add more' buttons present in the form have the same #value, - // which confuses drupalPost(). - // 'Add more' button in the first entity: - $this->drupalGet('test-entity/nested/1/2'); - $this->drupalPostAJAX(NULL, array(), 'field_unlimited_add_more'); - $this->assertFieldByName('field_unlimited[und][0][value]', 3, t('Entity 1: field_unlimited value 0 appears correctly is the form.')); - $this->assertFieldByName('field_unlimited[und][1][value]', 2, t('Entity 1: field_unlimited value 1 appears correctly is the form.')); - $this->assertFieldByName('field_unlimited[und][2][value]', '', t('Entity 1: field_unlimited value 2 appears correctly is the form.')); - $this->assertFieldByName('field_unlimited[und][3][value]', '', t('Entity 1: an empty widget was added for field_unlimited value 3.')); - // 'Add more' button in the first entity (changing field values): - $edit = array( - 'entity_2[field_unlimited][und][0][value]' => 13, - 'entity_2[field_unlimited][und][1][value]' => 14, - 'entity_2[field_unlimited][und][2][value]' => 15, - ); - $this->drupalPostAJAX(NULL, $edit, 'entity_2_field_unlimited_add_more'); - $this->assertFieldByName('entity_2[field_unlimited][und][0][value]', 13, t('Entity 2: field_unlimited value 0 appears correctly is the form.')); - $this->assertFieldByName('entity_2[field_unlimited][und][1][value]', 14, t('Entity 2: field_unlimited value 1 appears correctly is the form.')); - $this->assertFieldByName('entity_2[field_unlimited][und][2][value]', 15, t('Entity 2: field_unlimited value 2 appears correctly is the form.')); - $this->assertFieldByName('entity_2[field_unlimited][und][3][value]', '', t('Entity 2: an empty widget was added for field_unlimited value 3.')); - // Save the form and check values are saved correclty. - $this->drupalPost(NULL, array(), t('Save')); - field_cache_clear(); - $this->assertFieldValues($entity_1, 'field_unlimited', LANGUAGE_NONE, array(3, 2)); - $this->assertFieldValues($entity_2, 'field_unlimited', LANGUAGE_NONE, array(13, 14, 15)); - } -} - -class FieldDisplayAPITestCase extends FieldTestCase { - public static function getInfo() { - return array( - 'name' => 'Field Display API tests', - 'description' => 'Test the display API.', - 'group' => 'Field API', - ); - } - - function setUp() { - parent::setUp('field_test'); - - // Create a field and instance. - $this->field_name = 'test_field'; - $this->label = $this->randomName(); - $this->cardinality = 4; - - $this->field = array( - 'field_name' => $this->field_name, - 'type' => 'test_field', - 'cardinality' => $this->cardinality, - ); - $this->instance = array( - 'field_name' => $this->field_name, - 'entity_type' => 'test_entity', - 'bundle' => 'test_bundle', - 'label' => $this->label, - 'display' => array( - 'default' => array( - 'type' => 'field_test_default', - 'settings' => array( - 'test_formatter_setting' => $this->randomName(), - ), - ), - 'teaser' => array( - 'type' => 'field_test_default', - 'settings' => array( - 'test_formatter_setting' => $this->randomName(), - ), - ), - ), - ); - field_create_field($this->field); - field_create_instance($this->instance); - - // Create an entity with values. - $this->values = $this->_generateTestFieldValues($this->cardinality); - $this->entity = field_test_create_stub_entity(); - $this->is_new = TRUE; - $this->entity->{$this->field_name}[LANGUAGE_NONE] = $this->values; - field_test_entity_save($this->entity); - } - - /** - * Test the field_view_field() function. - */ - function testFieldViewField() { - // No display settings: check that default display settings are used. - $output = field_view_field('test_entity', $this->entity, $this->field_name); - $this->drupalSetContent(drupal_render($output)); - $settings = field_info_formatter_settings('field_test_default'); - $setting = $settings['test_formatter_setting']; - $this->assertText($this->label, t('Label was displayed.')); - foreach ($this->values as $delta => $value) { - $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); - } - - // Check that explicit display settings are used. - $display = array( - 'label' => 'hidden', - 'type' => 'field_test_multiple', - 'settings' => array( - 'test_formatter_setting_multiple' => $this->randomName(), - 'alter' => TRUE, - ), - ); - $output = field_view_field('test_entity', $this->entity, $this->field_name, $display); - $this->drupalSetContent(drupal_render($output)); - $setting = $display['settings']['test_formatter_setting_multiple']; - $this->assertNoText($this->label, t('Label was not displayed.')); - $this->assertText('field_test_field_attach_view_alter', t('Alter fired, display passed.')); - $array = array(); - foreach ($this->values as $delta => $value) { - $array[] = $delta . ':' . $value['value']; - } - $this->assertText($setting . '|' . implode('|', $array), t('Values were displayed with expected setting.')); - - // Check the prepare_view steps are invoked. - $display = array( - 'label' => 'hidden', - 'type' => 'field_test_with_prepare_view', - 'settings' => array( - 'test_formatter_setting_additional' => $this->randomName(), - ), - ); - $output = field_view_field('test_entity', $this->entity, $this->field_name, $display); - $view = drupal_render($output); - $this->drupalSetContent($view); - $setting = $display['settings']['test_formatter_setting_additional']; - $this->assertNoText($this->label, t('Label was not displayed.')); - $this->assertNoText('field_test_field_attach_view_alter', t('Alter not fired.')); - foreach ($this->values as $delta => $value) { - $this->assertText($setting . '|' . $value['value'] . '|' . ($value['value'] + 1), t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); - } - - // View mode: check that display settings specified in the instance are - // used. - $output = field_view_field('test_entity', $this->entity, $this->field_name, 'teaser'); - $this->drupalSetContent(drupal_render($output)); - $setting = $this->instance['display']['teaser']['settings']['test_formatter_setting']; - $this->assertText($this->label, t('Label was displayed.')); - foreach ($this->values as $delta => $value) { - $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); - } - - // Unknown view mode: check that display settings for 'default' view mode - // are used. - $output = field_view_field('test_entity', $this->entity, $this->field_name, 'unknown_view_mode'); - $this->drupalSetContent(drupal_render($output)); - $setting = $this->instance['display']['default']['settings']['test_formatter_setting']; - $this->assertText($this->label, t('Label was displayed.')); - foreach ($this->values as $delta => $value) { - $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); - } - } - - /** - * Test the field_view_value() function. - */ - function testFieldViewValue() { - // No display settings: check that default display settings are used. - $settings = field_info_formatter_settings('field_test_default'); - $setting = $settings['test_formatter_setting']; - foreach ($this->values as $delta => $value) { - $item = $this->entity->{$this->field_name}[LANGUAGE_NONE][$delta]; - $output = field_view_value('test_entity', $this->entity, $this->field_name, $item); - $this->drupalSetContent(drupal_render($output)); - $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); - } - - // Check that explicit display settings are used. - $display = array( - 'type' => 'field_test_multiple', - 'settings' => array( - 'test_formatter_setting_multiple' => $this->randomName(), - ), - ); - $setting = $display['settings']['test_formatter_setting_multiple']; - $array = array(); - foreach ($this->values as $delta => $value) { - $item = $this->entity->{$this->field_name}[LANGUAGE_NONE][$delta]; - $output = field_view_value('test_entity', $this->entity, $this->field_name, $item, $display); - $this->drupalSetContent(drupal_render($output)); - $this->assertText($setting . '|0:' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); - } - - // Check that prepare_view steps are invoked. - $display = array( - 'type' => 'field_test_with_prepare_view', - 'settings' => array( - 'test_formatter_setting_additional' => $this->randomName(), - ), - ); - $setting = $display['settings']['test_formatter_setting_additional']; - $array = array(); - foreach ($this->values as $delta => $value) { - $item = $this->entity->{$this->field_name}[LANGUAGE_NONE][$delta]; - $output = field_view_value('test_entity', $this->entity, $this->field_name, $item, $display); - $this->drupalSetContent(drupal_render($output)); - $this->assertText($setting . '|' . $value['value'] . '|' . ($value['value'] + 1), t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); - } - - // View mode: check that display settings specified in the instance are - // used. - $setting = $this->instance['display']['teaser']['settings']['test_formatter_setting']; - foreach ($this->values as $delta => $value) { - $item = $this->entity->{$this->field_name}[LANGUAGE_NONE][$delta]; - $output = field_view_value('test_entity', $this->entity, $this->field_name, $item, 'teaser'); - $this->drupalSetContent(drupal_render($output)); - $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); - } - - // Unknown view mode: check that display settings for 'default' view mode - // are used. - $setting = $this->instance['display']['default']['settings']['test_formatter_setting']; - foreach ($this->values as $delta => $value) { - $item = $this->entity->{$this->field_name}[LANGUAGE_NONE][$delta]; - $output = field_view_value('test_entity', $this->entity, $this->field_name, $item, 'unknown_view_mode'); - $this->drupalSetContent(drupal_render($output)); - $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); - } - } -} - -class FieldCrudTestCase extends FieldTestCase { - public static function getInfo() { - return array( - 'name' => 'Field CRUD tests', - 'description' => 'Test field create, read, update, and delete.', - 'group' => 'Field API', - ); - } - - function setUp() { - // field_update_field() tests use number.module - parent::setUp('field_test', 'number'); - } - - // 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' => 'field_2', - 'type' => 'test_field', - ); - field_test_memorize(); - $field_definition = field_create_field($field_definition); - $mem = field_test_memorize(); - $this->assertIdentical($mem['field_test_field_create_field'][0][0], $field_definition, 'hook_field_create_field() called with correct arguments.'); - - // Read the raw record from the {field_config_instance} table. - $result = db_query('SELECT * FROM {field_config} WHERE field_name = :field_name', array(':field_name' => $field_definition['field_name'])); - $record = $result->fetchAssoc(); - $record['data'] = unserialize($record['data']); - - // Ensure that basic properties are preserved. - $this->assertEqual($record['field_name'], $field_definition['field_name'], t('The field name is properly saved.')); - $this->assertEqual($record['type'], $field_definition['type'], t('The field type is properly saved.')); - - // Ensure that cardinality defaults to 1. - $this->assertEqual($record['cardinality'], 1, t('Cardinality defaults to 1.')); - - // Ensure that default settings are present. - $field_type = field_info_field_types($field_definition['type']); - $this->assertIdentical($record['data']['settings'], $field_type['settings'], t('Default field settings have been written.')); - - // Ensure that default storage was set. - $this->assertEqual($record['storage_type'], variable_get('field_storage_default'), t('The field type is properly saved.')); - - // 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 field type is required. - try { - $field_definition = array( - 'field_name' => 'field_1', - ); - field_create_field($field_definition); - $this->fail(t('Cannot create a field with no type.')); - } - catch (FieldException $e) { - $this->pass(t('Cannot create a field with no type.')); - } - - // Check that field name is required. - try { - $field_definition = array( - 'type' => 'test_field' - ); - field_create_field($field_definition); - $this->fail(t('Cannot create an unnamed field.')); - } - catch (FieldException $e) { - $this->pass(t('Cannot create an unnamed field.')); - } - - // Check that field name must start with a letter or _. - try { - $field_definition = array( - 'field_name' => '2field_2', - 'type' => 'test_field', - ); - field_create_field($field_definition); - $this->fail(t('Cannot create a field with a name starting with a digit.')); - } - catch (FieldException $e) { - $this->pass(t('Cannot create a field with a name starting with a digit.')); - } - - // Check that field name must only contain lowercase alphanumeric or _. - try { - $field_definition = array( - 'field_name' => 'field#_3', - 'type' => 'test_field', - ); - field_create_field($field_definition); - $this->fail(t('Cannot create a field with a name containing an illegal character.')); - } - catch (FieldException $e) { - $this->pass(t('Cannot create a field with a name containing an illegal character.')); - } - - // Check that field name cannot be longer than 32 characters long. - try { - $field_definition = array( - 'field_name' => '_12345678901234567890123456789012', - 'type' => 'test_field', - ); - field_create_field($field_definition); - $this->fail(t('Cannot create a field with a name longer than 32 characters.')); - } - catch (FieldException $e) { - $this->pass(t('Cannot create a field with a name longer than 32 characters.')); - } - - // Check that field name can not be an entity key. - // "ftvid" is known as an entity key from the "test_entity" type. - try { - $field_definition = array( - 'type' => 'test_field', - 'field_name' => 'ftvid', - ); - $field = field_create_field($field_definition); - $this->fail(t('Cannot create a field bearing the name of an entity key.')); - } - catch (FieldException $e) { - $this->pass(t('Cannot create a field bearing the name of an entity key.')); - } - } - - /** - * Test failure to create a field. - */ - function testCreateFieldFail() { - $field_name = 'duplicate'; - $field_definition = array('field_name' => $field_name, 'type' => 'test_field', 'storage' => array('type' => 'field_test_storage_failure')); - $query = db_select('field_config')->condition('field_name', $field_name)->countQuery(); - - // The field does not appear in field_config. - $count = $query->execute()->fetchField(); - $this->assertEqual($count, 0, 'A field_config row for the field does not exist.'); - - // Try to create the field. - try { - $field = field_create_field($field_definition); - $this->assertTrue(FALSE, 'Field creation (correctly) fails.'); - } - catch (Exception $e) { - $this->assertTrue(TRUE, 'Field creation (correctly) fails.'); - } - - // The field does not appear in field_config. - $count = $query->execute()->fetchField(); - $this->assertEqual($count, 0, 'A field_config row for the field does not exist.'); - } - - /** - * Test reading back a field definition. - */ - function testReadField() { - $field_definition = array( - 'field_name' => 'field_1', - 'type' => 'test_field', - ); - field_create_field($field_definition); - - // Read the field back. - $field = field_read_field($field_definition['field_name']); - $this->assertTrue($field_definition < $field, t('The field was properly read.')); - } - - /** - * Test creation of indexes on data column. - */ - function testFieldIndexes() { - // Check that indexes specified by the field type are used by default. - $field_definition = array( - 'field_name' => 'field_1', - 'type' => 'test_field', - ); - field_create_field($field_definition); - $field = field_read_field($field_definition['field_name']); - $expected_indexes = array('value' => array('value')); - $this->assertEqual($field['indexes'], $expected_indexes, t('Field type indexes saved by default')); - - // Check that indexes specified by the field definition override the field - // type indexes. - $field_definition = array( - 'field_name' => 'field_2', - 'type' => 'test_field', - 'indexes' => array( - 'value' => array(), - ), - ); - field_create_field($field_definition); - $field = field_read_field($field_definition['field_name']); - $expected_indexes = array('value' => array()); - $this->assertEqual($field['indexes'], $expected_indexes, t('Field definition indexes override field type indexes')); - - // Check that indexes specified by the field definition add to the field - // type indexes. - $field_definition = array( - 'field_name' => 'field_3', - 'type' => 'test_field', - 'indexes' => array( - 'value_2' => array('value'), - ), - ); - field_create_field($field_definition); - $field = field_read_field($field_definition['field_name']); - $expected_indexes = array('value' => array('value'), 'value_2' => array('value')); - $this->assertEqual($field['indexes'], $expected_indexes, t('Field definition indexes are merged with field type indexes')); - } - - /** - * 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 = array('field_name' => 'field_1', 'type' => 'test_field'); - field_create_field($this->field); - $this->another_field = array('field_name' => 'field_2', 'type' => 'test_field'); - field_create_field($this->another_field); - - // Create instances for each. - $this->instance_definition = array( - 'field_name' => $this->field['field_name'], - 'entity_type' => 'test_entity', - 'bundle' => '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('test_entity', $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('test_entity', $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('test_entity', $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.')); - - // Try to create a new field the same name as a deleted field and - // write data into it. - field_create_field($this->field); - field_create_instance($this->instance_definition); - $field = field_read_field($this->field['field_name']); - $this->assertTrue(!empty($field) && empty($field['deleted']), t('A new field with a previously used name is created.')); - $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); - $this->assertTrue(!empty($instance) && empty($instance['deleted']), t('A new instance for a previously used field name is created.')); - - // Save an entity with data for the field - $entity = field_test_create_stub_entity(0, 0, $instance['bundle']); - $langcode = LANGUAGE_NONE; - $values[0]['value'] = mt_rand(1, 127); - $entity->{$field['field_name']}[$langcode] = $values; - $entity_type = 'test_entity'; - field_attach_insert('test_entity', $entity); - - // Verify the field is present on load - $entity = field_test_create_stub_entity(0, 0, $this->instance_definition['bundle']); - field_attach_load($entity_type, array(0 => $entity)); - $this->assertIdentical(count($entity->{$field['field_name']}[$langcode]), count($values), "Data in previously deleted field saves and loads correctly"); - foreach ($values as $delta => $value) { - $this->assertEqual($entity->{$field['field_name']}[$langcode][$delta]['value'], $values[$delta]['value'], "Data in previously deleted field saves and loads correctly"); - } - } - - function testUpdateNonExistentField() { - $test_field = array('field_name' => 'does_not_exist', 'type' => 'number_decimal'); - try { - field_update_field($test_field); - $this->fail(t('Cannot update a field that does not exist.')); - } - catch (FieldException $e) { - $this->pass(t('Cannot update a field that does not exist.')); - } - } - - function testUpdateFieldType() { - $field = array('field_name' => 'field_type', 'type' => 'number_decimal'); - $field = field_create_field($field); - - $test_field = array('field_name' => 'field_type', 'type' => 'number_integer'); - try { - field_update_field($test_field); - $this->fail(t('Cannot update a field to a different type.')); - } - catch (FieldException $e) { - $this->pass(t('Cannot update a field to a different type.')); - } - } - - /** - * Test updating a field. - */ - function testUpdateField() { - // Create a field with a defined cardinality, so that we can ensure it's - // respected. Since cardinality enforcement is consistent across database - // systems, it makes a good test case. - $cardinality = 4; - $field_definition = array( - 'field_name' => 'field_update', - 'type' => 'test_field', - 'cardinality' => $cardinality, - ); - $field_definition = field_create_field($field_definition); - $instance = array( - 'field_name' => 'field_update', - 'entity_type' => 'test_entity', - 'bundle' => 'test_bundle', - ); - $instance = field_create_instance($instance); - - do { - // We need a unique ID for our entity. $cardinality will do. - $id = $cardinality; - $entity = field_test_create_stub_entity($id, $id, $instance['bundle']); - // Fill in the entity with more values than $cardinality. - for ($i = 0; $i < 20; $i++) { - $entity->field_update[LANGUAGE_NONE][$i]['value'] = $i; - } - // Save the entity. - field_attach_insert('test_entity', $entity); - // Load back and assert there are $cardinality number of values. - $entity = field_test_create_stub_entity($id, $id, $instance['bundle']); - field_attach_load('test_entity', array($id => $entity)); - $this->assertEqual(count($entity->field_update[LANGUAGE_NONE]), $field_definition['cardinality'], 'Cardinality is kept'); - // Now check the values themselves. - for ($delta = 0; $delta < $cardinality; $delta++) { - $this->assertEqual($entity->field_update[LANGUAGE_NONE][$delta]['value'], $delta, 'Value is kept'); - } - // Increase $cardinality and set the field cardinality to the new value. - $field_definition['cardinality'] = ++$cardinality; - field_update_field($field_definition); - } while ($cardinality < 6); - } - - /** - * Test field type modules forbidding an update. - */ - function testUpdateFieldForbid() { - $field = array('field_name' => 'forbidden', 'type' => 'test_field', 'settings' => array('changeable' => 0, 'unchangeable' => 0)); - $field = field_create_field($field); - $field['settings']['changeable']++; - try { - field_update_field($field); - $this->pass(t("A changeable setting can be updated.")); - } - catch (FieldException $e) { - $this->fail(t("An unchangeable setting cannot be updated.")); - } - $field['settings']['unchangeable']++; - try { - field_update_field($field); - $this->fail(t("An unchangeable setting can be updated.")); - } - catch (FieldException $e) { - $this->pass(t("An unchangeable setting cannot be updated.")); - } - } - - /** - * Test that fields are properly marked active or inactive. - */ - function testActive() { - $field_definition = array( - 'field_name' => 'field_1', - 'type' => 'test_field', - // For this test, we need a storage backend provided by a different - // module than field_test.module. - 'storage' => array( - 'type' => 'field_sql_storage', - ), - ); - field_create_field($field_definition); - - // Test disabling and enabling: - // - the field type module, - // - the storage module, - // - both. - $this->_testActiveHelper($field_definition, array('field_test')); - $this->_testActiveHelper($field_definition, array('field_sql_storage')); - $this->_testActiveHelper($field_definition, array('field_test', 'field_sql_storage')); - } - - /** - * Helper function for testActive(). - * - * Test dependency between a field and a set of modules. - * - * @param $field_definition - * A field definition. - * @param $modules - * An aray of module names. The field will be tested to be inactive as long - * as any of those modules is disabled. - */ - function _testActiveHelper($field_definition, $modules) { - $field_name = $field_definition['field_name']; - - // Read the field. - $field = field_read_field($field_name); - $this->assertTrue($field_definition <= $field, t('The field was properly read.')); - - module_disable($modules, FALSE); - drupal_flush_all_caches(); - - $fields = field_read_fields(array('field_name' => $field_name), array('include_inactive' => TRUE)); - $this->assertTrue(isset($fields[$field_name]) && $field_definition < $field, t('The field is properly read when explicitly fetching inactive fields.')); - - // Re-enable modules one by one, and check that the field is still inactive - // while some modules remain disabled. - while ($modules) { - $field = field_read_field($field_name); - $this->assertTrue(empty($field), t('%modules disabled. The field is marked inactive.', array('%modules' => implode(', ', $modules)))); - - $module = array_shift($modules); - module_enable(array($module), FALSE); - drupal_flush_all_caches(); - } - - // Check that the field is active again after all modules have been - // enabled. - $field = field_read_field($field_name); - $this->assertTrue($field_definition <= $field, t('The field was was marked active.')); - } -} - -class FieldInstanceCrudTestCase extends FieldTestCase { - protected $field; - - public static function getInfo() { - return array( - 'name' => 'Field instance CRUD tests', - 'description' => 'Create field entities by attaching fields to entities.', - 'group' => 'Field API', - ); - } - - function setUp() { - parent::setUp('field_test'); - - $this->field = array( - 'field_name' => drupal_strtolower($this->randomName()), - 'type' => 'test_field', - ); - field_create_field($this->field); - $this->instance_definition = array( - 'field_name' => $this->field['field_name'], - 'entity_type' => 'test_entity', - 'bundle' => '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 - - /** - * Test the creation of a field instance. - */ - function testCreateFieldInstance() { - field_create_instance($this->instance_definition); - - // Read the raw record from the {field_config_instance} table. - $result = db_query('SELECT * FROM {field_config_instance} WHERE field_name = :field_name AND bundle = :bundle', array(':field_name' => $this->instance_definition['field_name'], ':bundle' => $this->instance_definition['bundle'])); - $record = $result->fetchAssoc(); - $record['data'] = unserialize($record['data']); - - $field_type = field_info_field_types($this->field['type']); - $widget_type = field_info_widget_types($field_type['default_widget']); - $formatter_type = field_info_formatter_types($field_type['default_formatter']); - - // Check that default values are set. - $this->assertIdentical($record['data']['required'], FALSE, t('Required defaults to false.')); - $this->assertIdentical($record['data']['label'], $this->instance_definition['field_name'], t('Label defaults to field name.')); - $this->assertIdentical($record['data']['description'], '', t('Description defaults to empty string.')); - $this->assertIdentical($record['data']['widget']['type'], $field_type['default_widget'], t('Default widget has been written.')); - $this->assertTrue(isset($record['data']['display']['default']), t('Display for "full" view_mode has been written.')); - $this->assertIdentical($record['data']['display']['default']['type'], $field_type['default_formatter'], t('Default formatter for "full" view_mode has been written.')); - - // Check that default settings are set. - $this->assertIdentical($record['data']['settings'], $field_type['instance_settings'] , t('Default instance settings have been written.')); - $this->assertIdentical($record['data']['widget']['settings'], $widget_type['settings'] , t('Default widget settings have been written.')); - $this->assertIdentical($record['data']['display']['default']['settings'], $formatter_type['settings'], t('Default formatter settings for "full" view_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.')); - } - - // Create a field restricted to a specific entity type. - $field_restricted = array( - 'field_name' => drupal_strtolower($this->randomName()), - 'type' => 'test_field', - 'entity_types' => array('test_cacheable_entity'), - ); - field_create_field($field_restricted); - - // Check that an instance can be added to an entity type allowed - // by the field. - try { - $instance = $this->instance_definition; - $instance['field_name'] = $field_restricted['field_name']; - $instance['entity_type'] = 'test_cacheable_entity'; - field_create_instance($instance); - $this->pass(t('Can create an instance on an entity type allowed by the field.')); - } - catch (FieldException $e) { - $this->fail(t('Can create an instance on an entity type allowed by the field.')); - } - - // Check that an instance cannot be added to an entity type - // forbidden by the field. - try { - $instance = $this->instance_definition; - $instance['field_name'] = $field_restricted['field_name']; - field_create_instance($instance); - $this->fail(t('Cannot create an instance on an entity type forbidden by the field.')); - } - catch (FieldException $e) { - $this->pass(t('Cannot create an instance on an entity type forbidden by the field.')); - } - - // TODO: test other failures. - } - - /** - * Test reading back an instance definition. - */ - function testReadFieldInstance() { - field_create_instance($this->instance_definition); - - // Read the instance back. - $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); - $this->assertTrue($this->instance_definition < $instance, t('The field was properly read.')); - } - - /** - * Test the update of a field instance. - */ - 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('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); - $instance['required'] = !$instance['required']; - $instance['label'] = $this->randomName(); - $instance['description'] = $this->randomName(); - $instance['settings']['test_instance_setting'] = $this->randomName(); - $instance['widget']['settings']['test_widget_setting'] =$this->randomName(); - $instance['widget']['weight']++; - $instance['display']['default']['settings']['test_formatter_setting'] = $this->randomName(); - $instance['display']['default']['weight']++; - field_update_instance($instance); - - $instance_new = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); - $this->assertEqual($instance['required'], $instance_new['required'], t('"required" 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['widget']['weight'], $instance_new['widget']['weight'], t('Widget weight change is saved')); - $this->assertEqual($instance['display']['default']['settings']['test_formatter_setting'], $instance_new['display']['default']['settings']['test_formatter_setting'], t('Formatter setting change is saved')); - $this->assertEqual($instance['display']['default']['weight'], $instance_new['display']['default']['weight'], t('Widget weight change is saved')); - - // Check that changing widget and formatter types updates the default settings. - $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); - $instance['widget']['type'] = 'test_field_widget_multiple'; - $instance['display']['default']['type'] = 'field_test_multiple'; - field_update_instance($instance); - - $instance_new = field_read_instance('test_entity', $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']['default']['type'], $instance_new['display']['default']['type'] , t('Formatter type change is saved.')); - $info = field_info_formatter_types($instance_new['display']['default']['type']); - $settings = $info['settings']; - $this->assertIdentical($settings, array_intersect_key($instance_new['display']['default']['settings'], $settings) , t('Changing formatter type updates default settings.')); - - // Check that adding a new view mode is saved and gets default settings. - $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); - $instance['display']['teaser'] = array(); - field_update_instance($instance); - - $instance_new = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); - $this->assertTrue(isset($instance_new['display']['teaser']), t('Display for the new view_mode has been written.')); - $this->assertIdentical($instance_new['display']['teaser']['type'], $field_type['default_formatter'], t('Default formatter for the new view_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 view_mode have been written.')); - - // TODO: test failures. - } - - /** - * Test the deletion of a field instance. - */ - 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'; - $instance = field_create_instance($this->another_instance_definition); - - // Test that the first instance is not deleted, and then delete it. - $instance = field_read_instance('test_entity', $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($instance); - - // Make sure the instance is marked as deleted when the instance is - // specifically loaded. - $instance = field_read_instance('test_entity', $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('test_entity', $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('test_entity', $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.')); - - // Make sure the field is deleted when its last instance is deleted. - field_delete_instance($another_instance); - $field = field_read_field($another_instance['field_name'], array('include_deleted' => TRUE)); - $this->assertTrue(!empty($field['deleted']), t('A deleted field is marked for deletion after all its instances have been marked for deletion.')); - } -} - -/** - * Unit test class for the multilanguage fields logic. - * - * The following tests will check the multilanguage logic of _field_invoke() and - * that only the correct values are returned by field_available_languages(). - */ -class FieldTranslationsTestCase extends FieldTestCase { - public static function getInfo() { - return array( - 'name' => 'Field translations tests', - 'description' => 'Test multilanguage fields logic.', - 'group' => 'Field API', - ); - } - - function setUp() { - parent::setUp('locale', 'field_test'); - - $this->field_name = drupal_strtolower($this->randomName() . '_field_name'); - - $this->entity_type = 'test_entity'; - - $field = array( - 'field_name' => $this->field_name, - 'type' => 'test_field', - 'cardinality' => 4, - 'translatable' => TRUE, - ); - field_create_field($field); - $this->field = field_read_field($this->field_name); - - $instance = array( - 'field_name' => $this->field_name, - 'entity_type' => $this->entity_type, - 'bundle' => 'test_bundle', - ); - field_create_instance($instance); - $this->instance = field_read_instance('test_entity', $this->field_name, 'test_bundle'); - - require_once DRUPAL_ROOT . '/includes/locale.inc'; - for ($i = 0; $i < 3; ++$i) { - $language = (object) array( - 'language' => 'l' . $i, - 'name' => $this->randomString(), - ); - locale_language_save($language); - } - } - - /** - * Ensures that only valid values are returned by field_available_languages(). - */ - function testFieldAvailableLanguages() { - // Test 'translatable' fieldable info. - field_test_entity_info_translatable('test_entity', FALSE); - $field = $this->field; - $field['field_name'] .= '_untranslatable'; - - // Enable field translations for the entity. - field_test_entity_info_translatable('test_entity', TRUE); - - // Test hook_field_languages() invocation on a translatable field. - variable_set('field_test_field_available_languages_alter', TRUE); - $enabled_languages = field_content_languages(); - $available_languages = field_available_languages($this->entity_type, $this->field); - foreach ($available_languages as $delta => $langcode) { - if ($langcode != 'xx' && $langcode != 'en') { - $this->assertTrue(in_array($langcode, $enabled_languages), t('%language is an enabled language.', array('%language' => $langcode))); - } - } - $this->assertTrue(in_array('xx', $available_languages), t('%language was made available.', array('%language' => 'xx'))); - $this->assertFalse(in_array('en', $available_languages), t('%language was made unavailable.', array('%language' => 'en'))); - - // Test field_available_languages() behavior for untranslatable fields. - $this->field['translatable'] = FALSE; - $this->field_name = $this->field['field_name'] = $this->instance['field_name'] = drupal_strtolower($this->randomName() . '_field_name'); - $available_languages = field_available_languages($this->entity_type, $this->field); - $this->assertTrue(count($available_languages) == 1 && $available_languages[0] === LANGUAGE_NONE, t('For untranslatable fields only LANGUAGE_NONE is available.')); - } - - /** - * Test the multilanguage logic of _field_invoke(). - */ - function testFieldInvoke() { - // Enable field translations for the entity. - field_test_entity_info_translatable('test_entity', TRUE); - - $entity_type = 'test_entity'; - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - - // Populate some extra languages to check if _field_invoke() correctly uses - // the result of field_available_languages(). - $values = array(); - $extra_languages = mt_rand(1, 4); - $languages = $available_languages = field_available_languages($this->entity_type, $this->field); - for ($i = 0; $i < $extra_languages; ++$i) { - $languages[] = $this->randomName(2); - } - - // For each given language provide some random values. - foreach ($languages as $langcode) { - for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { - $values[$langcode][$delta]['value'] = mt_rand(1, 127); - } - } - $entity->{$this->field_name} = $values; - - $results = _field_invoke('test_op', $entity_type, $entity); - foreach ($results as $langcode => $result) { - $hash = hash('sha256', serialize(array($entity_type, $entity, $this->field_name, $langcode, $values[$langcode]))); - // Check whether the parameters passed to _field_invoke() were correctly - // forwarded to the callback function. - $this->assertEqual($hash, $result, t('The result for %language is correctly stored.', array('%language' => $langcode))); - } - - $this->assertEqual(count($results), count($available_languages), t('No unavailable language has been processed.')); - } - - /** - * Test the multilanguage logic of _field_invoke_multiple(). - */ - function testFieldInvokeMultiple() { - // Enable field translations for the entity. - field_test_entity_info_translatable('test_entity', TRUE); - - $values = array(); - $options = array(); - $entities = array(); - $entity_type = 'test_entity'; - $entity_count = mt_rand(2, 5); - $available_languages = field_available_languages($this->entity_type, $this->field); - - for ($id = 1; $id <= $entity_count; ++$id) { - $entity = field_test_create_stub_entity($id, $id, $this->instance['bundle']); - $languages = $available_languages; - - // Populate some extra languages to check whether _field_invoke() - // correctly uses the result of field_available_languages(). - $extra_languages = mt_rand(1, 4); - for ($i = 0; $i < $extra_languages; ++$i) { - $languages[] = $this->randomName(2); - } - - // For each given language provide some random values. - $language_count = count($languages); - for ($i = 0; $i < $language_count; ++$i) { - $langcode = $languages[$i]; - // Avoid to populate at least one field translation to check that - // per-entity language suggestions work even when available field values - // are different for each language. - if ($i !== $id) { - for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { - $values[$id][$langcode][$delta]['value'] = mt_rand(1, 127); - } - } - // Ensure that a language for which there is no field translation is - // used as display language to prepare per-entity language suggestions. - elseif (!isset($display_language)) { - $display_language = $langcode; - } - } - - $entity->{$this->field_name} = $values[$id]; - $entities[$id] = $entity; - - // Store per-entity language suggestions. - $options['language'][$id] = field_language($entity_type, $entity, NULL, $display_language); - } - - $grouped_results = _field_invoke_multiple('test_op_multiple', $entity_type, $entities); - foreach ($grouped_results as $id => $results) { - foreach ($results as $langcode => $result) { - if (isset($values[$id][$langcode])) { - $hash = hash('sha256', serialize(array($entity_type, $entities[$id], $this->field_name, $langcode, $values[$id][$langcode]))); - // Check whether the parameters passed to _field_invoke_multiple() - // were correctly forwarded to the callback function. - $this->assertEqual($hash, $result, t('The result for entity %id/%language is correctly stored.', array('%id' => $id, '%language' => $langcode))); - } - } - $this->assertEqual(count($results), count($available_languages), t('No unavailable language has been processed for entity %id.', array('%id' => $id))); - } - - $null = NULL; - $grouped_results = _field_invoke_multiple('test_op_multiple', $entity_type, $entities, $null, $null, $options); - foreach ($grouped_results as $id => $results) { - foreach ($results as $langcode => $result) { - $this->assertTrue(isset($options['language'][$id]), t('The result language %language for entity %id was correctly suggested (display language: %display_language).', array('%id' => $id, '%language' => $langcode, '%display_language' => $display_language))); - } - } - } - - /** - * Test translatable fields storage/retrieval. - */ - function testTranslatableFieldSaveLoad() { - // Enable field translations for nodes. - field_test_entity_info_translatable('node', TRUE); - $entity_info = entity_get_info('node'); - $this->assertTrue(count($entity_info['translation']), t('Nodes are translatable.')); - - // Prepare the field translations. - field_test_entity_info_translatable('test_entity', TRUE); - $eid = $evid = 1; - $entity_type = 'test_entity'; - $entity = field_test_create_stub_entity($eid, $evid, $this->instance['bundle']); - $field_translations = array(); - $available_languages = field_available_languages($entity_type, $this->field); - $this->assertTrue(count($available_languages) > 1, t('Field is translatable.')); - foreach ($available_languages as $langcode) { - $field_translations[$langcode] = $this->_generateTestFieldValues($this->field['cardinality']); - } - - // Save and reload the field translations. - $entity->{$this->field_name} = $field_translations; - field_attach_insert($entity_type, $entity); - unset($entity->{$this->field_name}); - field_attach_load($entity_type, array($eid => $entity)); - - // Check if the correct values were saved/loaded. - foreach ($field_translations as $langcode => $items) { - $result = TRUE; - foreach ($items as $delta => $item) { - $result = $result && $item['value'] == $entity->{$this->field_name}[$langcode][$delta]['value']; - } - $this->assertTrue($result, t('%language translation correctly handled.', array('%language' => $langcode))); - } - } - - /** - * Tests display language logic for translatable fields. - */ - function testFieldDisplayLanguage() { - $field_name = drupal_strtolower($this->randomName() . '_field_name'); - $entity_type = 'test_entity'; - - // We need an additional field here to properly test display language - // suggestions. - $field = array( - 'field_name' => $field_name, - 'type' => 'test_field', - 'cardinality' => 2, - 'translatable' => TRUE, - ); - field_create_field($field); - - $instance = array( - 'field_name' => $field['field_name'], - 'entity_type' => $entity_type, - 'bundle' => 'test_bundle', - ); - field_create_instance($instance); - - $entity = field_test_create_stub_entity(1, 1, $this->instance['bundle']); - $instances = field_info_instances($entity_type, $this->instance['bundle']); - - $enabled_languages = field_content_languages(); - $languages = array(); - - // Generate field translations for languages different from the first - // enabled. - foreach ($instances as $instance) { - $field_name = $instance['field_name']; - $field = field_info_field($field_name); - do { - // Index 0 is reserved for the requested language, this way we ensure - // that no field is actually populated with it. - $langcode = $enabled_languages[mt_rand(1, count($enabled_languages) - 1)]; - } - while (isset($languages[$langcode])); - $languages[$langcode] = TRUE; - $entity->{$field_name}[$langcode] = $this->_generateTestFieldValues($field['cardinality']); - } - - // Test multiple-fields display languages for untranslatable entities. - field_test_entity_info_translatable($entity_type, FALSE); - drupal_static_reset('field_language'); - $requested_language = $enabled_languages[0]; - $display_language = field_language($entity_type, $entity, NULL, $requested_language); - foreach ($instances as $instance) { - $field_name = $instance['field_name']; - $this->assertTrue($display_language[$field_name] == LANGUAGE_NONE, t('The display language for field %field_name is %language.', array('%field_name' => $field_name, '%language' => LANGUAGE_NONE))); - } - - // Test multiple-fields display languages for translatable entities. - field_test_entity_info_translatable($entity_type, TRUE); - drupal_static_reset('field_language'); - $display_language = field_language($entity_type, $entity, NULL, $requested_language); - - foreach ($instances as $instance) { - $field_name = $instance['field_name']; - $langcode = $display_language[$field_name]; - // As the requested language was not assinged to any field, if the - // returned language is defined for the current field, core fallback rules - // were successfully applied. - $this->assertTrue(isset($entity->{$field_name}[$langcode]) && $langcode != $requested_language, t('The display language for the field %field_name is %language.', array('%field_name' => $field_name, '%language' => $langcode))); - } - - // Test single-field display language. - drupal_static_reset('field_language'); - $langcode = field_language($entity_type, $entity, $this->field_name, $requested_language); - $this->assertTrue(isset($entity->{$this->field_name}[$langcode]) && $langcode != $requested_language, t('The display language for the (single) field %field_name is %language.', array('%field_name' => $field_name, '%language' => $langcode))); - - // Test field_language() basic behavior without language fallback. - variable_set('field_test_language_fallback', FALSE); - $entity->{$this->field_name}[$requested_language] = mt_rand(1, 127); - drupal_static_reset('field_language'); - $display_language = field_language($entity_type, $entity, $this->field_name, $requested_language); - $this->assertEqual($display_language, $requested_language, t('Display language behave correctly when language fallback is disabled')); - } - - /** - * Tests field translations when creating a new revision. - */ - function testFieldFormTranslationRevisions() { - $web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content')); - $this->drupalLogin($web_user); - - // Prepare the field translations. - field_test_entity_info_translatable($this->entity_type, TRUE); - $eid = 1; - $entity = field_test_create_stub_entity($eid, $eid, $this->instance['bundle']); - $available_languages = array_flip(field_available_languages($this->entity_type, $this->field)); - unset($available_languages[LANGUAGE_NONE]); - $field_name = $this->field['field_name']; - - // Store the field translations. - $entity->is_new = TRUE; - foreach ($available_languages as $langcode => $value) { - $entity->{$field_name}[$langcode][0]['value'] = $value + 1; - } - field_test_entity_save($entity); - - // Create a new revision. - $langcode = field_valid_language(NULL); - $edit = array("{$field_name}[$langcode][0][value]" => $entity->{$field_name}[$langcode][0]['value'], 'revision' => TRUE); - $this->drupalPost('test-entity/manage/' . $eid . '/edit', $edit, t('Save')); - - // Check translation revisions. - $this->checkTranslationRevisions($eid, $eid, $available_languages); - $this->checkTranslationRevisions($eid, $eid + 1, $available_languages); - } - - /** - * Check if the field translation attached to the entity revision identified - * by the passed arguments were correctly stored. - */ - private function checkTranslationRevisions($eid, $evid, $available_languages) { - $field_name = $this->field['field_name']; - $entity = field_test_entity_test_load($eid, $evid); - foreach ($available_languages as $langcode => $value) { - $passed = isset($entity->{$field_name}[$langcode]) && $entity->{$field_name}[$langcode][0]['value'] == $value + 1; - $this->assertTrue($passed, t('The @language translation for revision @revision was correctly stored', array('@language' => $langcode, '@revision' => $entity->ftvid))); - } - } -} - -/** - * Unit test class for field bulk delete and batch purge functionality. - */ -class FieldBulkDeleteTestCase extends FieldTestCase { - protected $field; - - public static function getInfo() { - return array( - 'name' => 'Field bulk delete tests', - 'description' => 'Bulk delete fields and instances, and clean up afterwards.', - 'group' => 'Field API', - ); - } - - /** - * Convenience function for Field API tests. - * - * Given an array of potentially fully-populated entities and an - * optional field name, generate an array of stub entities of the - * same fieldable type which contains the data for the field name - * (if given). - * - * @param $entity_type - * The entity type of $entities. - * @param $entities - * An array of entities of type $entity_type. - * @param $field_name - * Optional; a field name whose data should be copied from - * $entities into the returned stub entities. - * @return - * An array of stub entities corresponding to $entities. - */ - function _generateStubEntities($entity_type, $entities, $field_name = NULL) { - $stubs = array(); - foreach ($entities as $entity) { - $stub = entity_create_stub_entity($entity_type, entity_extract_ids($entity_type, $entity)); - if (isset($field_name)) { - $stub->{$field_name} = $entity->{$field_name}; - } - $stubs[] = $stub; - } - return $stubs; - } - - function setUp() { - parent::setUp('field_test'); - - // Clean up data from previous test cases. - $this->fields = array(); - $this->instances = array(); - - // Create two bundles. - $this->bundles = array('bb_1' => 'bb_1', 'bb_2' => 'bb_2'); - foreach ($this->bundles as $name => $desc) { - field_test_create_bundle($name, $desc); - } - - // Create two fields. - $field = array('field_name' => 'bf_1', 'type' => 'test_field', 'cardinality' => 1); - $this->fields[] = field_create_field($field); - $field = array('field_name' => 'bf_2', 'type' => 'test_field', 'cardinality' => 4); - $this->fields[] = field_create_field($field); - - // For each bundle, create an instance of each field, and 10 - // entities with values for each field. - $id = 0; - $this->entity_type = 'test_entity'; - foreach ($this->bundles as $bundle) { - foreach ($this->fields as $field) { - $instance = array( - 'field_name' => $field['field_name'], - 'entity_type' => $this->entity_type, - 'bundle' => $bundle, - 'widget' => array( - 'type' => 'test_field_widget', - ) - ); - $this->instances[] = field_create_instance($instance); - } - - for ($i = 0; $i < 10; $i++) { - $entity = field_test_create_stub_entity($id, $id, $bundle); - foreach ($this->fields as $field) { - $entity->{$field['field_name']}[LANGUAGE_NONE] = $this->_generateTestFieldValues($field['cardinality']); - } - $this->entities[$id] = $entity; - field_attach_insert($this->entity_type, $entity); - $id++; - } - } - } - - /** - * Verify that deleting an instance leaves the field data items in - * the database and that the appropriate Field API functions can - * operate on the deleted data and instance. - * - * This tests how EntityFieldQuery interacts with - * field_delete_instance() and could be moved to FieldCrudTestCase, - * but depends on this class's setUp(). - */ - function testDeleteFieldInstance() { - $bundle = reset($this->bundles); - $field = reset($this->fields); - - // There are 10 entities of this bundle. - $query = new EntityFieldQuery(); - $found = $query - ->fieldCondition($field) - ->entityCondition('bundle', $bundle) - ->execute(); - $this->assertEqual(count($found['test_entity']), 10, 'Correct number of entities found before deleting'); - - // Delete the instance. - $instance = field_info_instance($this->entity_type, $field['field_name'], $bundle); - field_delete_instance($instance); - - // The instance still exists, deleted. - $instances = field_read_instances(array('field_id' => $field['id'], 'deleted' => 1), array('include_deleted' => 1, 'include_inactive' => 1)); - $this->assertEqual(count($instances), 1, 'There is one deleted instance'); - $this->assertEqual($instances[0]['bundle'], $bundle, 'The deleted instance is for the correct bundle'); - - // There are 0 entities of this bundle with non-deleted data. - $query = new EntityFieldQuery(); - $found = $query - ->fieldCondition($field) - ->entityCondition('bundle', $bundle) - ->execute(); - $this->assertTrue(!isset($found['test_entity']), 'No entities found after deleting'); - - // There are 10 entities of this bundle when deleted fields are allowed, and - // their values are correct. - $query = new EntityFieldQuery(); - $found = $query - ->fieldCondition($field) - ->entityCondition('bundle', $bundle) - ->deleted(TRUE) - ->execute(); - field_attach_load($this->entity_type, $found[$this->entity_type], FIELD_LOAD_CURRENT, array('field_id' => $field['id'], 'deleted' => 1)); - $this->assertEqual(count($found['test_entity']), 10, 'Correct number of entities found after deleting'); - foreach ($found['test_entity'] as $id => $entity) { - $this->assertEqual($this->entities[$id]->{$field['field_name']}, $entity->{$field['field_name']}, "Entity $id with deleted data loaded correctly"); - } - } - - /** - * Verify that field data items and instances are purged when an - * instance is deleted. - */ - function testPurgeInstance() { - field_test_memorize(); - - $bundle = reset($this->bundles); - $field = reset($this->fields); - - // Delete the instance. - $instance = field_info_instance($this->entity_type, $field['field_name'], $bundle); - field_delete_instance($instance); - - // No field hooks were called. - $mem = field_test_memorize(); - $this->assertEqual(count($mem), 0, 'No field hooks were called'); - - $batch_size = 2; - for ($count = 8; $count >= 0; $count -= 2) { - // Purge two entities. - field_purge_batch($batch_size); - - // There are $count deleted entities left. - $query = new EntityFieldQuery(); - $found = $query - ->fieldCondition($field) - ->entityCondition('bundle', $bundle) - ->deleted(TRUE) - ->execute(); - $this->assertEqual($count ? count($found['test_entity']) : count($found), $count, 'Correct number of entities found after purging 2'); - } - - // hook_field_delete() was called on a pseudo-entity for each entity. Each - // pseudo entity has a $field property that matches the original entity, - // but no others. - $mem = field_test_memorize(); - $this->assertEqual(count($mem['field_test_field_delete']), 10, 'hook_field_delete was called for the right number of entities'); - $stubs = $this->_generateStubEntities($this->entity_type, $this->entities, $field['field_name']); - $count = count($stubs); - foreach ($mem['field_test_field_delete'] as $args) { - $entity = $args[1]; - $this->assertEqual($stubs[$entity->ftid], $entity, 'hook_field_delete() called with the correct stub'); - unset($stubs[$entity->ftid]); - } - $this->assertEqual(count($stubs), $count-10, 'hook_field_delete was called with each entity once'); - - // The instance still exists, deleted. - $instances = field_read_instances(array('field_id' => $field['id'], 'deleted' => 1), array('include_deleted' => 1, 'include_inactive' => 1)); - $this->assertEqual(count($instances), 1, 'There is one deleted instance'); - - // Purge the instance. - field_purge_batch($batch_size); - - // The instance is gone. - $instances = field_read_instances(array('field_id' => $field['id'], 'deleted' => 1), array('include_deleted' => 1, 'include_inactive' => 1)); - $this->assertEqual(count($instances), 0, 'The instance is gone'); - - // The field still exists, not deleted, because it has a second instance. - $fields = field_read_fields(array('id' => $field['id']), array('include_deleted' => 1, 'include_inactive' => 1)); - $this->assertTrue(isset($fields[$field['id']]), 'The field exists and is not deleted'); - } - - /** - * Verify that fields are preserved and purged correctly as multiple - * instances are deleted and purged. - */ - function testPurgeField() { - $field = reset($this->fields); - - // Delete the first instance. - $instance = field_info_instance($this->entity_type, $field['field_name'], 'bb_1'); - field_delete_instance($instance); - - // Purge the data. - field_purge_batch(10); - - // Purge again to purge the instance. - field_purge_batch(0); - - // The field still exists, not deleted. - $fields = field_read_fields(array('id' => $field['id']), array('include_deleted' => 1)); - $this->assertTrue(isset($fields[$field['id']]) && !$fields[$field['id']]['deleted'], 'The field exists and is not deleted'); - - // Delete the second instance. - $instance = field_info_instance($this->entity_type, $field['field_name'], 'bb_2'); - field_delete_instance($instance); - - // Purge the data. - field_purge_batch(10); - - // The field still exists, deleted. - $fields = field_read_fields(array('id' => $field['id']), array('include_deleted' => 1)); - $this->assertTrue(isset($fields[$field['id']]) && $fields[$field['id']]['deleted'], 'The field exists and is deleted'); - - // Purge again to purge the instance and the field. - field_purge_batch(0); - - // The field is gone. - $fields = field_read_fields(array('id' => $field['id']), array('include_deleted' => 1, 'include_inactive' => 1)); - $this->assertEqual(count($fields), 0, 'The field is purged.'); - } -} - -/** - * Tests entity properties. - */ -class EntityPropertiesTestCase extends FieldTestCase { - public static function getInfo() { - return array( - 'name' => 'Entity properties', - 'description' => 'Tests entity properties.', - 'group' => 'Entity API', - ); - } - - function setUp() { - parent::setUp('field_test'); - } - - /** - * Tests label key and label callback of an entity. - */ - function testEntityLabel() { - $entity_types = array( - 'test_entity_no_label', - 'test_entity_label', - 'test_entity_label_callback', - ); - - $entity = field_test_create_stub_entity(); - - foreach ($entity_types as $entity_type) { - $label = entity_label($entity_type, $entity); - - switch ($entity_type) { - case 'test_entity_no_label': - $this->assertFalse($label, 'Entity with no label property or callback returned FALSE.'); - break; - - case 'test_entity_label': - $this->assertEqual($label, $entity->ftlabel, 'Entity with label key returned correct label.'); - break; - - case 'test_entity_label_callback': - $this->assertEqual($label, 'label callback ' . $entity->ftlabel, 'Entity with label callback returned correct label.'); - break; - } - } - } -} diff --git a/modules/field/tests/field_test.entity.inc b/modules/field/tests/field_test.entity.inc deleted file mode 100644 index b7c70a67711..00000000000 --- a/modules/field/tests/field_test.entity.inc +++ /dev/null @@ -1,494 +0,0 @@ -<?php - -/** - * @file - * Defines an entity type. - */ - -/** - * Implements hook_entity_info(). - */ -function field_test_entity_info() { - $bundles = variable_get('field_test_bundles', array('test_bundle' => array('label' => 'Test Bundle'))); - $test_entity_modes = array( - 'full' => array( - 'label' => t('Full object'), - 'custom settings' => TRUE, - ), - 'teaser' => array( - 'label' => t('Teaser'), - 'custom settings' => TRUE, - ), - ); - - return array( - 'test_entity' => array( - 'name' => t('Test Entity'), - 'fieldable' => TRUE, - 'field cache' => FALSE, - 'base table' => 'test_entity', - 'revision table' => 'test_entity_revision', - 'entity keys' => array( - 'id' => 'ftid', - 'revision' => 'ftvid', - 'bundle' => 'fttype', - ), - 'bundles' => $bundles, - 'view modes' => $test_entity_modes, - ), - // This entity type doesn't get form handling for now... - 'test_cacheable_entity' => array( - 'name' => t('Test Entity, cacheable'), - 'fieldable' => TRUE, - 'field cache' => TRUE, - 'entity keys' => array( - 'id' => 'ftid', - 'revision' => 'ftvid', - 'bundle' => 'fttype', - ), - 'bundles' => $bundles, - 'view modes' => $test_entity_modes, - ), - 'test_entity_bundle_key' => array( - 'name' => t('Test Entity with a bundle key.'), - 'base table' => 'test_entity_bundle_key', - 'fieldable' => TRUE, - 'field cache' => FALSE, - 'entity keys' => array( - 'id' => 'ftid', - 'bundle' => 'fttype', - ), - 'bundles' => array('bundle1' => array('label' => 'Bundle1'), 'bundle2' => array('label' => 'Bundle2')), - 'view modes' => $test_entity_modes, - ), - // In this case, the bundle key is not stored in the database. - 'test_entity_bundle' => array( - 'name' => t('Test Entity with a specified bundle.'), - 'base table' => 'test_entity_bundle', - 'fieldable' => TRUE, - 'controller class' => 'TestEntityBundleController', - 'field cache' => FALSE, - 'entity keys' => array( - 'id' => 'ftid', - 'bundle' => 'fttype', - ), - 'bundles' => array('test_entity_2' => array('label' => 'Test entity 2')), - 'view modes' => $test_entity_modes, - ), - // @see EntityPropertiesTestCase::testEntityLabel() - 'test_entity_no_label' => array( - 'name' => t('Test entity without label'), - 'fieldable' => TRUE, - 'field cache' => FALSE, - 'base table' => 'test_entity', - 'entity keys' => array( - 'id' => 'ftid', - 'revision' => 'ftvid', - 'bundle' => 'fttype', - ), - 'bundles' => $bundles, - 'view modes' => $test_entity_modes, - ), - 'test_entity_label' => array( - 'name' => t('Test entity label'), - 'fieldable' => TRUE, - 'field cache' => FALSE, - 'base table' => 'test_entity', - 'entity keys' => array( - 'id' => 'ftid', - 'revision' => 'ftvid', - 'bundle' => 'fttype', - 'label' => 'ftlabel', - ), - 'bundles' => $bundles, - 'view modes' => $test_entity_modes, - ), - 'test_entity_label_callback' => array( - 'name' => t('Test entity label callback'), - 'fieldable' => TRUE, - 'field cache' => FALSE, - 'base table' => 'test_entity', - 'label callback' => 'field_test_entity_label_callback', - 'entity keys' => array( - 'id' => 'ftid', - 'revision' => 'ftvid', - 'bundle' => 'fttype', - ), - 'bundles' => $bundles, - 'view modes' => $test_entity_modes, - ), - ); -} - -/** - * Implements hook_entity_info_alter(). - */ -function field_test_entity_info_alter(&$entity_info) { - // Enable/disable field_test as a translation handler. - foreach (field_test_entity_info_translatable() as $entity_type => $translatable) { - $entity_info[$entity_type]['translation']['field_test'] = $translatable; - } - // Disable locale as a translation handler. - foreach ($entity_info as $entity_type => $info) { - $entity_info[$entity_type]['translation']['locale'] = FALSE; - } -} - -/** - * Helper function to enable entity translations. - */ -function field_test_entity_info_translatable($entity_type = NULL, $translatable = NULL) { - drupal_static_reset('field_has_translation_handler'); - $stored_value = &drupal_static(__FUNCTION__, array()); - if (isset($entity_type)) { - $stored_value[$entity_type] = $translatable; - entity_info_cache_clear(); - } - return $stored_value; -} - -/** - * Creates a new bundle for test_entity entities. - * - * @param $bundle - * The machine-readable name of the bundle. - * @param $text - * The human-readable name of the bundle. If none is provided, the machine - * name will be used. - */ -function field_test_create_bundle($bundle, $text = NULL) { - $bundles = variable_get('field_test_bundles', array('test_bundle' => array('label' => 'Test Bundle'))); - $bundles += array($bundle => array('label' => $text ? $text : $bundle)); - variable_set('field_test_bundles', $bundles); - - $info = field_test_entity_info(); - foreach ($info as $type => $type_info) { - field_attach_create_bundle($type, $bundle); - } -} - -/** - * Renames a bundle for test_entity entities. - * - * @param $bundle_old - * The machine-readable name of the bundle to rename. - * @param $bundle_new - * The new machine-readable name of the bundle. - */ -function field_test_rename_bundle($bundle_old, $bundle_new) { - $bundles = variable_get('field_test_bundles', array('test_bundle' => array('label' => 'Test Bundle'))); - $bundles[$bundle_new] = $bundles[$bundle_old]; - unset($bundles[$bundle_old]); - variable_set('field_test_bundles', $bundles); - - $info = field_test_entity_info(); - foreach ($info as $type => $type_info) { - field_attach_rename_bundle($type, $bundle_old, $bundle_new); - } -} - -/** - * Deletes a bundle for test_entity objects. - * - * @param $bundle - * The machine-readable name of the bundle to delete. - */ -function field_test_delete_bundle($bundle) { - $bundles = variable_get('field_test_bundles', array('test_bundle' => array('label' => 'Test Bundle'))); - unset($bundles[$bundle]); - variable_set('field_test_bundles', $bundles); - - $info = field_test_entity_info(); - foreach ($info as $type => $type_info) { - field_attach_delete_bundle($type, $bundle); - } -} - -/** - * Creates a basic test_entity entity. - */ -function field_test_create_stub_entity($id = 1, $vid = 1, $bundle = 'test_bundle', $label = '') { - $entity = new stdClass(); - // Only set id and vid properties if they don't come as NULL (creation form). - if (isset($id)) { - $entity->ftid = $id; - } - if (isset($vid)) { - $entity->ftvid = $vid; - } - $entity->fttype = $bundle; - - $label = !empty($label) ? $label : $bundle . ' label'; - $entity->ftlabel = $label; - - return $entity; -} - -/** - * Loads a test_entity. - * - * @param $ftid - * The id of the entity to load. - * @param $ftvid - * (Optional) The revision id of the entity to load. If not specified, the - * current revision will be used. - * @return - * The loaded entity. - */ -function field_test_entity_test_load($ftid, $ftvid = NULL) { - // Load basic strucure. - $query = db_select('test_entity', 'fte', array()) - ->condition('fte.ftid', $ftid); - - if ($ftvid) { - $query->join('test_entity_revision', 'fter', 'fte.ftid = fter.ftid'); - $query->addField('fte', 'ftid'); - $query->addField('fte', 'fttype'); - $query->addField('fter', 'ftvid'); - $query->condition('fter.ftvid', $ftvid); - } - else { - $query->fields('fte'); - } - - $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]; -} - -/** - * Saves a test_entity. - * - * A new entity is created if $entity->ftid and $entity->is_new are both empty. - * A new revision is created if $entity->revision is not empty. - * - * @param $entity - * The entity to save. - */ -function field_test_entity_save(&$entity) { - field_attach_presave('test_entity', $entity); - - if (!isset($entity->is_new)) { - $entity->is_new = empty($entity->ftid); - } - - if (!$entity->is_new && !empty($entity->revision)) { - $entity->old_ftvid = $entity->ftvid; - unset($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); -} - -/** - * Menu callback: displays the 'Add new test_entity' form. - */ -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, TRUE); -} - -/** - * Menu callback: displays the 'Edit exiisting test_entity' form. - */ -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); -} - -/** - * Test_entity form. - */ -function field_test_entity_form($form, &$form_state, $entity, $add = FALSE) { - // During initial form build, add the entity to the form state for use during - // form building and processing. During a rebuild, use what is in the form - // state. - if (!isset($form_state['test_entity'])) { - $form_state['test_entity'] = $entity; - } - else { - $entity = $form_state['test_entity']; - } - - foreach (array('ftid', 'ftvid', 'fttype') as $key) { - $form[$key] = array( - '#type' => 'value', - '#value' => isset($entity->$key) ? $entity->$key : NULL, - ); - } - - // Add field widgets. - field_attach_form('test_entity', $entity, $form, $form_state); - - if (!$add) { - $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_entity_form(). - */ -function field_test_entity_form_validate($form, &$form_state) { - entity_form_field_validate('test_entity', $form, $form_state); -} - -/** - * Submit handler for field_test_entity_form(). - */ -function field_test_entity_form_submit($form, &$form_state) { - $entity = field_test_entity_form_submit_build_test_entity($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) { - $form_state['redirect'] = 'test-entity/manage/' . $entity->ftid . '/edit'; - } - else { - // Error on save. - drupal_set_message(t('The entity could not be saved.'), 'error'); - $form_state['rebuild'] = TRUE; - } -} - -/** - * Updates the form state's entity by processing this submission's values. - */ -function field_test_entity_form_submit_build_test_entity($form, &$form_state) { - $entity = $form_state['test_entity']; - entity_form_submit_build_entity('test_entity', $entity, $form, $form_state); - return $entity; -} - -/** - * Form combining two separate entities. - */ -function field_test_entity_nested_form($form, &$form_state, $entity_1, $entity_2) { - // First entity. - foreach (array('ftid', 'ftvid', 'fttype') as $key) { - $form[$key] = array( - '#type' => 'value', - '#value' => $entity_1->$key, - ); - } - field_attach_form('test_entity', $entity_1, $form, $form_state); - - // Second entity. - $form['entity_2'] = array( - '#type' => 'fieldset', - '#title' => t('Second entity'), - '#tree' => TRUE, - '#parents' => array('entity_2'), - '#weight' => 50, - ); - foreach (array('ftid', 'ftvid', 'fttype') as $key) { - $form['entity_2'][$key] = array( - '#type' => 'value', - '#value' => $entity_2->$key, - ); - } - field_attach_form('test_entity', $entity_2, $form['entity_2'], $form_state); - - $form['save'] = array( - '#type' => 'submit', - '#value' => t('Save'), - '#weight' => 100, - ); - - return $form; -} - -/** - * Validate handler for field_test_entity_nested_form(). - */ -function field_test_entity_nested_form_validate($form, &$form_state) { - $entity_1 = (object) $form_state['values']; - field_attach_form_validate('test_entity', $entity_1, $form, $form_state); - - $entity_2 = (object) $form_state['values']['entity_2']; - field_attach_form_validate('test_entity', $entity_2, $form['entity_2'], $form_state); -} - -/** - * Submit handler for field_test_entity_nested_form(). - */ -function field_test_entity_nested_form_submit($form, &$form_state) { - $entity_1 = (object) $form_state['values']; - field_attach_submit('test_entity', $entity_1, $form, $form_state); - field_test_entity_save($entity_1); - - $entity_2 = (object) $form_state['values']['entity_2']; - field_attach_submit('test_entity', $entity_2, $form['entity_2'], $form_state); - field_test_entity_save($entity_2); - - drupal_set_message(t('test_entities @id_1 and @id_2 have been updated.', array('@id_1' => $entity_1->ftid, '@id_2' => $entity_2->ftid))); -} - -/** - * Controller class for the test_entity_bundle entity type. - * - * This extends the DrupalDefaultEntityController class, adding required - * special handling for bundles (since they are not stored in the database). - */ -class TestEntityBundleController extends DrupalDefaultEntityController { - - protected function attachLoad(&$entities, $revision_id = FALSE) { - // Add bundle information. - foreach ($entities as $key => $entity) { - $entity->fttype = 'test_entity_bundle'; - $entities[$key] = $entity; - } - parent::attachLoad($entities, $revision_id); - } -} diff --git a/modules/field/tests/field_test.field.inc b/modules/field/tests/field_test.field.inc deleted file mode 100644 index b8a2939d64a..00000000000 --- a/modules/field/tests/field_test.field.inc +++ /dev/null @@ -1,379 +0,0 @@ -<?php - -/** - * @file - * Defines a field type and its formatters and widgets. - */ - -/** - * Implements hook_field_info(). - */ -function field_test_field_info() { - return array( - 'test_field' => array( - 'label' => t('Test field'), - 'description' => t('Dummy field type used for tests.'), - 'settings' => array( - 'test_field_setting' => 'dummy test string', - 'changeable' => 'a changeable field setting', - 'unchangeable' => 'an unchangeable field setting', - ), - 'instance_settings' => array( - 'test_instance_setting' => 'dummy test string', - 'test_hook_field_load' => FALSE, - ), - 'default_widget' => 'test_field_widget', - 'default_formatter' => 'field_test_default', - ), - 'shape' => array( - 'label' => t('Shape'), - 'description' => t('Another dummy field type.'), - 'settings' => array(), - 'instance_settings' => array(), - 'default_widget' => 'test_field_widget', - 'default_formatter' => 'field_test_default', - ), - 'hidden_test_field' => array( - 'no_ui' => TRUE, - 'label' => t('Hidden from UI test field'), - 'description' => t('Dummy hidden field type used for tests.'), - 'settings' => array(), - 'instance_settings' => array(), - 'default_widget' => 'test_field_widget', - 'default_formatter' => 'field_test_default', - ), - ); -} - -/** - * Implements hook_field_update_forbid(). - */ -function field_test_field_update_forbid($field, $prior_field, $has_data) { - if ($field['type'] == 'test_field' && $field['settings']['unchangeable'] != $prior_field['settings']['unchangeable']) { - throw new FieldException("field_test 'unchangeable' setting cannot be changed'"); - } -} - -/** - * Implements hook_field_load(). - */ -function field_test_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) { - foreach ($items as $id => $item) { - // To keep the test non-intrusive, only act for instances with the - // test_hook_field_load setting explicitly set to TRUE. - if ($instances[$id]['settings']['test_hook_field_load']) { - foreach ($item as $delta => $value) { - // Don't add anything on empty values. - if ($value) { - $items[$id][$delta]['additional_key'] = 'additional_value'; - } - } - } - } -} - -/** - * Implements hook_field_validate(). - * - * Possible error codes: - * - 'field_test_invalid': The value is invalid. - */ -function field_test_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) { - foreach ($items as $delta => $item) { - if ($item['value'] == -1) { - $errors[$field['field_name']][$langcode][$delta][] = array( - 'error' => 'field_test_invalid', - 'message' => t('%name does not accept the value -1.', array('%name' => $instance['label'])), - ); - } - } -} - -/** - * Implements hook_field_is_empty(). - */ -function field_test_field_is_empty($item, $field) { - return empty($item['value']); -} - -/** - * Implements hook_field_settings_form(). - */ -function field_test_field_settings_form($field, $instance, $has_data) { - $settings = $field['settings']; - - $form['test_field_setting'] = array( - '#type' => 'textfield', - '#title' => t('Field test field setting'), - '#default_value' => $settings['test_field_setting'], - '#required' => FALSE, - '#description' => t('A dummy form element to simulate field setting.'), - ); - - return $form; -} - -/** - * Implements hook_field_instance_settings_form(). - */ -function field_test_field_instance_settings_form($field, $instance) { - $settings = $instance['settings']; - - $form['test_instance_setting'] = array( - '#type' => 'textfield', - '#title' => t('Field test field instance setting'), - '#default_value' => $settings['test_instance_setting'], - '#required' => FALSE, - '#description' => t('A dummy form element to simulate field instance setting.'), - ); - - return $form; -} - -/** - * Implements hook_field_widget_info(). - */ -function field_test_field_widget_info() { - return array( - 'test_field_widget' => array( - 'label' => t('Test field'), - 'field types' => array('test_field', 'hidden_test_field'), - 'settings' => array('test_widget_setting' => 'dummy test string'), - ), - 'test_field_widget_multiple' => array( - 'label' => t('Test field 1'), - 'field types' => array('test_field'), - 'settings' => array('test_widget_setting_multiple' => 'dummy test string'), - 'behaviors' => array( - 'multiple values' => FIELD_BEHAVIOR_CUSTOM, - ), - ), - ); -} - -/** - * Implements hook_field_widget_form(). - */ -function field_test_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { - switch ($instance['widget']['type']) { - case 'test_field_widget': - $element += array( - '#type' => 'textfield', - '#default_value' => isset($items[$delta]['value']) ? $items[$delta]['value'] : '', - ); - return array('value' => $element); - - case 'test_field_widget_multiple': - $values = array(); - foreach ($items as $delta => $value) { - $values[] = $value['value']; - } - $element += array( - '#type' => 'textfield', - '#default_value' => implode(', ', $values), - '#element_validate' => array('field_test_widget_multiple_validate'), - ); - return $element; - } -} - -/** - * Form element validation handler for 'test_field_widget_multiple' widget. - */ -function field_test_widget_multiple_validate($element, &$form_state) { - $values = array_map('trim', explode(',', $element['#value'])); - $items = array(); - foreach ($values as $value) { - $items[] = array('value' => $value); - } - form_set_value($element, $items, $form_state); -} - -/** - * Implements hook_field_widget_error(). - */ -function field_test_field_widget_error($element, $error, $form, &$form_state) { - // @todo No easy way to differenciate widget types, we should receive it as a - // parameter. - if (isset($element['value'])) { - // Widget is test_field_widget. - $error_element = $element['value']; - } - else { - // Widget is test_field_widget_multiple. - $error_element = $element; - } - - form_error($error_element, $error['message']); -} - -/** - * Implements hook_field_widget_settings_form(). - */ -function field_test_field_widget_settings_form($field, $instance) { - $widget = $instance['widget']; - $settings = $widget['settings']; - - $form['test_widget_setting'] = array( - '#type' => 'textfield', - '#title' => t('Field test field widget setting'), - '#default_value' => $settings['test_widget_setting'], - '#required' => FALSE, - '#description' => t('A dummy form element to simulate field widget setting.'), - ); - - return $form; -} - -/** - * Implements hook_field_formatter_info(). - */ -function field_test_field_formatter_info() { - return array( - 'field_test_default' => array( - 'label' => t('Default'), - 'description' => t('Default formatter'), - 'field types' => array('test_field'), - 'settings' => array( - 'test_formatter_setting' => 'dummy test string', - ), - ), - 'field_test_multiple' => array( - 'label' => t('Multiple'), - 'description' => t('Multiple formatter'), - 'field types' => array('test_field'), - 'settings' => array( - 'test_formatter_setting_multiple' => 'dummy test string', - ), - ), - 'field_test_with_prepare_view' => array( - 'label' => t('Tests hook_field_formatter_prepare_view()'), - 'field types' => array('test_field'), - 'settings' => array( - 'test_formatter_setting_additional' => 'dummy test string', - ), - ), - ); -} - -/** - * Implements hook_field_formatter_settings_form(). - */ -function field_test_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) { - $display = $instance['display'][$view_mode]; - $settings = $display['settings']; - - $element = array(); - - // The name of the setting depends on the formatter type. - $map = array( - 'field_test_default' => 'test_formatter_setting', - 'field_test_multiple' => 'test_formatter_setting_multiple', - 'field_test_with_prepare_view' => 'test_formatter_setting_additional', - ); - - if (isset($map[$display['type']])) { - $name = $map[$display['type']]; - - $element[$name] = array( - '#title' => t('Setting'), - '#type' => 'textfield', - '#size' => 20, - '#default_value' => $settings[$name], - '#required' => TRUE, - ); - } - - return $element; -} - -/** - * Implements hook_field_formatter_settings_summary(). - */ -function field_test_field_formatter_settings_summary($field, $instance, $view_mode) { - $display = $instance['display'][$view_mode]; - $settings = $display['settings']; - - $summary = ''; - - // The name of the setting depends on the formatter type. - $map = array( - 'field_test_default' => 'test_formatter_setting', - 'field_test_multiple' => 'test_formatter_setting_multiple', - 'field_test_with_prepare_view' => 'test_formatter_setting_additional', - ); - - if (isset($map[$display['type']])) { - $name = $map[$display['type']]; - $summary = t('@setting: @value', array('@setting' => $name, '@value' => $settings[$name])); - } - - return $summary; -} - -/** - * Implements hook_field_formatter_prepare_view(). - */ -function field_test_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) { - foreach ($items as $id => $item) { - // To keep the test non-intrusive, only act on the - // 'field_test_with_prepare_view' formatter. - if ($displays[$id]['type'] == 'field_test_with_prepare_view') { - foreach ($item as $delta => $value) { - // Don't add anything on empty values. - if ($value) { - $items[$id][$delta]['additional_formatter_value'] = $value['value'] + 1; - } - } - } - } -} - -/** - * Implements hook_field_formatter_view(). - */ -function field_test_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) { - $element = array(); - $settings = $display['settings']; - - switch ($display['type']) { - case 'field_test_default': - foreach ($items as $delta => $item) { - $element[$delta] = array('#markup' => $settings['test_formatter_setting'] . '|' . $item['value']); - } - break; - - case 'field_test_with_prepare_view': - foreach ($items as $delta => $item) { - $element[$delta] = array('#markup' => $settings['test_formatter_setting_additional'] . '|' . $item['value'] . '|' . $item['additional_formatter_value']); - } - break; - - case 'field_test_multiple': - $array = array(); - foreach ($items as $delta => $item) { - $array[] = $delta . ':' . $item['value']; - } - $element[0] = array('#markup' => $settings['test_formatter_setting_multiple'] . '|' . implode('|', $array)); - break; - } - - return $element; -} - -/** - * Sample 'default value' callback. - */ -function field_test_default_value($entity_type, $entity, $field, $instance) { - return array(array('value' => 99)); -} - -/** - * Implements hook_field_access(). - */ -function field_test_field_access($op, $field, $entity_type, $entity, $account) { - if ($field['field_name'] == "field_no_{$op}_access") { - return FALSE; - } - return TRUE; -} diff --git a/modules/field/tests/field_test.info b/modules/field/tests/field_test.info deleted file mode 100644 index 5fc9b27d862..00000000000 --- a/modules/field/tests/field_test.info +++ /dev/null @@ -1,7 +0,0 @@ -name = "Field API Test" -description = "Support module for the Field API tests." -core = 8.x -package = Testing -files[] = field_test.entity.inc -version = VERSION -hidden = TRUE diff --git a/modules/field/tests/field_test.install b/modules/field/tests/field_test.install deleted file mode 100644 index 59575611033..00000000000 --- a/modules/field/tests/field_test.install +++ /dev/null @@ -1,150 +0,0 @@ -<?php - -/** - * @file - * Install, update and uninstall functions for the field_test module. - */ - -/** - * Implements hook_install(). - */ -function field_test_install() { - // hook_entity_info_alter() needs to be executed as last. - db_update('system') - ->fields(array('weight' => 1)) - ->condition('name', 'field_test') - ->execute(); -} - -/** - * Implements 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' => '', - ), - 'ftlabel' => array( - 'description' => 'The label of this test_entity.', - 'type' => 'varchar', - 'length' => 255, - 'not null' => TRUE, - 'default' => '', - ), - ), - 'unique keys' => array( - 'ftvid' => array('ftvid'), - ), - 'primary key' => array('ftid'), - ); - $schema['test_entity_bundle_key'] = array( - 'description' => 'The base table for test entities with a bundle key.', - 'fields' => array( - 'ftid' => array( - 'description' => 'The primary indentifier for a test_entity_bundle_key.', - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'default' => 0, - ), - 'fttype' => array( - 'description' => 'The type of this test_entity.', - 'type' => 'varchar', - 'length' => 32, - 'not null' => FALSE, - 'default' => '', - ), - ), - ); - $schema['test_entity_bundle'] = array( - 'description' => 'The base table for test entities with a bundle.', - 'fields' => array( - 'ftid' => array( - 'description' => 'The primary indentifier for a test_entity_bundle.', - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'default' => 0, - ), - ), - ); - $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; -} - -/** - * Implements hook_field_schema(). - */ -function field_test_field_schema($field) { - if ($field['type'] == 'test_field') { - return array( - 'columns' => array( - 'value' => array( - 'type' => 'int', - 'size' => 'medium', - 'not null' => FALSE, - ), - ), - 'indexes' => array( - 'value' => array('value'), - ), - ); - } - else { - return array( - 'columns' => array( - 'shape' => array( - 'type' => 'varchar', - 'length' => 32, - 'not null' => FALSE, - ), - 'color' => array( - 'type' => 'varchar', - 'length' => 32, - 'not null' => FALSE, - ), - ), - ); - } -} diff --git a/modules/field/tests/field_test.module b/modules/field/tests/field_test.module deleted file mode 100644 index 40752962b63..00000000000 --- a/modules/field/tests/field_test.module +++ /dev/null @@ -1,252 +0,0 @@ -<?php - -/** - * @file - * Helper module for the Field API tests. - * - * The module defines - * - an entity type (field_test.entity.inc) - * - a field type and its formatters and widgets (field_test.field.inc) - * - a field storage backend (field_test.storage.inc) - * - * The main field_test.module file implements generic hooks and provides some - * test helper functions - */ - -require_once DRUPAL_ROOT . '/modules/field/tests/field_test.entity.inc'; -require_once DRUPAL_ROOT . '/modules/field/tests/field_test.field.inc'; -require_once DRUPAL_ROOT . '/modules/field/tests/field_test.storage.inc'; - -/** - * Implements hook_permission(). - */ -function field_test_permission() { - $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; -} - -/** - * Implements hook_menu(). - */ -function field_test_menu() { - $items = array(); - $bundles = field_info_bundles('test_entity'); - - foreach ($bundles as $bundle_name => $bundle_info) { - $bundle_url_str = str_replace('_', '-', $bundle_name); - $items['test-entity/add/' . $bundle_url_str] = array( - 'title' => t('Add %bundle test_entity', array('%bundle' => $bundle_info['label'])), - 'page callback' => 'field_test_entity_add', - 'page arguments' => array(2), - 'access arguments' => array('administer field_test content'), - 'type' => MENU_NORMAL_ITEM, - ); - } - $items['test-entity/manage/%field_test_entity_test/edit'] = array( - 'title' => 'Edit test entity', - 'page callback' => 'field_test_entity_edit', - 'page arguments' => array(2), - 'access arguments' => array('administer field_test content'), - 'type' => MENU_NORMAL_ITEM, - ); - - $items['test-entity/nested/%field_test_entity_test/%field_test_entity_test'] = array( - 'title' => 'Nested entity form', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('field_test_entity_nested_form', 2, 3), - 'access arguments' => array('administer field_test content'), - 'type' => MENU_NORMAL_ITEM, - ); - - return $items; -} - -/** - * Generic op to test _field_invoke behavior. - * - * This simulates a field operation callback to be invoked by _field_invoke(). - */ -function field_test_field_test_op($entity_type, $entity, $field, $instance, $langcode, &$items) { - return array($langcode => hash('sha256', serialize(array($entity_type, $entity, $field['field_name'], $langcode, $items)))); -} - -/** - * Generic op to test _field_invoke_multiple behavior. - * - * This simulates a multiple field operation callback to be invoked by - * _field_invoke_multiple(). - */ -function field_test_field_test_op_multiple($entity_type, $entities, $field, $instances, $langcode, &$items) { - $result = array(); - foreach ($entities as $id => $entity) { - // Entities, instances and items are assumed to be consistently grouped by - // language. To verify this we try to access all the passed data structures - // by entity id. If they are grouped correctly, one entity, one instance and - // one array of items should be available for each entity id. - $field_name = $instances[$id]['field_name']; - $result[$id] = array($langcode => hash('sha256', serialize(array($entity_type, $entity, $field_name, $langcode, $items[$id])))); - } - return $result; -} - -/** - * Implements hook_field_available_languages_alter(). - */ -function field_test_field_available_languages_alter(&$languages, $context) { - if (variable_get('field_test_field_available_languages_alter', FALSE)) { - // Add an unavailable language. - $languages[] = 'xx'; - // Remove an available language. - $index = array_search('en', $languages); - unset($languages[$index]); - } -} - -/** - * Implements hook_field_language_alter(). - */ -function field_test_field_language_alter(&$display_language, $context) { - if (variable_get('field_test_language_fallback', TRUE)) { - locale_field_language_fallback($display_language, $context['entity'], $context['language']); - } -} - -/** - * Store and retrieve keyed data for later verification by unit tests. - * - * This function is a simple in-memory key-value store with the - * distinction that it stores all values for a given key instead of - * just the most recently set value. field_test module hooks call - * this function to record their arguments, keyed by hook name. The - * unit tests later call this function to verify that the correct - * hooks were called and were passed the correct arguments. - * - * This function ignores all calls until the first time it is called - * with $key of NULL. Each time it is called with $key of NULL, it - * erases all previously stored data from its internal cache, but also - * returns the previously stored data to the caller. A typical usage - * scenario is: - * - * @code - * // calls to field_test_memorize() here are ignored - * - * // turn on memorization - * field_test_memorize(); - * - * // call some Field API functions that invoke field_test hooks - * $field = field_create_field(...); - * - * // retrieve and reset the memorized hook call data - * $mem = field_test_memorize(); - * - * // make sure hook_field_create_field() is invoked correctly - * assertEqual(count($mem['field_test_field_create_field']), 1); - * assertEqual($mem['field_test_field_create_field'][0], array($field)); - * @endcode - * - * @param $key - * The key under which to store to $value, or NULL as described above. - * @param $value - * A value to store for $key. - * @return - * An array mapping each $key to an array of each $value passed in - * for that key. - */ -function field_test_memorize($key = NULL, $value = NULL) { - $memorize = &drupal_static(__FUNCTION__, NULL); - - if (!isset($key)) { - $return = $memorize; - $memorize = array(); - return $return; - } - if (is_array($memorize)) { - $memorize[$key][] = $value; - } -} - -/** - * Memorize calls to hook_field_create_field(). - */ -function field_test_field_create_field($field) { - $args = func_get_args(); - field_test_memorize(__FUNCTION__, $args); -} - -/** - * Memorize calls to hook_field_insert(). - */ -function field_test_field_insert($entity_type, $entity, $field, $instance, $items) { - $args = func_get_args(); - field_test_memorize(__FUNCTION__, $args); -} - -/** - * Memorize calls to hook_field_update(). - */ -function field_test_field_update($entity_type, $entity, $field, $instance, $items) { - $args = func_get_args(); - field_test_memorize(__FUNCTION__, $args); -} - -/** - * Memorize calls to hook_field_delete(). - */ -function field_test_field_delete($entity_type, $entity, $field, $instance, $items) { - $args = func_get_args(); - field_test_memorize(__FUNCTION__, $args); -} - -/** - * Implements hook_entity_query_alter(). - */ -function field_test_entity_query_alter(&$query) { - if (!empty($query->alterMyExecuteCallbackPlease)) { - $query->executeCallback = 'field_test_dummy_field_storage_query'; - } -} - -/** - * Pseudo-implements hook_field_storage_query(). - */ -function field_test_dummy_field_storage_query(EntityFieldQuery $query) { - // Return dummy values that will be checked by the test. - return array( - 'user' => array( - 1 => entity_create_stub_entity('user', array(1, NULL, NULL)), - ), - ); -} - -/** - * Entity label callback. - * - * @param $entity_type - * The entity type. - * @param $entity - * The entity object. - * - * @return - * The label of the entity prefixed with "label callback". - */ -function field_test_entity_label_callback($entity_type, $entity) { - return 'label callback ' . $entity->ftlabel; -} - -/** - * Implements hook_field_attach_view_alter(). - */ -function field_test_field_attach_view_alter(&$output, $context) { - if (!empty($context['display']['settings']['alter'])) { - $output['test_field'][] = array('#markup' => 'field_test_field_attach_view_alter'); - } -} diff --git a/modules/field/tests/field_test.storage.inc b/modules/field/tests/field_test.storage.inc deleted file mode 100644 index a26af176552..00000000000 --- a/modules/field/tests/field_test.storage.inc +++ /dev/null @@ -1,473 +0,0 @@ -<?php - -/** - * @file - * Defines a field storage backend. - */ - - -/** - * Implements hook_field_storage_info(). - */ -function field_test_field_storage_info() { - return array( - 'field_test_storage' => array( - 'label' => t('Test storage'), - 'description' => t('Dummy test storage backend. Stores field values in the variable table.'), - ), - 'field_test_storage_failure' => array( - 'label' => t('Test storage failure'), - 'description' => t('Dummy test storage backend. Always fails to create fields.'), - ), - ); -} - -/** - * Implements hook_field_storage_details(). - */ -function field_test_field_storage_details($field) { - $details = array(); - - // Add field columns. - $columns = array(); - foreach ((array) $field['columns'] as $column_name => $attributes) { - $columns[$column_name] = $column_name; - } - return array( - 'drupal_variables' => array( - 'field_test_storage_data[FIELD_LOAD_CURRENT]' => $columns, - 'field_test_storage_data[FIELD_LOAD_REVISION]' => $columns, - ), - ); -} - -/** - * Implements hook_field_storage_details_alter(). - * - * @see FieldAttachStorageTestCase::testFieldStorageDetailsAlter() - */ -function field_test_field_storage_details_alter(&$details, $field) { - - // For testing, storage details are changed only because of the field name. - if ($field['field_name'] == 'field_test_change_my_details') { - $columns = array(); - foreach ((array) $field['columns'] as $column_name => $attributes) { - $columns[$column_name] = $column_name; - } - $details['drupal_variables'] = array( - FIELD_LOAD_CURRENT => array( - 'moon' => $columns, - ), - FIELD_LOAD_REVISION => array( - 'mars' => $columns, - ), - ); - } -} - -/** - * Helper function: stores or retrieves data from the 'storage backend'. - */ -function _field_test_storage_data($data = NULL) { - if (!isset($data)) { - return variable_get('field_test_storage_data', array()); - } - else { - variable_set('field_test_storage_data', $data); - } -} - -/** - * Implements hook_field_storage_load(). - */ -function field_test_field_storage_load($entity_type, $entities, $age, $fields, $options) { - $data = _field_test_storage_data(); - - $load_current = $age == FIELD_LOAD_CURRENT; - - foreach ($fields as $field_id => $ids) { - $field = field_info_field_by_id($field_id); - $field_name = $field['field_name']; - $field_data = $data[$field['id']]; - $sub_table = $load_current ? 'current' : 'revisions'; - $delta_count = array(); - foreach ($field_data[$sub_table] as $row) { - if ($row->type == $entity_type && (!$row->deleted || $options['deleted'])) { - if (($load_current && in_array($row->entity_id, $ids)) || (!$load_current && in_array($row->revision_id, $ids))) { - if (in_array($row->language, field_available_languages($entity_type, $field))) { - if (!isset($delta_count[$row->entity_id][$row->language])) { - $delta_count[$row->entity_id][$row->language] = 0; - } - if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || $delta_count[$row->entity_id][$row->language] < $field['cardinality']) { - $item = array(); - foreach ($field['columns'] as $column => $attributes) { - $item[$column] = $row->{$column}; - } - $entities[$row->entity_id]->{$field_name}[$row->language][] = $item; - $delta_count[$row->entity_id][$row->language]++; - } - } - } - } - } - } -} - -/** - * Implements hook_field_storage_write(). - */ -function field_test_field_storage_write($entity_type, $entity, $op, $fields) { - $data = _field_test_storage_data(); - - list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); - - foreach ($fields as $field_id) { - $field = field_info_field_by_id($field_id); - $field_name = $field['field_name']; - $field_data = &$data[$field_id]; - - $all_languages = field_available_languages($entity_type, $field); - $field_languages = array_intersect($all_languages, array_keys((array) $entity->$field_name)); - - // Delete and insert, rather than update, in case a value was added. - if ($op == FIELD_STORAGE_UPDATE) { - // Delete languages present in the incoming $entity->$field_name. - // Delete all languages if $entity->$field_name is empty. - $languages = !empty($entity->$field_name) ? $field_languages : $all_languages; - if ($languages) { - foreach ($field_data['current'] as $key => $row) { - if ($row->type == $entity_type && $row->entity_id == $id && in_array($row->language, $languages)) { - unset($field_data['current'][$key]); - } - } - if (isset($vid)) { - foreach ($field_data['revisions'] as $key => $row) { - if ($row->type == $entity_type && $row->revision_id == $vid) { - unset($field_data['revisions'][$key]); - } - } - } - } - } - - foreach ($field_languages as $langcode) { - $items = (array) $entity->{$field_name}[$langcode]; - $delta_count = 0; - foreach ($items as $delta => $item) { - $row = (object) array( - 'field_id' => $field_id, - 'type' => $entity_type, - 'entity_id' => $id, - 'revision_id' => $vid, - 'bundle' => $bundle, - 'delta' => $delta, - 'deleted' => FALSE, - 'language' => $langcode, - ); - foreach ($field['columns'] as $column => $attributes) { - $row->{$column} = isset($item[$column]) ? $item[$column] : NULL; - } - - $field_data['current'][] = $row; - if (isset($vid)) { - $field_data['revisions'][] = $row; - } - - if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED && ++$delta_count == $field['cardinality']) { - break; - } - } - } - } - - _field_test_storage_data($data); -} - -/** - * Implements hook_field_storage_delete(). - */ -function field_test_field_storage_delete($entity_type, $entity, $fields) { - list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); - - // Note: reusing field_test_storage_purge(), like field_sql_storage.module - // does, is highly inefficient in our case... - foreach (field_info_instances($bundle) as $instance) { - if (isset($fields[$instance['field_id']])) { - $field = field_info_field_by_id($instance['field_id']); - field_test_field_storage_purge($entity_type, $entity, $field, $instance); - } - } -} - -/** - * Implements hook_field_storage_purge(). - */ -function field_test_field_storage_purge($entity_type, $entity, $field, $instance) { - $data = _field_test_storage_data(); - - list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); - - $field_data = &$data[$field['id']]; - foreach (array('current', 'revisions') as $sub_table) { - foreach ($field_data[$sub_table] as $key => $row) { - if ($row->type == $entity_type && $row->entity_id == $id) { - unset($field_data[$sub_table][$key]); - } - } - } - - _field_test_storage_data($data); -} - -/** - * Implements hook_field_storage_delete_revision(). - */ -function field_test_field_storage_delete_revision($entity_type, $entity, $fields) { - $data = _field_test_storage_data(); - - list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); - foreach ($fields as $field_id) { - $field_data = &$data[$field_id]; - foreach (array('current', 'revisions') as $sub_table) { - foreach ($field_data[$sub_table] as $key => $row) { - if ($row->type == $entity_type && $row->entity_id == $id && $row->revision_id == $vid) { - unset($field_data[$sub_table][$key]); - } - } - } - } - - _field_test_storage_data($data); -} - -/** - * Implements hook_field_storage_query(). - */ -function field_test_field_storage_query($field_id, $conditions, $count, &$cursor = NULL, $age) { - $data = _field_test_storage_data(); - - $load_current = $age == FIELD_LOAD_CURRENT; - - $field = field_info_field_by_id($field_id); - $field_columns = array_keys($field['columns']); - - $field_data = $data[$field['id']]; - $sub_table = $load_current ? 'current' : 'revisions'; - // We need to sort records by entity type and entity id. - usort($field_data[$sub_table], '_field_test_field_storage_query_sort_helper'); - - // Initialize results array. - $return = array(); - $entity_count = 0; - $rows_count = 0; - $rows_total = count($field_data[$sub_table]); - $skip = $cursor; - $skipped = 0; - - foreach ($field_data[$sub_table] as $row) { - if ($count != FIELD_QUERY_NO_LIMIT && $entity_count >= $count) { - break; - } - - if ($row->field_id == $field['id']) { - $match = TRUE; - $condition_deleted = FALSE; - // Add conditions. - foreach ($conditions as $condition) { - @list($column, $value, $operator) = $condition; - if (empty($operator)) { - $operator = is_array($value) ? 'IN' : '='; - } - switch ($operator) { - case '=': - $match = $match && $row->{$column} == $value; - break; - case '<>': - case '<': - case '<=': - case '>': - case '>=': - eval('$match = $match && ' . $row->{$column} . ' ' . $operator . ' '. $value); - break; - case 'IN': - $match = $match && in_array($row->{$column}, $value); - break; - case 'NOT IN': - $match = $match && !in_array($row->{$column}, $value); - break; - case 'BETWEEN': - $match = $match && $row->{$column} >= $value[0] && $row->{$column} <= $value[1]; - break; - case 'STARTS_WITH': - case 'ENDS_WITH': - case 'CONTAINS': - // Not supported. - $match = FALSE; - break; - } - // Track condition on 'deleted'. - if ($column == 'deleted') { - $condition_deleted = TRUE; - } - } - - // Exclude deleted data unless we have a condition on it. - if (!$condition_deleted && $row->deleted) { - $match = FALSE; - } - - if ($match) { - if (!isset($skip) || $skipped >= $skip) { - $cursor++; - // If querying all revisions and the entity type has revisions, we need - // to key the results by revision_ids. - $entity_type = entity_get_info($row->type); - $id = ($load_current || empty($entity_type['entity keys']['revision'])) ? $row->entity_id : $row->revision_id; - - if (!isset($return[$row->type][$id])) { - $return[$row->type][$id] = entity_create_stub_entity($row->type, array($row->entity_id, $row->revision_id, $row->bundle)); - $entity_count++; - } - } - else { - $skipped++; - } - } - } - $rows_count++; - - // The query is complete if we walked the whole array. - if ($count != FIELD_QUERY_NO_LIMIT && $rows_count >= $rows_total) { - $cursor = FIELD_QUERY_COMPLETE; - } - } - - return $return; -} - -/** - * Sort helper for field_test_field_storage_query(). - * - * Sorts by entity type and entity id. - */ -function _field_test_field_storage_query_sort_helper($a, $b) { - if ($a->type == $b->type) { - if ($a->entity_id == $b->entity_id) { - return 0; - } - else { - return $a->entity_id < $b->entity_id ? -1 : 1; - } - } - else { - return $a->type < $b->type ? -1 : 1; - } -} - -/** - * Implements hook_field_storage_create_field(). - */ -function field_test_field_storage_create_field($field) { - if ($field['storage']['type'] == 'field_test_storage_failure') { - throw new Exception('field_test_storage_failure engine always fails to create fields'); - } - - $data = _field_test_storage_data(); - - $data[$field['id']] = array( - 'current' => array(), - 'revisions' => array(), - ); - - _field_test_storage_data($data); -} - -/** - * Implements hook_field_storage_delete_field(). - */ -function field_test_field_storage_delete_field($field) { - $data = _field_test_storage_data(); - - $field_data = &$data[$field['id']]; - foreach (array('current', 'revisions') as $sub_table) { - foreach ($field_data[$sub_table] as &$row) { - $row->deleted = TRUE; - } - } - - _field_test_storage_data($data); -} - -/** - * Implements hook_field_storage_delete_instance(). - */ -function field_test_field_storage_delete_instance($instance) { - $data = _field_test_storage_data(); - - $field = field_info_field($instance['field_name']); - $field_data = &$data[$field['id']]; - foreach (array('current', 'revisions') as $sub_table) { - foreach ($field_data[$sub_table] as &$row) { - if ($row->bundle == $instance['bundle']) { - $row->deleted = TRUE; - } - } - } - - _field_test_storage_data($data); -} - -/** - * Implements hook_field_attach_create_bundle(). - */ -function field_test_field_attach_create_bundle($bundle) { - // We don't need to do anything here. -} - -/** - * Implements hook_field_attach_rename_bundle(). - */ -function field_test_field_attach_rename_bundle($bundle_old, $bundle_new) { - $data = _field_test_storage_data(); - - // We need to account for deleted or inactive fields and instances. - $instances = field_read_instances(array('bundle' => $bundle_new), array('include_deleted' => TRUE, 'include_inactive' => TRUE)); - foreach ($instances as $field_name => $instance) { - $field = field_info_field_by_id($instance['field_id']); - if ($field['storage']['type'] == 'field_test_storage') { - $field_data = &$data[$field['id']]; - foreach (array('current', 'revisions') as $sub_table) { - foreach ($field_data[$sub_table] as &$row) { - if ($row->bundle == $bundle_old) { - $row->bundle = $bundle_new; - } - } - } - } - } - - _field_test_storage_data($data); -} - -/** - * Implements hook_field_attach_delete_bundle(). - */ -function field_test_field_attach_delete_bundle($entity_type, $bundle, $instances) { - $data = _field_test_storage_data(); - - foreach ($instances as $field_name => $instance) { - $field = field_info_field($field_name); - if ($field['storage']['type'] == 'field_test_storage') { - $field_data = &$data[$field['id']]; - foreach (array('current', 'revisions') as $sub_table) { - foreach ($field_data[$sub_table] as &$row) { - if ($row->bundle == $bundle_old) { - $row->deleted = TRUE; - } - } - } - } - } - - _field_test_storage_data($data); -} diff --git a/modules/field/theme/field-rtl.css b/modules/field/theme/field-rtl.css deleted file mode 100644 index 5d35a86a11c..00000000000 --- a/modules/field/theme/field-rtl.css +++ /dev/null @@ -1,14 +0,0 @@ - -form .field-multiple-table th.field-label { - padding-right: 0; -} -form .field-multiple-table td.field-multiple-drag { - padding-left: 0; -} -form .field-multiple-table td.field-multiple-drag a.tabledrag-handle{ - padding-left: .5em; -} -.field-label-inline .field-label, -.field-label-inline .field-items { - float: right; -} diff --git a/modules/field/theme/field.css b/modules/field/theme/field.css deleted file mode 100644 index 9eba32f0b7f..00000000000 --- a/modules/field/theme/field.css +++ /dev/null @@ -1,28 +0,0 @@ - -/* Field display */ -.field .field-label { - font-weight: bold; -} -.field-label-inline .field-label, -.field-label-inline .field-items { - float:left; /*LTR*/ -} - -/* Form display */ -form .field-multiple-table { - margin: 0; -} -form .field-multiple-table th.field-label { - padding-left: 0; /*LTR*/ -} -form .field-multiple-table td.field-multiple-drag { - width: 30px; - padding-right: 0; /*LTR*/ -} -form .field-multiple-table td.field-multiple-drag a.tabledrag-handle { - padding-right: .5em; /*LTR*/ -} - -form .field-add-more-submit { - margin: .5em 0 0; -} diff --git a/modules/field/theme/field.tpl.php b/modules/field/theme/field.tpl.php deleted file mode 100644 index 9e76e3b9c12..00000000000 --- a/modules/field/theme/field.tpl.php +++ /dev/null @@ -1,60 +0,0 @@ -<?php - -/** - * @file field.tpl.php - * Default template implementation to display the value of a field. - * - * This file is not used and is here as a starting point for customization only. - * @see theme_field() - * - * Available variables: - * - $items: An array of field values. Use render() to output them. - * - $label: The item label. - * - $label_hidden: Whether the label display is set to 'hidden'. - * - $classes: String of classes that can be used to style contextually through - * CSS. It can be manipulated through the variable $classes_array from - * preprocess functions. The default values can be one or more of the - * following: - * - field: The current template type, i.e., "theming hook". - * - field-name-[field_name]: The current field name. For example, if the - * field name is "field_description" it would result in - * "field-name-field-description". - * - field-type-[field_type]: The current field type. For example, if the - * field type is "text" it would result in "field-type-text". - * - field-label-[label_display]: The current label position. For example, if - * the label position is "above" it would result in "field-label-above". - * - * Other variables: - * - $element['#object']: The entity to which the field is attached. - * - $element['#view_mode']: View mode, e.g. 'full', 'teaser'... - * - $element['#field_name']: The field name. - * - $element['#field_type']: The field type. - * - $element['#field_language']: The field language. - * - $element['#field_translatable']: Whether the field is translatable or not. - * - $element['#label_display']: Position of label display, inline, above, or - * hidden. - * - $field_name_css: The css-compatible field name. - * - $field_type_css: The css-compatible field type. - * - $classes_array: Array of html class attribute values. It is flattened - * into a string within the variable $classes. - * - * @see template_preprocess_field() - * @see theme_field() - */ -?> -<!-- -THIS FILE IS NOT USED AND IS HERE AS A STARTING POINT FOR CUSTOMIZATION ONLY. -See http://api.drupal.org/api/function/theme_field/7 for details. -After copying this file to your theme's folder and customizing it, remove this -HTML comment. ---> -<div class="<?php print $classes; ?> clearfix"<?php print $attributes; ?>> - <?php if (!$label_hidden): ?> - <div class="field-label"<?php print $title_attributes; ?>><?php print $label ?>: </div> - <?php endif; ?> - <div class="field-items"<?php print $content_attributes; ?>> - <?php foreach ($items as $delta => $item): ?> - <div class="field-item <?php print $delta % 2 ? 'odd' : 'even'; ?>"<?php print $item_attributes[$delta]; ?>><?php print render($item); ?></div> - <?php endforeach; ?> - </div> -</div> |