mirror of
https://github.com/tag1consulting/d7_to_d10_migration.git
synced 2025-05-30 00:55:12 +00:00
Initial commit
This commit is contained in:
commit
c5e731d8ae
2773 changed files with 600767 additions and 0 deletions
drupal7/web/modules/field
field.api.phpfield.attach.incfield.crud.incfield.default.incfield.form.incfield.infofield.info.class.incfield.info.incfield.installfield.modulefield.multilingual.inc
modules
field_sql_storage
list
number
options
text
tests
field.testfield_test.entity.incfield_test.field.incfield_test.infofield_test.installfield_test.modulefield_test.storage.incfield_test_schema_alter.infofield_test_schema_alter.installfield_test_schema_alter.module
theme
2786
drupal7/web/modules/field/field.api.php
Normal file
2786
drupal7/web/modules/field/field.api.php
Normal file
File diff suppressed because it is too large
Load diff
1416
drupal7/web/modules/field/field.attach.inc
Normal file
1416
drupal7/web/modules/field/field.attach.inc
Normal file
File diff suppressed because it is too large
Load diff
1026
drupal7/web/modules/field/field.crud.inc
Normal file
1026
drupal7/web/modules/field/field.crud.inc
Normal file
File diff suppressed because it is too large
Load diff
268
drupal7/web/modules/field/field.default.inc
Normal file
268
drupal7/web/modules/field/field.default.inc
Normal file
|
@ -0,0 +1,268 @@
|
|||
<?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];
|
||||
}
|
||||
}
|
615
drupal7/web/modules/field/field.form.inc
Normal file
615
drupal7/web/modules/field/field.form.inc
Normal file
|
@ -0,0 +1,615 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Field forms management.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a form element for a field and can populate it with a default value.
|
||||
*
|
||||
* If the form element is not associated with an entity (i.e., $entity is NULL)
|
||||
* field_get_default_value will be called to supply the default value for the
|
||||
* field. Also allows other modules to alter the form element by implementing
|
||||
* their own hooks.
|
||||
*
|
||||
* @param $entity_type
|
||||
* The type of entity (for example 'node' or 'user') that the field belongs
|
||||
* to.
|
||||
* @param $entity
|
||||
* The entity object that the field belongs to. This may be NULL if creating a
|
||||
* form element with a default value.
|
||||
* @param $field
|
||||
* An array representing the field whose editing element is being created.
|
||||
* @param $instance
|
||||
* An array representing the structure for $field in its current context.
|
||||
* @param $langcode
|
||||
* The language associated with the field.
|
||||
* @param $items
|
||||
* An array of the field values. When creating a new entity this may be NULL
|
||||
* or an empty array to use default values.
|
||||
* @param $form
|
||||
* An array representing the form that the editing element will be attached
|
||||
* to.
|
||||
* @param $form_state
|
||||
* An array containing the current state of the form.
|
||||
* @param $get_delta
|
||||
* Used to get only a specific delta value of a multiple value field.
|
||||
*
|
||||
* @return
|
||||
* The form element array created for this 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();
|
||||
|
||||
// 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
|
||||
// not displaying an individual element, process the multiple value form.
|
||||
if (!isset($get_delta) && field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_DEFAULT) {
|
||||
// Store the entity in the form.
|
||||
$form['#entity'] = $entity;
|
||||
$elements = field_multiple_value_form($field, $instance, $langcode, $items, $form, $form_state);
|
||||
}
|
||||
// If the widget is handling multiple values (e.g Options), or if we are
|
||||
// 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' => $entity,
|
||||
'#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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
'#access' => field_access('edit', $field, $entity_type, $entity),
|
||||
);
|
||||
|
||||
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'],
|
||||
'#entity' => $form['#entity'],
|
||||
'#bundle' => $instance['bundle'],
|
||||
'#field_name' => $field_name,
|
||||
'#language' => $langcode,
|
||||
'#field_parents' => $parents,
|
||||
'#columns' => array_keys($field['columns']),
|
||||
'#title' => $title,
|
||||
'#description' => $description,
|
||||
// Only the first widget should be required.
|
||||
'#required' => $delta == 0 && $instance['required'],
|
||||
'#delta' => $delta,
|
||||
'#weight' => $delta,
|
||||
);
|
||||
// For multiple fields, title and description are handled by the wrapping
|
||||
// table.
|
||||
if ($multiple) {
|
||||
if ($delta == 0) {
|
||||
$element['#title'] = $title;
|
||||
}
|
||||
else {
|
||||
$element['#title'] = t('!title (value @number)', array('@number' => $delta + 1, '!title' => $title));
|
||||
}
|
||||
$element['#title_display'] = 'invisible';
|
||||
$element['#description'] = '';
|
||||
}
|
||||
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'])) {
|
||||
// Locate the correct element in the form.
|
||||
$element = drupal_array_get_nested_value($form_state['complete form'], $field_state['array_parents']);
|
||||
// Only set errors if the element is accessible.
|
||||
if (!isset($element['#access']) || $element['#access']) {
|
||||
$function = $instance['widget']['module'] . '_field_widget_error';
|
||||
$function_exists = function_exists($function);
|
||||
|
||||
$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, pass all errors 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['message']);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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'];
|
||||
}
|
17
drupal7/web/modules/field/field.info
Normal file
17
drupal7/web/modules/field/field.info
Normal file
|
@ -0,0 +1,17 @@
|
|||
name = Field
|
||||
description = Field API to add fields to entities like nodes and users.
|
||||
package = Core
|
||||
version = VERSION
|
||||
core = 7.x
|
||||
files[] = field.module
|
||||
files[] = field.attach.inc
|
||||
files[] = field.info.class.inc
|
||||
files[] = tests/field.test
|
||||
dependencies[] = field_sql_storage
|
||||
required = TRUE
|
||||
stylesheets[all][] = theme/field.css
|
||||
|
||||
; Information added by Drupal.org packaging script on 2024-03-06
|
||||
version = "7.100"
|
||||
project = "drupal"
|
||||
datestamp = "1709734591"
|
686
drupal7/web/modules/field/field.info.class.inc
Normal file
686
drupal7/web/modules/field/field.info.class.inc
Normal file
|
@ -0,0 +1,686 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* @file
|
||||
* Definition of the FieldInfo class.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides field and instance definitions for the current runtime environment.
|
||||
*
|
||||
* A FieldInfo object is created and statically persisted through the request
|
||||
* by the _field_info_field_cache() function. The object properties act as a
|
||||
* "static cache" of fields and instances definitions.
|
||||
*
|
||||
* The preferred way to access definitions is through the getBundleInstances()
|
||||
* method, which keeps cache entries per bundle, storing both fields and
|
||||
* instances for a given bundle. Fields used in multiple bundles are duplicated
|
||||
* in several cache entries, and are merged into a single list in the memory
|
||||
* cache. Cache entries are loaded for bundles as a whole, optimizing memory
|
||||
* and CPU usage for the most common pattern of iterating over all instances of
|
||||
* a bundle rather than accessing a single instance.
|
||||
*
|
||||
* The getFields() and getInstances() methods, which return all existing field
|
||||
* and instance definitions, are kept mainly for backwards compatibility, and
|
||||
* should be avoided when possible, since they load and persist in memory a
|
||||
* potentially large array of information. In many cases, the lightweight
|
||||
* getFieldMap() method should be preferred.
|
||||
*/
|
||||
class FieldInfo {
|
||||
|
||||
/**
|
||||
* Lightweight map of fields across entity types and bundles.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fieldMap;
|
||||
|
||||
/**
|
||||
* List of $field structures keyed by ID. Includes deleted fields.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fieldsById = array();
|
||||
|
||||
/**
|
||||
* Mapping of field names to the ID of the corresponding non-deleted field.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fieldIdsByName = array();
|
||||
|
||||
/**
|
||||
* Whether $fieldsById contains all field definitions or a subset.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $loadedAllFields = FALSE;
|
||||
|
||||
/**
|
||||
* Separately tracks requested field names or IDs that do not exist.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $unknownFields = array();
|
||||
|
||||
/**
|
||||
* Instance definitions by bundle.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $bundleInstances = array();
|
||||
|
||||
/**
|
||||
* Whether $bundleInstances contains all instances definitions or a subset.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $loadedAllInstances = FALSE;
|
||||
|
||||
/**
|
||||
* Separately tracks requested bundles that are empty (or do not exist).
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $emptyBundles = array();
|
||||
|
||||
/**
|
||||
* Extra fields by bundle.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $bundleExtraFields = array();
|
||||
|
||||
/**
|
||||
* Clears the "static" and persistent caches.
|
||||
*/
|
||||
public function flush() {
|
||||
$this->fieldMap = NULL;
|
||||
|
||||
$this->fieldsById = array();
|
||||
$this->fieldIdsByName = array();
|
||||
$this->loadedAllFields = FALSE;
|
||||
$this->unknownFields = array();
|
||||
|
||||
$this->bundleInstances = array();
|
||||
$this->loadedAllInstances = FALSE;
|
||||
$this->emptyBundles = array();
|
||||
|
||||
$this->bundleExtraFields = array();
|
||||
|
||||
cache_clear_all('field_info:', 'cache_field', TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects a lightweight map of fields across bundles.
|
||||
*
|
||||
* @return
|
||||
* An array keyed by field name. Each value is an array with two entries:
|
||||
* - type: The field type.
|
||||
* - bundles: The bundles in which the field appears, as an array with
|
||||
* entity types as keys and the array of bundle names as values.
|
||||
*/
|
||||
public function getFieldMap() {
|
||||
// Read from the "static" cache.
|
||||
if ($this->fieldMap !== NULL) {
|
||||
return $this->fieldMap;
|
||||
}
|
||||
|
||||
// Read from persistent cache.
|
||||
if ($cached = cache_get('field_info:field_map', 'cache_field')) {
|
||||
$map = $cached->data;
|
||||
|
||||
// Save in "static" cache.
|
||||
$this->fieldMap = $map;
|
||||
|
||||
return $map;
|
||||
}
|
||||
|
||||
$map = array();
|
||||
|
||||
$query = db_query('SELECT fc.type, fci.field_name, fci.entity_type, fci.bundle FROM {field_config_instance} fci INNER JOIN {field_config} fc ON fc.id = fci.field_id WHERE fc.active = 1 AND fc.storage_active = 1 AND fc.deleted = 0 AND fci.deleted = 0 ORDER BY bundle, entity_type');
|
||||
foreach ($query as $row) {
|
||||
$map[$row->field_name]['bundles'][$row->entity_type][] = $row->bundle;
|
||||
$map[$row->field_name]['type'] = $row->type;
|
||||
}
|
||||
|
||||
// Save in "static" and persistent caches.
|
||||
$this->fieldMap = $map;
|
||||
if (lock_acquire('field_info:field_map')) {
|
||||
cache_set('field_info:field_map', $map, 'cache_field');
|
||||
lock_release('field_info:field_map');
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all active fields, including deleted ones.
|
||||
*
|
||||
* @return
|
||||
* An array of field definitions, keyed by field ID.
|
||||
*/
|
||||
public function getFields() {
|
||||
// Read from the "static" cache.
|
||||
if ($this->loadedAllFields) {
|
||||
return $this->fieldsById;
|
||||
}
|
||||
|
||||
// Read from persistent cache.
|
||||
if ($cached = cache_get('field_info:fields', 'cache_field')) {
|
||||
$this->fieldsById = $cached->data;
|
||||
}
|
||||
else {
|
||||
// Collect and prepare fields.
|
||||
foreach (field_read_fields(array(), array('include_deleted' => TRUE)) as $field) {
|
||||
$this->fieldsById[$field['id']] = $this->prepareField($field);
|
||||
}
|
||||
|
||||
// Store in persistent cache.
|
||||
if (lock_acquire('field_info:fields')) {
|
||||
cache_set('field_info:fields', $this->fieldsById, 'cache_field');
|
||||
lock_release('field_info:fields');
|
||||
}
|
||||
}
|
||||
|
||||
// Fill the name/ID map.
|
||||
foreach ($this->fieldsById as $field) {
|
||||
if (!$field['deleted']) {
|
||||
$this->fieldIdsByName[$field['field_name']] = $field['id'];
|
||||
}
|
||||
}
|
||||
|
||||
$this->loadedAllFields = TRUE;
|
||||
|
||||
return $this->fieldsById;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all active, non-deleted instances definitions.
|
||||
*
|
||||
* @param $entity_type
|
||||
* (optional) The entity type.
|
||||
*
|
||||
* @return
|
||||
* If $entity_type is not set, all instances keyed by entity type and bundle
|
||||
* name. If $entity_type is set, all instances for that entity type, keyed
|
||||
* by bundle name.
|
||||
*/
|
||||
public function getInstances($entity_type = NULL) {
|
||||
// If the full list is not present in "static" cache yet.
|
||||
if (!$this->loadedAllInstances) {
|
||||
|
||||
// Read from persistent cache.
|
||||
if ($cached = cache_get('field_info:instances', 'cache_field')) {
|
||||
$this->bundleInstances = $cached->data;
|
||||
}
|
||||
else {
|
||||
// Collect and prepare instances.
|
||||
|
||||
// We also need to populate the static field cache, since it will not
|
||||
// be set by subsequent getBundleInstances() calls.
|
||||
$this->getFields();
|
||||
|
||||
// Initialize empty arrays for all existing entity types and bundles.
|
||||
// This is not strictly needed, but is done to preserve the behavior of
|
||||
// field_info_instances() before http://drupal.org/node/1915646.
|
||||
foreach (field_info_bundles() as $existing_entity_type => $bundles) {
|
||||
foreach ($bundles as $bundle => $bundle_info) {
|
||||
$this->bundleInstances[$existing_entity_type][$bundle] = array();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (field_read_instances() as $instance) {
|
||||
$field = $this->getField($instance['field_name']);
|
||||
$instance = $this->prepareInstance($instance, $field['type']);
|
||||
$this->bundleInstances[$instance['entity_type']][$instance['bundle']][$instance['field_name']] = $instance;
|
||||
}
|
||||
|
||||
// Store in persistent cache.
|
||||
if (lock_acquire('field_info:instances')) {
|
||||
cache_set('field_info:instances', $this->bundleInstances, 'cache_field');
|
||||
lock_release('field_info:instances');
|
||||
}
|
||||
}
|
||||
|
||||
$this->loadedAllInstances = TRUE;
|
||||
}
|
||||
|
||||
if (isset($entity_type)) {
|
||||
return isset($this->bundleInstances[$entity_type]) ? $this->bundleInstances[$entity_type] : array();
|
||||
}
|
||||
else {
|
||||
return $this->bundleInstances;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a field definition from a field name.
|
||||
*
|
||||
* This method only retrieves active, non-deleted fields.
|
||||
*
|
||||
* @param $field_name
|
||||
* The field name.
|
||||
*
|
||||
* @return
|
||||
* The field definition, or NULL if no field was found.
|
||||
*/
|
||||
public function getField($field_name) {
|
||||
// Read from the "static" cache.
|
||||
if (isset($this->fieldIdsByName[$field_name])) {
|
||||
$field_id = $this->fieldIdsByName[$field_name];
|
||||
return $this->fieldsById[$field_id];
|
||||
}
|
||||
if (isset($this->unknownFields[$field_name])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not check the (large) persistent cache, but read the definition.
|
||||
|
||||
// Cache miss: read from definition.
|
||||
if ($field = field_read_field($field_name)) {
|
||||
$field = $this->prepareField($field);
|
||||
|
||||
// Save in the "static" cache.
|
||||
$this->fieldsById[$field['id']] = $field;
|
||||
$this->fieldIdsByName[$field['field_name']] = $field['id'];
|
||||
|
||||
return $field;
|
||||
}
|
||||
else {
|
||||
$this->unknownFields[$field_name] = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a field definition from a field ID.
|
||||
*
|
||||
* This method only retrieves active fields, deleted or not.
|
||||
*
|
||||
* @param $field_id
|
||||
* The field ID.
|
||||
*
|
||||
* @return
|
||||
* The field definition, or NULL if no field was found.
|
||||
*/
|
||||
public function getFieldById($field_id) {
|
||||
// Read from the "static" cache.
|
||||
if (isset($this->fieldsById[$field_id])) {
|
||||
return $this->fieldsById[$field_id];
|
||||
}
|
||||
if (isset($this->unknownFields[$field_id])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// No persistent cache, fields are only persistently cached as part of a
|
||||
// bundle.
|
||||
|
||||
// Cache miss: read from definition.
|
||||
if ($fields = field_read_fields(array('id' => $field_id), array('include_deleted' => TRUE))) {
|
||||
$field = current($fields);
|
||||
$field = $this->prepareField($field);
|
||||
|
||||
// Store in the static cache.
|
||||
$this->fieldsById[$field['id']] = $field;
|
||||
if (!$field['deleted']) {
|
||||
$this->fieldIdsByName[$field['field_name']] = $field['id'];
|
||||
}
|
||||
|
||||
return $field;
|
||||
}
|
||||
else {
|
||||
$this->unknownFields[$field_id] = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the instances for a bundle.
|
||||
*
|
||||
* The function also populates the corresponding field definitions in the
|
||||
* "static" cache.
|
||||
*
|
||||
* @param $entity_type
|
||||
* The entity type.
|
||||
* @param $bundle
|
||||
* The bundle name.
|
||||
*
|
||||
* @return
|
||||
* The array of instance definitions, keyed by field name.
|
||||
*/
|
||||
public function getBundleInstances($entity_type, $bundle) {
|
||||
// Read from the "static" cache.
|
||||
if (isset($this->bundleInstances[$entity_type][$bundle])) {
|
||||
return $this->bundleInstances[$entity_type][$bundle];
|
||||
}
|
||||
if (isset($this->emptyBundles[$entity_type][$bundle])) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// Read from the persistent cache.
|
||||
if ($cached = cache_get("field_info:bundle:$entity_type:$bundle", 'cache_field')) {
|
||||
$info = $cached->data;
|
||||
|
||||
// Extract the field definitions and save them in the "static" cache.
|
||||
foreach ($info['fields'] as $field) {
|
||||
if (!isset($this->fieldsById[$field['id']])) {
|
||||
$this->fieldsById[$field['id']] = $field;
|
||||
if (!$field['deleted']) {
|
||||
$this->fieldIdsByName[$field['field_name']] = $field['id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($info['fields']);
|
||||
|
||||
// Store the instance definitions in the "static" cache'. Empty (or
|
||||
// non-existent) bundles are stored separately, so that they do not
|
||||
// pollute the global list returned by getInstances().
|
||||
if ($info['instances']) {
|
||||
$this->bundleInstances[$entity_type][$bundle] = $info['instances'];
|
||||
}
|
||||
else {
|
||||
$this->emptyBundles[$entity_type][$bundle] = TRUE;
|
||||
}
|
||||
|
||||
return $info['instances'];
|
||||
}
|
||||
|
||||
// Cache miss: collect from the definitions.
|
||||
|
||||
$instances = array();
|
||||
|
||||
// Collect the fields in the bundle.
|
||||
$params = array('entity_type' => $entity_type, 'bundle' => $bundle);
|
||||
$fields = field_read_fields($params);
|
||||
|
||||
// This iterates on non-deleted instances, so deleted fields are kept out of
|
||||
// the persistent caches.
|
||||
foreach (field_read_instances($params) as $instance) {
|
||||
$field = $fields[$instance['field_name']];
|
||||
|
||||
$instance = $this->prepareInstance($instance, $field['type']);
|
||||
$instances[$field['field_name']] = $instance;
|
||||
|
||||
// If the field is not in our global "static" list yet, add it.
|
||||
if (!isset($this->fieldsById[$field['id']])) {
|
||||
$field = $this->prepareField($field);
|
||||
|
||||
$this->fieldsById[$field['id']] = $field;
|
||||
$this->fieldIdsByName[$field['field_name']] = $field['id'];
|
||||
}
|
||||
}
|
||||
|
||||
// Store in the 'static' cache'. Empty (or non-existent) bundles are stored
|
||||
// separately, so that they do not pollute the global list returned by
|
||||
// getInstances().
|
||||
if ($instances) {
|
||||
$this->bundleInstances[$entity_type][$bundle] = $instances;
|
||||
}
|
||||
else {
|
||||
$this->emptyBundles[$entity_type][$bundle] = TRUE;
|
||||
}
|
||||
|
||||
// The persistent cache additionally contains the definitions of the fields
|
||||
// involved in the bundle.
|
||||
$cache = array(
|
||||
'instances' => $instances,
|
||||
'fields' => array()
|
||||
);
|
||||
foreach ($instances as $instance) {
|
||||
$cache['fields'][] = $this->fieldsById[$instance['field_id']];
|
||||
}
|
||||
|
||||
if (lock_acquire("field_info:bundle:$entity_type:$bundle")) {
|
||||
cache_set("field_info:bundle:$entity_type:$bundle", $cache, 'cache_field');
|
||||
lock_release("field_info:bundle:$entity_type:$bundle");
|
||||
}
|
||||
|
||||
return $instances;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the "extra fields" for a bundle.
|
||||
*
|
||||
* @param $entity_type
|
||||
* The entity type.
|
||||
* @param $bundle
|
||||
* The bundle name.
|
||||
*
|
||||
* @return
|
||||
* The array of extra fields.
|
||||
*/
|
||||
public function getBundleExtraFields($entity_type, $bundle) {
|
||||
// Read from the "static" cache.
|
||||
if (isset($this->bundleExtraFields[$entity_type][$bundle])) {
|
||||
return $this->bundleExtraFields[$entity_type][$bundle];
|
||||
}
|
||||
|
||||
// Read from the persistent cache.
|
||||
if ($cached = cache_get("field_info:bundle_extra:$entity_type:$bundle", 'cache_field')) {
|
||||
$this->bundleExtraFields[$entity_type][$bundle] = $cached->data;
|
||||
return $this->bundleExtraFields[$entity_type][$bundle];
|
||||
}
|
||||
|
||||
// Cache miss: read from hook_field_extra_fields(). Note: given the current
|
||||
// shape of the hook, we have no other way than collecting extra fields on
|
||||
// all bundles.
|
||||
$info = array();
|
||||
$extra = module_invoke_all('field_extra_fields');
|
||||
drupal_alter('field_extra_fields', $extra);
|
||||
// Merge in saved settings.
|
||||
if (isset($extra[$entity_type][$bundle])) {
|
||||
$info = $this->prepareExtraFields($extra[$entity_type][$bundle], $entity_type, $bundle);
|
||||
}
|
||||
|
||||
// Store in the 'static' and persistent caches.
|
||||
$this->bundleExtraFields[$entity_type][$bundle] = $info;
|
||||
if (lock_acquire("field_info:bundle_extra:$entity_type:$bundle")) {
|
||||
cache_set("field_info:bundle_extra:$entity_type:$bundle", $info, 'cache_field');
|
||||
lock_release("field_info:bundle_extra:$entity_type:$bundle");
|
||||
}
|
||||
|
||||
return $this->bundleExtraFields[$entity_type][$bundle];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a field definition for the current run-time context.
|
||||
*
|
||||
* @param $field
|
||||
* The raw field structure as read from the database.
|
||||
*
|
||||
* @return
|
||||
* The field definition completed for the current runtime context.
|
||||
*/
|
||||
public function prepareField($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);
|
||||
$field['storage']['details'] = $details;
|
||||
|
||||
// Populate the list of bundles using the field.
|
||||
$field['bundles'] = array();
|
||||
if (!$field['deleted']) {
|
||||
$map = $this->getFieldMap();
|
||||
if (isset($map[$field['field_name']])) {
|
||||
$field['bundles'] = $map[$field['field_name']]['bundles'];
|
||||
}
|
||||
}
|
||||
|
||||
return $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares an instance definition for the current run-time context.
|
||||
*
|
||||
* @param $instance
|
||||
* The raw instance structure as read from the database.
|
||||
* @param $field_type
|
||||
* The field type.
|
||||
*
|
||||
* @return
|
||||
* The field instance array completed for the current runtime context.
|
||||
*/
|
||||
public function prepareInstance($instance, $field_type) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Prepare widget settings.
|
||||
$instance['widget'] = $this->prepareInstanceWidget($instance['widget'], $field_type);
|
||||
|
||||
// Prepare display settings.
|
||||
foreach ($instance['display'] as $view_mode => $display) {
|
||||
$instance['display'][$view_mode] = $this->prepareInstanceDisplay($display, $field_type);
|
||||
}
|
||||
|
||||
// Fall back 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares widget properties for the current run-time context.
|
||||
*
|
||||
* @param $widget
|
||||
* Widget specifications as found in $instance['widget'].
|
||||
* @param $field_type
|
||||
* The field type.
|
||||
*
|
||||
* @return
|
||||
* The widget properties completed for the current runtime context.
|
||||
*/
|
||||
public function prepareInstanceWidget($widget, $field_type) {
|
||||
$field_type_info = field_info_field_types($field_type);
|
||||
|
||||
// Fill in default values.
|
||||
$widget += array(
|
||||
'type' => $field_type_info['default_widget'],
|
||||
'settings' => array(),
|
||||
'weight' => 0,
|
||||
);
|
||||
|
||||
$widget_type_info = field_info_widget_types($widget['type']);
|
||||
// Fall back to default formatter if formatter type is not available.
|
||||
if (!$widget_type_info) {
|
||||
$widget['type'] = $field_type_info['default_widget'];
|
||||
$widget_type_info = field_info_widget_types($widget['type']);
|
||||
}
|
||||
$widget['module'] = $widget_type_info['module'];
|
||||
// Fill in default settings for the widget.
|
||||
$widget['settings'] += field_info_widget_settings($widget['type']);
|
||||
|
||||
return $widget;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapts display specifications to the current run-time context.
|
||||
*
|
||||
* @param $display
|
||||
* Display specifications as found in $instance['display']['a_view_mode'].
|
||||
* @param $field_type
|
||||
* The field type.
|
||||
*
|
||||
* @return
|
||||
* The display properties completed for the current runtime context.
|
||||
*/
|
||||
public function prepareInstanceDisplay($display, $field_type) {
|
||||
$field_type_info = field_info_field_types($field_type);
|
||||
|
||||
// Fill in default values.
|
||||
$display += array(
|
||||
'label' => 'above',
|
||||
'settings' => array(),
|
||||
'weight' => 0,
|
||||
);
|
||||
if (empty($display['type'])) {
|
||||
$display['type'] = $field_type_info['default_formatter'];
|
||||
}
|
||||
if ($display['type'] != 'hidden') {
|
||||
$formatter_type_info = field_info_formatter_types($display['type']);
|
||||
// Fall back to default formatter if formatter type is not available.
|
||||
if (!$formatter_type_info) {
|
||||
$display['type'] = $field_type_info['default_formatter'];
|
||||
$formatter_type_info = field_info_formatter_types($display['type']);
|
||||
}
|
||||
$display['module'] = $formatter_type_info['module'];
|
||||
// Fill in default settings for the formatter.
|
||||
$display['settings'] += field_info_formatter_settings($display['type']);
|
||||
}
|
||||
|
||||
return $display;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return
|
||||
* The list of extra fields completed for the current runtime context.
|
||||
*/
|
||||
public function prepareExtraFields($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;
|
||||
}
|
||||
}
|
816
drupal7/web/modules/field/field.info.inc
Normal file
816
drupal7/web/modules/field/field.info.inc
Normal file
|
@ -0,0 +1,816 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Field Info API, providing information about available fields and field types.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Retrieves the FieldInfo object for the current request.
|
||||
*
|
||||
* @return FieldInfo
|
||||
* An instance of the FieldInfo class.
|
||||
*/
|
||||
function _field_info_field_cache() {
|
||||
// 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_field_cache'] = &drupal_static(__FUNCTION__);
|
||||
}
|
||||
$field_info = &$drupal_static_fast['field_info_field_cache'];
|
||||
|
||||
if (!isset($field_info)) {
|
||||
// @todo The registry should save the need for an explicit include, but not
|
||||
// a couple upgrade tests (DisabledNodeTypeTestCase,
|
||||
// FilterFormatUpgradePathTestCase...) break in a strange way without it.
|
||||
include_once dirname(__FILE__) . '/field.info.class.inc';
|
||||
$field_info = new FieldInfo();
|
||||
}
|
||||
|
||||
return $field_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*
|
||||
* See @link field Field API @endlink for information about the other parts of
|
||||
* 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');
|
||||
drupal_static_reset('field_available_languages');
|
||||
|
||||
// @todo: Remove this when field_attach_*_bundle() bundle management
|
||||
// functions are moved to the entity API.
|
||||
entity_info_cache_clear();
|
||||
|
||||
_field_info_collate_types(TRUE);
|
||||
_field_info_field_cache()->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Collates all information on existing fields and instances.
|
||||
*
|
||||
* Deprecated. This function is kept to ensure backwards compatibility, but has
|
||||
* a serious performance impact, and should be absolutely avoided.
|
||||
* See http://drupal.org/node/1915646.
|
||||
*
|
||||
* Use the regular field_info_*() API functions to access the information, or
|
||||
* field_info_cache_clear() to clear the cached data.
|
||||
*/
|
||||
function _field_info_collate_fields($reset = FALSE) {
|
||||
if ($reset) {
|
||||
_field_info_field_cache()->flush();
|
||||
return;
|
||||
}
|
||||
|
||||
$cache = _field_info_field_cache();
|
||||
|
||||
// Collect fields, and build the array of IDs keyed by field_name.
|
||||
$fields = $cache->getFields();
|
||||
$field_ids = array();
|
||||
foreach ($fields as $id => $field) {
|
||||
if (!$field['deleted']) {
|
||||
$field_ids[$field['field_name']] = $id;
|
||||
}
|
||||
}
|
||||
|
||||
// Collect extra fields for all entity types.
|
||||
$extra_fields = array();
|
||||
foreach (field_info_bundles() as $entity_type => $bundles) {
|
||||
foreach ($bundles as $bundle => $info) {
|
||||
$extra_fields[$entity_type][$bundle] = $cache->getBundleExtraFields($entity_type, $bundle);
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'fields' => $fields,
|
||||
'field_ids' => $field_ids,
|
||||
'instances' => $cache->getInstances(),
|
||||
'extra_fields' => $extra_fields,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collates all information on field types, widget types and related structures.
|
||||
*
|
||||
* @param $reset
|
||||
* If TRUE, clear the cache. The information will be rebuilt from the database
|
||||
* next time it is needed. Defaults to FALSE.
|
||||
*
|
||||
* @return
|
||||
* If $reset is TRUE, nothing.
|
||||
* If $reset is FALSE, an array containing the following elements:
|
||||
* - '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, weight, 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.
|
||||
*/
|
||||
function _field_info_collate_types($reset = FALSE) {
|
||||
global $language;
|
||||
static $info;
|
||||
|
||||
// The _info() hooks invoked below include translated strings, so each
|
||||
// language is cached separately.
|
||||
$langcode = $language->language;
|
||||
|
||||
if ($reset) {
|
||||
$info = NULL;
|
||||
// Clear all languages.
|
||||
cache_clear_all('field_info_types:', 'cache_field', TRUE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isset($info)) {
|
||||
if ($cached = cache_get("field_info_types:$langcode", 'cache_field')) {
|
||||
$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']);
|
||||
uasort($info['widget types'], 'drupal_sort_weight');
|
||||
|
||||
// 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']);
|
||||
|
||||
// Set the cache if we can acquire a lock.
|
||||
if (lock_acquire("field_info_types:$langcode")) {
|
||||
cache_set("field_info_types:$langcode", $info, 'cache_field');
|
||||
lock_release("field_info_types:$langcode");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a field definition for the current run-time context.
|
||||
*
|
||||
* The functionality has moved to the FieldInfo class. This function is kept as
|
||||
* a backwards-compatibility layer. See http://drupal.org/node/1915646.
|
||||
*
|
||||
* @see FieldInfo::prepareField()
|
||||
*/
|
||||
function _field_info_prepare_field($field) {
|
||||
$cache = _field_info_field_cache();
|
||||
return $cache->prepareField($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares an instance definition for the current run-time context.
|
||||
*
|
||||
* The functionality has moved to the FieldInfo class. This function is kept as
|
||||
* a backwards-compatibility layer. See http://drupal.org/node/1915646.
|
||||
*
|
||||
* @see FieldInfo::prepareInstance()
|
||||
*/
|
||||
function _field_info_prepare_instance($instance, $field) {
|
||||
$cache = _field_info_field_cache();
|
||||
return $cache->prepareInstance($instance, $field['type']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapts display specifications to the current run-time context.
|
||||
*
|
||||
* The functionality has moved to the FieldInfo class. This function is kept as
|
||||
* a backwards-compatibility layer. See http://drupal.org/node/1915646.
|
||||
*
|
||||
* @see FieldInfo::prepareInstanceDisplay()
|
||||
*/
|
||||
function _field_info_prepare_instance_display($field, $display) {
|
||||
$cache = _field_info_field_cache();
|
||||
return $cache->prepareInstanceDisplay($display, $field['type']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares widget specifications for the current run-time context.
|
||||
*
|
||||
* The functionality has moved to the FieldInfo class. This function is kept as
|
||||
* a backwards-compatibility layer. See http://drupal.org/node/1915646.
|
||||
*
|
||||
* @see FieldInfo::prepareInstanceWidget()
|
||||
*/
|
||||
function _field_info_prepare_instance_widget($field, $widget) {
|
||||
$cache = _field_info_field_cache();
|
||||
return $cache->prepareInstanceWidget($widget, $field['type']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares 'extra fields' for the current run-time context.
|
||||
*
|
||||
* The functionality has moved to the FieldInfo class. This function is kept as
|
||||
* a backwards-compatibility layer. See http://drupal.org/node/1915646.
|
||||
*
|
||||
* @see FieldInfo::prepareExtraFields()
|
||||
*/
|
||||
function _field_info_prepare_extra_fields($extra_fields, $entity_type, $bundle) {
|
||||
$cache = _field_info_field_cache();
|
||||
return $cache->prepareExtraFields($extra_fields, $entity_type, $bundle);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 a lightweight map of fields across bundles.
|
||||
*
|
||||
* The function only returns active, non deleted fields.
|
||||
*
|
||||
* @return
|
||||
* An array keyed by field name. Each value is an array with two entries:
|
||||
* - type: The field type.
|
||||
* - bundles: The bundles in which the field appears, as an array with entity
|
||||
* types as keys and the array of bundle names as values.
|
||||
* Example:
|
||||
* @code
|
||||
* array(
|
||||
* 'body' => array(
|
||||
* 'bundles' => array(
|
||||
* 'node' => array('page', 'article'),
|
||||
* ),
|
||||
* 'type' => 'text_with_summary',
|
||||
* ),
|
||||
* );
|
||||
* @endcode
|
||||
*/
|
||||
function field_info_field_map() {
|
||||
$cache = _field_info_field_cache();
|
||||
return $cache->getFieldMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all field definitions.
|
||||
*
|
||||
* Use of this function should be avoided when possible, since it loads and
|
||||
* statically caches a potentially large array of information. Use
|
||||
* field_info_field_map() instead.
|
||||
*
|
||||
* When iterating over the fields present in a given bundle after a call to
|
||||
* field_info_instances($entity_type, $bundle), it is recommended to use
|
||||
* field_info_field() on each individual field instead.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @see field_info_field_map()
|
||||
*/
|
||||
function field_info_fields() {
|
||||
$cache = _field_info_field_cache();
|
||||
$info = $cache->getFields();
|
||||
|
||||
$fields = array();
|
||||
foreach ($info 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. For deleted fields, use
|
||||
* field_info_field_by_id(). To retrieve information about inactive fields,
|
||||
* use field_read_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. NULL if the field was not
|
||||
* found.
|
||||
*
|
||||
* @see field_info_field_by_id()
|
||||
*/
|
||||
function field_info_field($field_name) {
|
||||
$cache = _field_info_field_cache();
|
||||
return $cache->getField($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, but not an inactive one.
|
||||
*
|
||||
* @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) {
|
||||
$cache = _field_info_field_cache();
|
||||
return $cache->getFieldById($field_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the same data as field_info_field_by_id() for every field.
|
||||
*
|
||||
* Use of this function should be avoided when possible, since it loads and
|
||||
* statically caches a potentially large array of information.
|
||||
*
|
||||
* When iterating over the fields present in a given bundle after a call to
|
||||
* field_info_instances($entity_type, $bundle), it is recommended to use
|
||||
* field_info_field() on each individual field instead.
|
||||
*
|
||||
* @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() {
|
||||
$cache = _field_info_field_cache();
|
||||
return $cache->getFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves information about field instances.
|
||||
*
|
||||
* Use of this function to retrieve instances across separate bundles (i.e.
|
||||
* when the $bundle parameter is NULL) should be avoided when possible, since
|
||||
* it loads and statically caches a potentially large array of information. Use
|
||||
* field_info_field_map() instead.
|
||||
*
|
||||
* When retrieving the instances of a specific bundle (i.e. when both
|
||||
* $entity_type and $bundle_name are provided), the function also populates a
|
||||
* static cache with the corresponding field definitions, allowing fast
|
||||
* retrieval of field_info_field() later in the request.
|
||||
*
|
||||
* @param $entity_type
|
||||
* (optional) The entity type for which to return instances.
|
||||
* @param $bundle_name
|
||||
* (optional) The bundle name for which to return instances. If $entity_type
|
||||
* is NULL, the $bundle_name parameter is ignored.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @see field_info_field_map()
|
||||
*/
|
||||
function field_info_instances($entity_type = NULL, $bundle_name = NULL) {
|
||||
$cache = _field_info_field_cache();
|
||||
|
||||
if (!isset($entity_type)) {
|
||||
return $cache->getInstances();
|
||||
}
|
||||
if (!isset($bundle_name)) {
|
||||
return $cache->getInstances($entity_type);
|
||||
}
|
||||
|
||||
return $cache->getBundleInstances($entity_type, $bundle_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of instance data for a specific field and bundle.
|
||||
*
|
||||
* The function populates a static cache with all fields and instances used in
|
||||
* the bundle, allowing fast retrieval of field_info_field() or
|
||||
* field_info_instance() later in the request.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @return
|
||||
* An associative array of instance data for the specific field and bundle;
|
||||
* NULL if the instance does not exist.
|
||||
*/
|
||||
function field_info_instance($entity_type, $field_name, $bundle_name) {
|
||||
$cache = _field_info_field_cache();
|
||||
$info = $cache->getBundleInstances($entity_type, $bundle_name);
|
||||
if (isset($info[$field_name])) {
|
||||
return $info[$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) {
|
||||
$cache = _field_info_field_cache();
|
||||
$info = $cache->getBundleExtraFields($entity_type, $bundle);
|
||||
|
||||
return isset($info[$context]) ? $info[$context] : 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".
|
||||
*/
|
493
drupal7/web/modules/field/field.install
Normal file
493
drupal7/web/modules/field/field.install
Normal file
|
@ -0,0 +1,493 @@
|
|||
<?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_sync_field_status().
|
||||
'active' => array('active'),
|
||||
'storage_active' => array('storage_active'),
|
||||
'deleted' => array('deleted'),
|
||||
// Used by field_modules_disabled().
|
||||
'module' => array('module'),
|
||||
'storage_module' => array('storage_module'),
|
||||
'type' => array('type'),
|
||||
'storage_type' => array('storage_type'),
|
||||
),
|
||||
);
|
||||
$schema['field_config_instance'] = array(
|
||||
'fields' => array(
|
||||
'id' => array(
|
||||
'type' => 'serial',
|
||||
'not null' => TRUE,
|
||||
'description' => 'The primary identifier for a field instance',
|
||||
),
|
||||
'field_id' => array(
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'description' => 'The identifier of the field attached by this instance',
|
||||
),
|
||||
'field_name' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 32,
|
||||
'not null' => TRUE,
|
||||
'default' => ''
|
||||
),
|
||||
'entity_type' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 32,
|
||||
'not null' => TRUE,
|
||||
'default' => ''
|
||||
),
|
||||
'bundle' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 128,
|
||||
'not null' => TRUE,
|
||||
'default' => ''
|
||||
),
|
||||
'data' => array(
|
||||
'type' => 'blob',
|
||||
'size' => 'big',
|
||||
'not null' => TRUE,
|
||||
'serialize' => TRUE,
|
||||
),
|
||||
'deleted' => array(
|
||||
'type' => 'int',
|
||||
'size' => 'tiny',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
),
|
||||
),
|
||||
'primary key' => array('id'),
|
||||
'indexes' => array(
|
||||
// Used by field_delete_instance().
|
||||
'field_name_bundle' => array('field_name', 'entity_type', 'bundle'),
|
||||
// Used by field_read_instances().
|
||||
'deleted' => array('deleted'),
|
||||
),
|
||||
);
|
||||
$schema['cache_field'] = drupal_get_schema_unprocessed('system', 'cache');
|
||||
$schema['cache_field']['description'] = 'Cache table for the Field module to store already built field information.';
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function: create a field by writing directly to the database.
|
||||
*
|
||||
* This function can be used for databases whose schema is at field module
|
||||
* version 7000 or higher.
|
||||
*
|
||||
* @ingroup update_api
|
||||
*/
|
||||
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().
|
||||
*
|
||||
* This function can be used for databases whose schema is at field module
|
||||
* version 7000 or higher.
|
||||
*
|
||||
* @param $field_name
|
||||
* The field name to delete.
|
||||
*
|
||||
* @ingroup update_api
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* This function is valid for a database schema version 7000.
|
||||
*
|
||||
* @ingroup update_api
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* This function can be used for databases whose schema is at field module
|
||||
* version 7000 or higher.
|
||||
*
|
||||
* @ingroup update_api
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* @addtogroup updates-6.x-to-7.x
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Field update version placeholder.
|
||||
*/
|
||||
function field_update_7000() {
|
||||
// Some update helper functions (such as _update_7000_field_create_field())
|
||||
// modify the database directly. They can be used safely only if the database
|
||||
// schema matches the field module schema established for Drupal 7.0 (i.e.
|
||||
// version 7000). This function exists solely to set the schema version to
|
||||
// 7000, so that update functions calling those helpers can do so safely
|
||||
// by declaring a dependency on field_update_7000().
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix fields definitions created during the d6 to d7 upgrade path.
|
||||
*/
|
||||
function field_update_7001() {
|
||||
$fields = _update_7000_field_read_fields();
|
||||
foreach ($fields as $field) {
|
||||
// _update_7000_field_create_field() was broken in d7 RC2, and the fields
|
||||
// created during a d6 to d7 upgrade do not correcly store the 'index'
|
||||
// entry. See http://drupal.org/node/996160.
|
||||
|
||||
module_load_install($field['module']);
|
||||
$schema = (array) module_invoke($field['module'], 'field_schema', $field);
|
||||
$schema += array('indexes' => array());
|
||||
// 'indexes' can be both hardcoded in the field type, and specified in the
|
||||
// incoming $field definition.
|
||||
$field['indexes'] += $schema['indexes'];
|
||||
|
||||
// Place the updated entries in the existing serialized 'data' column.
|
||||
$data = db_query("SELECT data FROM {field_config} WHERE id = :id", array(':id' => $field['id']))->fetchField();
|
||||
$data = unserialize($data);
|
||||
$data['columns'] = $field['columns'];
|
||||
$data['indexes'] = $field['indexes'];
|
||||
|
||||
// Save the new data.
|
||||
$query = db_update('field_config')
|
||||
->condition('id', $field['id'])
|
||||
->fields(array('data' => serialize($data)))
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup updates-6.x-to-7.x".
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup updates-7.x-extra
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Split the all-inclusive field_bundle_settings variable per bundle.
|
||||
*/
|
||||
function field_update_7002() {
|
||||
$settings = variable_get('field_bundle_settings', array());
|
||||
if ($settings) {
|
||||
foreach ($settings as $entity_type => $entity_type_settings) {
|
||||
foreach ($entity_type_settings as $bundle => $bundle_settings) {
|
||||
variable_set('field_bundle_settings_' . $entity_type . '__' . $bundle, $bundle_settings);
|
||||
}
|
||||
}
|
||||
variable_del('field_bundle_settings');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the FieldInfo class to the class registry.
|
||||
*/
|
||||
function field_update_7003() {
|
||||
// Empty update to force a rebuild of the registry.
|
||||
}
|
||||
|
||||
/**
|
||||
* Grant the new "administer fields" permission to trusted users.
|
||||
*/
|
||||
function field_update_7004() {
|
||||
// Assign the permission to anyone that already has a trusted core permission
|
||||
// that would have previously let them administer fields on an entity type.
|
||||
$rids = array();
|
||||
$permissions = array(
|
||||
'administer site configuration',
|
||||
'administer content types',
|
||||
'administer users',
|
||||
);
|
||||
foreach ($permissions as $permission) {
|
||||
$rids = array_merge($rids, array_keys(user_roles(FALSE, $permission)));
|
||||
}
|
||||
$rids = array_unique($rids);
|
||||
foreach ($rids as $rid) {
|
||||
_update_7000_user_role_grant_permissions($rid, array('administer fields'), 'field');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup updates-7.x-extra".
|
||||
*/
|
1232
drupal7/web/modules/field/field.module
Normal file
1232
drupal7/web/modules/field/field.module
Normal file
File diff suppressed because it is too large
Load diff
301
drupal7/web/modules/field/field.multilingual.inc
Normal file
301
drupal7/web/modules/field/field.multilingual.inc
Normal file
|
@ -0,0 +1,301 @@
|
|||
<?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().
|
||||
*
|
||||
* See @link field Field API @endlink for information about the other parts of
|
||||
* the Field API.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_multilingual_settings_changed().
|
||||
*/
|
||||
function field_multilingual_settings_changed() {
|
||||
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;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
name = Field SQL storage
|
||||
description = Stores field data in an SQL database.
|
||||
package = Core
|
||||
version = VERSION
|
||||
core = 7.x
|
||||
dependencies[] = field
|
||||
files[] = field_sql_storage.test
|
||||
required = TRUE
|
||||
|
||||
; Information added by Drupal.org packaging script on 2024-03-06
|
||||
version = "7.100"
|
||||
project = "drupal"
|
||||
datestamp = "1709734591"
|
|
@ -0,0 +1,215 @@
|
|||
<?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.
|
||||
*
|
||||
* This function can be used for databases whose schema is at field module
|
||||
* version 7000 or higher.
|
||||
*
|
||||
* @ingroup update_api
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @addtogroup updates-6.x-to-7.x
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Field SQL storage update version placeholder.
|
||||
*/
|
||||
function field_sql_storage_update_7000() {
|
||||
// Some update helper functions (such as
|
||||
// _update_7000_field_sql_storage_write()) modify the database directly. They
|
||||
// can be used safely only if the database schema matches the field module
|
||||
// schema established for Drupal 7.0 (i.e. version 7000). This function exists
|
||||
// solely to set the schema version to 7000, so that update functions calling
|
||||
// those helpers can do so safely by declaring a dependency on
|
||||
// field_sql_storage_update_7000().
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the field_config_entity_type table and store 'entity_type' strings.
|
||||
*/
|
||||
function field_sql_storage_update_7001(&$sandbox) {
|
||||
if (!isset($sandbox['progress'])) {
|
||||
// Collect current etids.
|
||||
$sandbox['etids'] = db_query('SELECT etid, type FROM {field_config_entity_type}')->fetchAllKeyed();
|
||||
|
||||
// Collect affected tables: field data, field revision data, 'deleted'
|
||||
// tables.
|
||||
$sandbox['tables'] = array();
|
||||
$results = db_select('field_config', 'fc', array('fetch' => PDO::FETCH_ASSOC))
|
||||
->fields('fc')
|
||||
->condition('storage_module', 'field_sql_storage')
|
||||
->execute();
|
||||
foreach ($results as $field) {
|
||||
if ($field['deleted']) {
|
||||
$sandbox['tables']["field_deleted_data_{$field['id']}"] = 'data';
|
||||
$sandbox['tables']["field_deleted_revision_{$field['id']}"] = 'revision';
|
||||
}
|
||||
else {
|
||||
$sandbox['tables']["field_data_{$field['field_name']}"] = 'data';
|
||||
$sandbox['tables']["field_revision_{$field['field_name']}"] = 'revision';
|
||||
}
|
||||
}
|
||||
reset($sandbox['tables']);
|
||||
|
||||
$sandbox['total'] = count($sandbox['tables']);
|
||||
$sandbox['progress'] = 0;
|
||||
}
|
||||
|
||||
if ($sandbox['tables']) {
|
||||
// Grab the next table to process.
|
||||
$table = key($sandbox['tables']);
|
||||
$type = array_shift($sandbox['tables']);
|
||||
|
||||
if (db_table_exists($table)) {
|
||||
// Add the 'entity_type' column.
|
||||
if (!db_field_exists($table, 'entity_type')) {
|
||||
$column = array(
|
||||
'type' => 'varchar',
|
||||
'length' => 128,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
'description' => 'The entity type this data is attached to.',
|
||||
);
|
||||
db_add_field($table, 'entity_type', $column);
|
||||
|
||||
// Populate the 'entity_type' column based on the 'etid' column.
|
||||
foreach ($sandbox['etids'] as $etid => $entity_type) {
|
||||
db_update($table)
|
||||
->fields(array('entity_type' => $entity_type))
|
||||
->condition('etid', $etid)
|
||||
->execute();
|
||||
}
|
||||
|
||||
// Index the new column.
|
||||
db_add_index($table, 'entity_type', array('entity_type'));
|
||||
}
|
||||
|
||||
// Use the 'entity_type' column in the primary key.
|
||||
db_drop_primary_key($table);
|
||||
$primary_keys = array(
|
||||
'data' => array('entity_type', 'entity_id', 'deleted', 'delta', 'language'),
|
||||
'revision' => array('entity_type', 'entity_id', 'revision_id', 'deleted', 'delta', 'language'),
|
||||
);
|
||||
db_add_primary_key($table, $primary_keys[$type]);
|
||||
|
||||
// Drop the 'etid' column.
|
||||
if (db_field_exists($table, 'etid')) {
|
||||
db_drop_field($table, 'etid');
|
||||
}
|
||||
}
|
||||
|
||||
// Report progress.
|
||||
$sandbox['progress']++;
|
||||
$sandbox['#finished'] = min(0.99, $sandbox['progress'] / $sandbox['total']);
|
||||
}
|
||||
else {
|
||||
// No more tables left: drop the field_config_entity_type table.
|
||||
db_drop_table('field_config_entity_type');
|
||||
|
||||
// Drop the previous 'field_sql_storage_ENTITYTYPE_etid' system variables.
|
||||
foreach ($sandbox['etids'] as $etid => $entity_type) {
|
||||
variable_del('field_sql_storage_' . $entity_type . '_etid');
|
||||
}
|
||||
|
||||
// We're done.
|
||||
$sandbox['#finished'] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix primary keys in field revision data tables.
|
||||
*/
|
||||
function field_sql_storage_update_7002() {
|
||||
$results = db_select('field_config', 'fc', array('fetch' => PDO::FETCH_ASSOC))
|
||||
->fields('fc')
|
||||
->condition('storage_module', 'field_sql_storage')
|
||||
->execute();
|
||||
foreach ($results as $field) {
|
||||
// Revision tables of deleted fields do not need to be fixed, since no new
|
||||
// data is written to them.
|
||||
if (!$field['deleted']) {
|
||||
$table = "field_revision_{$field['field_name']}";
|
||||
db_drop_primary_key($table);
|
||||
db_add_primary_key($table, array('entity_type', 'entity_id', 'revision_id', 'deleted', 'delta', 'language'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup updates-6.x-to-7.x".
|
||||
*/
|
|
@ -0,0 +1,933 @@
|
|||
<?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']}";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a table alias for a field data table.
|
||||
*
|
||||
* The table alias is unique for each unique combination of field name
|
||||
* (represented by $tablename), delta_group and language_group.
|
||||
*
|
||||
* @param $tablename
|
||||
* The name of the data table for this field.
|
||||
* @param $field_key
|
||||
* The numeric key of this field in this query.
|
||||
* @param $query
|
||||
* The EntityFieldQuery that is executed.
|
||||
*
|
||||
* @return
|
||||
* A string containing the generated table alias.
|
||||
*/
|
||||
function _field_sql_storage_tablealias($tablename, $field_key, EntityFieldQuery $query) {
|
||||
// No conditions present: use a unique alias.
|
||||
if (empty($query->fieldConditions[$field_key])) {
|
||||
return $tablename . $field_key;
|
||||
}
|
||||
|
||||
// Find the delta and language condition values and append them to the alias.
|
||||
$condition = $query->fieldConditions[$field_key];
|
||||
$alias = $tablename;
|
||||
$has_group_conditions = FALSE;
|
||||
|
||||
foreach (array('delta', 'language') as $column) {
|
||||
if (isset($condition[$column . '_group'])) {
|
||||
$alias .= '_' . $column . '_' . $condition[$column . '_group'];
|
||||
$has_group_conditions = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the alias when it has delta/language group conditions.
|
||||
if ($has_group_conditions) {
|
||||
return $alias;
|
||||
}
|
||||
|
||||
// Return a unique alias in other cases.
|
||||
return $tablename . $field_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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'),
|
||||
),
|
||||
);
|
||||
|
||||
// If the target entity type uses a string for its entity ID then update
|
||||
// the fields entity_id and revision_id columns from INT to VARCHAR.
|
||||
if (!empty($field['entity_id_type']) && $field['entity_id_type'] === 'string') {
|
||||
$current['fields']['entity_id']['type'] = 'varchar';
|
||||
$current['fields']['entity_id']['length'] = 128;
|
||||
unset($current['fields']['entity_id']['unsigned']);
|
||||
|
||||
$current['fields']['revision_id']['type'] = 'varchar';
|
||||
$current['fields']['revision_id']['length'] = 128;
|
||||
unset($current['fields']['revision_id']['unsigned']);
|
||||
}
|
||||
|
||||
$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) {
|
||||
// Indexes can be specified as either a column name or an array with
|
||||
// column name and length. Allow for either case.
|
||||
if (is_array($column_name)) {
|
||||
$current['indexes'][$real_name][] = array(
|
||||
_field_sql_storage_columnname($field['field_name'], $column_name[0]),
|
||||
$column_name[1],
|
||||
);
|
||||
}
|
||||
else {
|
||||
$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_name => $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) {
|
||||
// Indexes can be specified as either a column name or an array with
|
||||
// column name and length. Allow for either case.
|
||||
if (is_array($column_name)) {
|
||||
$real_columns[] = array(
|
||||
_field_sql_storage_columnname($field['field_name'], $column_name[0]),
|
||||
$column_name[1],
|
||||
);
|
||||
}
|
||||
else {
|
||||
$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) {
|
||||
$load_current = $age == FIELD_LOAD_CURRENT;
|
||||
|
||||
foreach ($fields as $field_id => $ids) {
|
||||
// By the time this hook runs, the relevant field definitions have been
|
||||
// populated and cached in FieldInfo, so calling field_info_field_by_id()
|
||||
// on each field individually is more efficient than loading all fields in
|
||||
// memory upfront with field_info_field_by_ids().
|
||||
$field = field_info_field_by_id($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]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for array_filter().
|
||||
*/
|
||||
function _field_sql_storage_write_compare_filter_callback($value) {
|
||||
return NULL !== $value && '' !== $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup field values for later values comparison.
|
||||
*
|
||||
* @param array $field
|
||||
* Field info as returned by field_info_field_by_id().
|
||||
*
|
||||
* @param array $array
|
||||
* Field values to cleanup.
|
||||
*
|
||||
* @return array
|
||||
* Filtered values.
|
||||
*/
|
||||
function _field_sql_storage_write_compare_filter($field, $array) {
|
||||
foreach ($array as $language => $items) {
|
||||
if (empty($items)) {
|
||||
unset($array[$language]);
|
||||
}
|
||||
else {
|
||||
foreach ($items as $delta => $item) {
|
||||
// This should not happen but some modules provide invalid data to the
|
||||
// field API.
|
||||
if (!is_array($item)) {
|
||||
continue;
|
||||
}
|
||||
// Let's start by pruning empty values and non storable values.
|
||||
$array[$language][$delta] = array_filter(array_intersect_key($item, $field['columns']), '_field_sql_storage_write_compare_filter_callback');
|
||||
// Ordering is important because for widget elements and loaded columns
|
||||
// from database order might differ and give false positives on field
|
||||
// value change, especially with complex fields such as image fields.
|
||||
ksort($array[$language][$delta]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare a single field value for both entities and tell us if it changed.
|
||||
*
|
||||
* @param array $field
|
||||
* Loaded field structure.
|
||||
* @param object $entity1
|
||||
* First entity to compare.
|
||||
* @param object $entity2
|
||||
* Second entity to compare.
|
||||
*
|
||||
* @return bool
|
||||
* True if field value changed, false otherwise.
|
||||
*/
|
||||
function _field_sql_storage_write_compare($field, $entity1, $entity2) {
|
||||
$field_name = $field['field_name'];
|
||||
if (empty($entity1->$field_name) && empty($entity2->$field_name)) {
|
||||
// Both are empty we can safely assume that it did not change.
|
||||
return FALSE;
|
||||
}
|
||||
if (!isset($entity1->$field_name) || !isset($entity2->$field_name)) {
|
||||
// One of them is missing but not the other the value changed.
|
||||
return TRUE;
|
||||
}
|
||||
// We need to proceed to deep array comparison, but we cannot do it naively:
|
||||
// in most cases the field values come from the edit form, and some Form API
|
||||
// widget values that are not field columns may be present. We need to clean
|
||||
// up both original and new field values before comparison.
|
||||
$items1 = _field_sql_storage_write_compare_filter($field, (array) $entity1->$field_name);
|
||||
$items2 = _field_sql_storage_write_compare_filter($field, (array) $entity2->$field_name);
|
||||
return $items1 != $items2;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
// Check if the given entity is a new revision or not. In case of a new
|
||||
// revision creation, we cannot skip any field.
|
||||
if (!empty($vid) && !empty($entity->original)) {
|
||||
list(, $original_vid) = entity_extract_ids($entity_type, $entity->original);
|
||||
if (NULL === $original_vid) {
|
||||
$original_vid = $id;
|
||||
}
|
||||
$is_new_revision = $original_vid != $vid;
|
||||
}
|
||||
else {
|
||||
$is_new_revision = FALSE;
|
||||
}
|
||||
|
||||
// Allow this optimization to be optional.
|
||||
$skip_unchanged_fields = variable_get('field_sql_storage_skip_writing_unchanged_fields', FALSE);
|
||||
|
||||
foreach ($fields as $field_id) {
|
||||
$field = field_info_field_by_id($field_id);
|
||||
|
||||
if ($skip_unchanged_fields && !$is_new_revision && !empty($entity->original) && !_field_sql_storage_write_compare($field, $entity, $entity->original)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$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 something 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();
|
||||
$query_tables = NULL;
|
||||
// Add tables for the fields used.
|
||||
foreach ($query->fields as $key => $field) {
|
||||
$tablename = $tablename_function($field);
|
||||
$table_alias = _field_sql_storage_tablealias($tablename, $key, $query);
|
||||
$table_aliases[$key] = $table_alias;
|
||||
if ($key) {
|
||||
if (!isset($query_tables[$table_alias])) {
|
||||
$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);
|
||||
// Store a reference to the list of joined tables.
|
||||
$query_tables =& $select_query->getTables();
|
||||
// Allow queries internal to the Field API to opt out of the access
|
||||
// check, for situations where the query's results should not depend on
|
||||
// the access grants for the current user.
|
||||
if (!isset($query->tags['DANGEROUS_ACCESS_CHECK_OPT_OUT'])) {
|
||||
$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, '_field_sql_storage_query_columnname');
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Field meta condition column callback.
|
||||
*/
|
||||
function _field_sql_storage_query_columnname($field_name, $column) {
|
||||
return $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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,682 @@
|
|||
<?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 {
|
||||
protected $field;
|
||||
protected $instance;
|
||||
protected $field_name;
|
||||
protected $table;
|
||||
protected $revision_table;
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests adding a field with an entity ID type of string.
|
||||
*/
|
||||
function testFieldSqlSchemaForEntityWithStringIdentifier() {
|
||||
// Test programmatically adding field with string ID.
|
||||
$field_name = 'string_id_example';
|
||||
$field = array('field_name' => $field_name, 'type' => 'text', 'settings' => array('max_length' => 255), 'entity_id_type' => 'string');
|
||||
field_create_field($field);
|
||||
$schema = drupal_get_schema('field_data_' . $field_name);
|
||||
|
||||
$this->assertEqual($schema['fields']['entity_id']['type'], 'varchar');
|
||||
$this->assertEqual($schema['fields']['revision_id']['type'], 'varchar');
|
||||
|
||||
// Test programmatically adding field with default ID(int).
|
||||
$field_name = 'default_id_example';
|
||||
$field = array('field_name' => $field_name, 'type' => 'text', 'settings' => array('max_length' => 255));
|
||||
field_create_field($field);
|
||||
$schema = drupal_get_schema('field_data_' . $field_name);
|
||||
|
||||
$this->assertEqual($schema['fields']['entity_id']['type'], 'int');
|
||||
$this->assertEqual($schema['fields']['revision_id']['type'], 'int');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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'], format_string("Value %delta is inserted correctly", array('%delta' => $delta)));
|
||||
}
|
||||
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'], format_string("Value %delta is updated correctly", array('%delta' => $delta)));
|
||||
}
|
||||
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'], format_string("Update with no field_name entry leaves value %delta untouched", array('%delta' => $delta)));
|
||||
}
|
||||
}
|
||||
|
||||
// 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, "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.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the expected return values of _field_sql_storage_write_compare().
|
||||
*/
|
||||
public function testFieldCompareDataModification() {
|
||||
$langcode = LANGUAGE_NONE;
|
||||
$field_info = field_info_field($this->field_name);
|
||||
|
||||
// Make sure we have 2 sample field values that are unique.
|
||||
$value1 = 0;
|
||||
$value2 = 0;
|
||||
while ($value1 == $value2) {
|
||||
$value1 = mt_rand();
|
||||
$value2 = (string) mt_rand();
|
||||
}
|
||||
|
||||
// Create the 2 entities to compare.
|
||||
$entity = field_test_create_stub_entity();
|
||||
$entity->{$this->field_name}[$langcode][]['value'] = $value1;
|
||||
$entity1 = clone $entity;
|
||||
$entity2 = clone $entity;
|
||||
|
||||
// Make sure that it correctly compares identical entities.
|
||||
$this->assert(!_field_sql_storage_write_compare($field_info, $entity1, $entity2), 'The entities are identical.');
|
||||
|
||||
// Compare to an empty object.
|
||||
$this->assert(_field_sql_storage_write_compare($field_info, $entity1, new stdClass()), 'The entity is not the same as an empty object.');
|
||||
|
||||
// Change one of the values.
|
||||
$entity2->{$this->field_name}[$langcode][0]['value'] = $value2;
|
||||
$this->assert(_field_sql_storage_write_compare($field_info, $entity1, $entity2), 'The values are not the same.');
|
||||
|
||||
// Reset $entity2.
|
||||
$entity2 = clone $entity;
|
||||
|
||||
// Duplicate the value on one of the entities.
|
||||
$entity1->{$this->field_name}[$langcode][]['value'] = $value1;
|
||||
$this->assert(_field_sql_storage_write_compare($field_info, $entity1, $entity2), 'The fields do not have the same number of values.');
|
||||
|
||||
// Add a second value to both entities.
|
||||
$entity2->{$this->field_name}[$langcode][]['value'] = $value2;
|
||||
$this->assert(_field_sql_storage_write_compare($field_info, $entity1, $entity2), 'The values are not the same.');
|
||||
|
||||
// Replace the array containing the value with the actual value.
|
||||
$entity2->{$this->field_name}[$langcode] = $entity2->{$this->field_name}[$langcode][0];
|
||||
$this->assert(_field_sql_storage_write_compare($field_info, $entity1, $entity2), 'The array to hold field values is replaced by the value.');
|
||||
|
||||
// Null one value.
|
||||
$entity2->{$this->field_name}[$langcode] = NULL;
|
||||
$this->assert(_field_sql_storage_write_compare($field_info, $entity1, $entity2), 'One field is NULL and the other is not.');
|
||||
|
||||
// Null both values.
|
||||
$entity1->{$this->field_name}[$langcode] = NULL;
|
||||
$this->assert(!_field_sql_storage_write_compare($field_info, $entity1, $entity2), 'Both fields are NULL.');
|
||||
|
||||
// Unset one of the fields.
|
||||
unset($entity2->{$this->field_name});
|
||||
$this->assert(_field_sql_storage_write_compare($field_info, $entity1, $entity2), 'One field structure is unset.');
|
||||
|
||||
// Unset both of the fields.
|
||||
unset($entity1->{$this->field_name});
|
||||
$this->assert(!_field_sql_storage_write_compare($field_info, $entity1, $entity2), 'Both field structures are unset.');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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. The
|
||||
// parenthesis suffix is needed because SQLite has *very* relaxed rules for
|
||||
// data types, so we actually need to provide an invalid SQL syntax in order
|
||||
// to break it.
|
||||
// @see https://www.sqlite.org/datatype3.html
|
||||
$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), format_string('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'), format_string("No index named value exists in %table", array('%table' => $table)));
|
||||
$this->assertFalse(Database::getConnection()->schema()->indexExists($table, 'value_format'), format_string("No index named value_format exists in %table", array('%table' => $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(array('value', 255))));
|
||||
field_update_field($field);
|
||||
foreach ($tables as $table) {
|
||||
$this->assertTrue(Database::getConnection()->schema()->indexExists($table, "{$field_name}_value"), format_string("Index on value created in %table", array('%table' => $table)));
|
||||
}
|
||||
|
||||
// Add a different index, removing the existing custom one.
|
||||
$field = array('field_name' => $field_name, 'indexes' => array('value_format' => array(array('value', 127), array('format', 127))));
|
||||
field_update_field($field);
|
||||
foreach ($tables as $table) {
|
||||
$this->assertTrue(Database::getConnection()->schema()->indexExists($table, "{$field_name}_value_format"), format_string("Index on value_format created in %table", array('%table' => $table)));
|
||||
$this->assertFalse(Database::getConnection()->schema()->indexExists($table, "{$field_name}_value"), format_string("Index on value removed in %table", array('%table' => $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', "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']), '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]), 'Table name is available in the instance array.');
|
||||
$this->assertTrue(array_key_exists($revision, $details[FIELD_LOAD_REVISION]), 'Revision table name is available in the instance array.');
|
||||
|
||||
// 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, format_string('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, format_string('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => $revision)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test foreign key support.
|
||||
*/
|
||||
function testFieldSqlStorageForeignKeys() {
|
||||
// Create a 'shape' field, with a configurable foreign key (see
|
||||
// field_test_field_schema()).
|
||||
$field_name = 'testfield';
|
||||
$foreign_key_name = 'shape';
|
||||
$field = array('field_name' => $field_name, 'type' => 'shape', 'settings' => array('foreign_key_name' => $foreign_key_name));
|
||||
field_create_field($field);
|
||||
|
||||
// Retrieve the field definition and check that the foreign key is in place.
|
||||
$field = field_info_field($field_name);
|
||||
$this->assertEqual($field['foreign keys'][$foreign_key_name]['table'], $foreign_key_name, 'Foreign key table name preserved through CRUD');
|
||||
$this->assertEqual($field['foreign keys'][$foreign_key_name]['columns'][$foreign_key_name], 'id', 'Foreign key column name preserved through CRUD');
|
||||
|
||||
// Update the field settings, it should update the foreign key definition
|
||||
// too.
|
||||
$foreign_key_name = 'color';
|
||||
$field['settings']['foreign_key_name'] = $foreign_key_name;
|
||||
field_update_field($field);
|
||||
|
||||
// Retrieve the field definition and check that the foreign key is in place.
|
||||
$field = field_info_field($field_name);
|
||||
$this->assertEqual($field['foreign keys'][$foreign_key_name]['table'], $foreign_key_name, 'Foreign key table name modified after update');
|
||||
$this->assertEqual($field['foreign keys'][$foreign_key_name]['columns'][$foreign_key_name], 'id', 'Foreign key column name modified after update');
|
||||
|
||||
// Now grab the SQL schema and verify that too.
|
||||
$schema = drupal_get_schema(_field_sql_storage_tablename($field), TRUE);
|
||||
$this->assertEqual(count($schema['foreign keys']), 1, 'There is 1 foreign key in the schema');
|
||||
$foreign_key = reset($schema['foreign keys']);
|
||||
$foreign_key_column = _field_sql_storage_columnname($field['field_name'], $foreign_key_name);
|
||||
$this->assertEqual($foreign_key['table'], $foreign_key_name, 'Foreign key table name preserved in the schema');
|
||||
$this->assertEqual($foreign_key['columns'][$foreign_key_column], 'id', 'Foreign key column name preserved in the schema');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test handling multiple conditions on one column of a field.
|
||||
*
|
||||
* Tests both the result and the complexity of the query.
|
||||
*/
|
||||
function testFieldSqlStorageMultipleConditionsSameColumn() {
|
||||
$entity = field_test_create_stub_entity(NULL, NULL);
|
||||
$entity->{$this->field_name}[LANGUAGE_NONE][0] = array('value' => 1);
|
||||
field_test_entity_save($entity);
|
||||
|
||||
$entity = field_test_create_stub_entity(NULL, NULL);
|
||||
$entity->{$this->field_name}[LANGUAGE_NONE][0] = array('value' => 2);
|
||||
field_test_entity_save($entity);
|
||||
|
||||
$entity = field_test_create_stub_entity(NULL, NULL);
|
||||
$entity->{$this->field_name}[LANGUAGE_NONE][0] = array('value' => 3);
|
||||
field_test_entity_save($entity);
|
||||
|
||||
$query = new EntityFieldQuery();
|
||||
// This tag causes field_test_query_store_global_test_query_alter() to be
|
||||
// invoked so that the query can be tested.
|
||||
$query->addTag('store_global_test_query');
|
||||
$query->entityCondition('entity_type', 'test_entity');
|
||||
$query->entityCondition('bundle', 'test_bundle');
|
||||
$query->fieldCondition($this->field_name, 'value', 1, '<>', 0, LANGUAGE_NONE);
|
||||
$query->fieldCondition($this->field_name, 'value', 2, '<>', 0, LANGUAGE_NONE);
|
||||
$result = field_sql_storage_field_storage_query($query);
|
||||
|
||||
// Test the results.
|
||||
$this->assertEqual(1, count($result), format_string('One result should be returned, got @count', array('@count' => count($result))));
|
||||
|
||||
// Test the complexity of the query.
|
||||
$query = $GLOBALS['test_query'];
|
||||
$this->assertNotNull($query, 'Precondition: the query should be available');
|
||||
$tables = $query->getTables();
|
||||
$this->assertEqual(1, count($tables), 'The query contains just one table.');
|
||||
|
||||
// Clean up.
|
||||
unset($GLOBALS['test_query']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test handling multiple conditions on multiple columns of one field.
|
||||
*
|
||||
* Tests both the result and the complexity of the query.
|
||||
*/
|
||||
function testFieldSqlStorageMultipleConditionsDifferentColumns() {
|
||||
// Create the multi-column shape field
|
||||
$field_name = strtolower($this->randomName());
|
||||
$field = array('field_name' => $field_name, 'type' => 'shape', 'cardinality' => 4);
|
||||
$field = field_create_field($field);
|
||||
$instance = array(
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'test_entity',
|
||||
'bundle' => 'test_bundle'
|
||||
);
|
||||
$instance = field_create_instance($instance);
|
||||
|
||||
$entity = field_test_create_stub_entity(NULL, NULL);
|
||||
$entity->{$field_name}[LANGUAGE_NONE][0] = array('shape' => 'A', 'color' => 'X');
|
||||
field_test_entity_save($entity);
|
||||
|
||||
$entity = field_test_create_stub_entity(NULL, NULL);
|
||||
$entity->{$field_name}[LANGUAGE_NONE][0] = array('shape' => 'B', 'color' => 'X');
|
||||
field_test_entity_save($entity);
|
||||
|
||||
$entity = field_test_create_stub_entity(NULL, NULL);
|
||||
$entity->{$field_name}[LANGUAGE_NONE][0] = array('shape' => 'A', 'color' => 'Y');
|
||||
field_test_entity_save($entity);
|
||||
|
||||
$query = new EntityFieldQuery();
|
||||
// This tag causes field_test_query_store_global_test_query_alter() to be
|
||||
// invoked so that the query can be tested.
|
||||
$query->addTag('store_global_test_query');
|
||||
$query->entityCondition('entity_type', 'test_entity');
|
||||
$query->entityCondition('bundle', 'test_bundle');
|
||||
$query->fieldCondition($field_name, 'shape', 'B', '=', 'something', LANGUAGE_NONE);
|
||||
$query->fieldCondition($field_name, 'color', 'X', '=', 'something', LANGUAGE_NONE);
|
||||
$result = field_sql_storage_field_storage_query($query);
|
||||
|
||||
// Test the results.
|
||||
$this->assertEqual(1, count($result), format_string('One result should be returned, got @count', array('@count' => count($result))));
|
||||
|
||||
// Test the complexity of the query.
|
||||
$query = $GLOBALS['test_query'];
|
||||
$this->assertNotNull($query, 'Precondition: the query should be available');
|
||||
$tables = $query->getTables();
|
||||
$this->assertEqual(1, count($tables), 'The query contains just one table.');
|
||||
|
||||
// Clean up.
|
||||
unset($GLOBALS['test_query']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test handling multiple conditions on multiple columns of one field for multiple languages.
|
||||
*
|
||||
* Tests both the result and the complexity of the query.
|
||||
*/
|
||||
function testFieldSqlStorageMultipleConditionsDifferentColumnsMultipleLanguages() {
|
||||
field_test_entity_info_translatable('test_entity', TRUE);
|
||||
|
||||
// Create the multi-column shape field
|
||||
$field_name = strtolower($this->randomName());
|
||||
$field = array('field_name' => $field_name, 'type' => 'shape', 'cardinality' => 4, 'translatable' => TRUE);
|
||||
$field = field_create_field($field);
|
||||
$instance = array(
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'test_entity',
|
||||
'bundle' => 'test_bundle',
|
||||
'settings' => array(
|
||||
// Prevent warning from field_test_field_load().
|
||||
'test_hook_field_load' => FALSE,
|
||||
),
|
||||
);
|
||||
$instance = field_create_instance($instance);
|
||||
|
||||
$entity = field_test_create_stub_entity(NULL, NULL);
|
||||
$entity->{$field_name}[LANGUAGE_NONE][0] = array('shape' => 'A', 'color' => 'X');
|
||||
$entity->{$field_name}['en'][0] = array('shape' => 'B', 'color' => 'Y');
|
||||
field_test_entity_save($entity);
|
||||
$entity = field_test_entity_test_load($entity->ftid);
|
||||
|
||||
$query = new EntityFieldQuery();
|
||||
// This tag causes field_test_query_store_global_test_query_alter() to be
|
||||
// invoked so that the query can be tested.
|
||||
$query->addTag('store_global_test_query');
|
||||
$query->entityCondition('entity_type', 'test_entity');
|
||||
$query->entityCondition('bundle', 'test_bundle');
|
||||
$query->fieldCondition($field_name, 'color', 'X', '=', NULL, LANGUAGE_NONE);
|
||||
$query->fieldCondition($field_name, 'shape', 'B', '=', NULL, 'en');
|
||||
$result = field_sql_storage_field_storage_query($query);
|
||||
|
||||
// Test the results.
|
||||
$this->assertEqual(1, count($result), format_string('One result should be returned, got @count', array('@count' => count($result))));
|
||||
|
||||
// Test the complexity of the query.
|
||||
$query = $GLOBALS['test_query'];
|
||||
$this->assertNotNull($query, 'Precondition: the query should be available');
|
||||
$tables = $query->getTables();
|
||||
$this->assertEqual(2, count($tables), 'The query contains two tables.');
|
||||
|
||||
// Clean up.
|
||||
unset($GLOBALS['test_query']);
|
||||
}
|
||||
}
|
13
drupal7/web/modules/field/modules/list/list.info
Normal file
13
drupal7/web/modules/field/modules/list/list.info
Normal file
|
@ -0,0 +1,13 @@
|
|||
name = List
|
||||
description = Defines list field types. Use with Options to create selection lists.
|
||||
package = Core
|
||||
version = VERSION
|
||||
core = 7.x
|
||||
dependencies[] = field
|
||||
dependencies[] = options
|
||||
files[] = tests/list.test
|
||||
|
||||
; Information added by Drupal.org packaging script on 2024-03-06
|
||||
version = "7.100"
|
||||
project = "drupal"
|
||||
datestamp = "1709734591"
|
145
drupal7/web/modules/field/modules/list/list.install
Normal file
145
drupal7/web/modules/field/modules/list/list.install
Normal file
|
@ -0,0 +1,145 @@
|
|||
<?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'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename the list field types and change 'allowed_values' format.
|
||||
*/
|
||||
function list_update_7001() {
|
||||
$fields = _update_7000_field_read_fields(array('module' => 'list'));
|
||||
foreach ($fields as $field) {
|
||||
$update = array();
|
||||
|
||||
// Translate the old string format into the new array format.
|
||||
$allowed_values = $field['settings']['allowed_values'];
|
||||
if (is_string($allowed_values)) {
|
||||
$position_keys = ($field['type'] == 'list');
|
||||
$allowed_values = _list_update_7001_extract_allowed_values($allowed_values, $position_keys);
|
||||
|
||||
// Additionally, float keys need to be disambiguated ('.5' is '0.5').
|
||||
if ($field['type'] == 'list_number' && !empty($allowed_values)) {
|
||||
$keys = array_map('_list_update_7001_float_string_cast', array_keys($allowed_values));
|
||||
$allowed_values = array_combine($keys, array_values($allowed_values));
|
||||
}
|
||||
|
||||
// Place the new setting in the existing serialized 'data' column.
|
||||
$data = db_query("SELECT data FROM {field_config} WHERE id = :id", array(':id' => $field['id']))->fetchField();
|
||||
$data = unserialize($data);
|
||||
$data['settings']['allowed_values'] = $allowed_values;
|
||||
$update['data'] = serialize($data);
|
||||
}
|
||||
|
||||
// Rename field types.
|
||||
$types = array('list' => 'list_integer', 'list_number' => 'list_float');
|
||||
if (isset($types[$field['type']])) {
|
||||
$update['type'] = $types[$field['type']];
|
||||
}
|
||||
|
||||
// Save the new data.
|
||||
if ($update) {
|
||||
$query = db_update('field_config')
|
||||
->condition('id', $field['id'])
|
||||
->fields($update)
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper callback function to cast the array element.
|
||||
*/
|
||||
function _list_update_7001_float_string_cast($element) {
|
||||
return (string) (float) $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for list_update_7001: extract allowed values from a string.
|
||||
*
|
||||
* This reproduces the parsing logic in use before D7 RC2.
|
||||
*/
|
||||
function _list_update_7001_extract_allowed_values($string, $position_keys) {
|
||||
$values = array();
|
||||
|
||||
$list = explode("\n", $string);
|
||||
$list = array_map('trim', $list);
|
||||
$list = array_filter($list, 'strlen');
|
||||
|
||||
foreach ($list as $key => $value) {
|
||||
// Check for a manually specified key.
|
||||
if (strpos($value, '|') !== FALSE) {
|
||||
list($key, $value) = explode('|', $value);
|
||||
}
|
||||
// Otherwise see if we need to use the value as the key. The "list" type
|
||||
// will automatically convert non-keyed lines to integers.
|
||||
elseif (!$position_keys) {
|
||||
$key = $value;
|
||||
}
|
||||
$values[$key] = (isset($value) && $value !== '') ? $value : $key;
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* @addtogroup updates-7.x-extra
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Re-apply list_update_7001() for deleted fields.
|
||||
*/
|
||||
function list_update_7002() {
|
||||
// See http://drupal.org/node/1022924: list_update_7001() intitally
|
||||
// overlooked deleted fields, which then caused fatal errors when the fields
|
||||
// were being purged.
|
||||
// list_update_7001() has the required checks to ensure it is reentrant, so
|
||||
// it can simply be executed once more..
|
||||
list_update_7001();
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup updates-7.x-extra".
|
||||
*/
|
487
drupal7/web/modules/field/modules/list/list.module
Normal file
487
drupal7/web/modules/field/modules/list/list.module
Normal file
|
@ -0,0 +1,487 @@
|
|||
<?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' => empty($settings['allowed_values_function']) ? 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.
|
||||
* @param $instance
|
||||
* (optional) A field instance array. Defaults to NULL.
|
||||
* @param $entity_type
|
||||
* (optional) The type of entity; e.g. 'node' or 'user'. Defaults to NULL.
|
||||
* @param $entity
|
||||
* (optional) The entity object. Defaults to NULL.
|
||||
*
|
||||
* @return
|
||||
* The array of allowed values. Keys of the array are the raw stored values
|
||||
* (number or text), values of the array are the display labels.
|
||||
*/
|
||||
function list_allowed_values($field, $instance = NULL, $entity_type = NULL, $entity = NULL) {
|
||||
$allowed_values = &drupal_static(__FUNCTION__, array());
|
||||
|
||||
if (!isset($allowed_values[$field['id']])) {
|
||||
$function = $field['settings']['allowed_values_function'];
|
||||
// If $cacheable is FALSE, then the allowed values are not statically
|
||||
// cached. See list_test_dynamic_values_callback() for an example of
|
||||
// generating dynamic and uncached values.
|
||||
$cacheable = TRUE;
|
||||
if (!empty($function) && function_exists($function)) {
|
||||
$values = $function($field, $instance, $entity_type, $entity, $cacheable);
|
||||
}
|
||||
else {
|
||||
$values = $field['settings']['allowed_values'];
|
||||
}
|
||||
|
||||
if ($cacheable) {
|
||||
$allowed_values[$field['id']] = $values;
|
||||
}
|
||||
else {
|
||||
return $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('A list field (@field_name) with existing data cannot have its keys changed.', array('@field_name' => $field['field_name'])));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
// Flatten the array before validating to account for optgroups.
|
||||
$allowed_values = options_array_flatten(list_allowed_values($field, $instance, $entity_type, $entity));
|
||||
foreach ($items as $delta => $item) {
|
||||
if (!empty($item['value'])) {
|
||||
if (!empty($allowed_values) && !isset($allowed_values[$item['value']])) {
|
||||
$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, $entity_type, $entity) {
|
||||
return list_allowed_values($field, $instance, $entity_type, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, $instance, $entity_type, $entity);
|
||||
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;
|
||||
}
|
490
drupal7/web/modules/field/modules/list/tests/list.test
Normal file
490
drupal7/web/modules/field/modules/list/tests/list.test
Normal file
|
@ -0,0 +1,490 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Tests for list.module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Tests for the 'List' field types.
|
||||
*/
|
||||
class ListFieldTestCase extends FieldTestCase {
|
||||
protected $field;
|
||||
protected $instance;
|
||||
protected $field_name;
|
||||
|
||||
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]), 'Option 1 exists');
|
||||
$this->assertTrue(!empty($form[$this->field_name][$langcode][2]), 'Option 2 exists');
|
||||
$this->assertTrue(!empty($form[$this->field_name][$langcode][3]), '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]), 'Option 1 does not exist');
|
||||
$this->assertTrue(!empty($form[$this->field_name][$langcode][2]), 'Option 2 exists');
|
||||
$this->assertTrue(empty($form[$this->field_name][$langcode][3]), '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]), 'Option 1 does not exist');
|
||||
$this->assertTrue(empty($form[$this->field_name][$langcode][2]), 'Option 2 does not exist');
|
||||
$this->assertTrue(empty($form[$this->field_name][$langcode][3]), 'Option 3 does not exist');
|
||||
$this->assertTrue(!empty($form[$this->field_name][$langcode][10]), 'Option 10 exists');
|
||||
$this->assertTrue(!empty($form[$this->field_name][$langcode][20]), '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]), 'Option 1 exists');
|
||||
$this->assertTrue(!empty($form[$this->field_name][$langcode][2]), 'Option 2 exists');
|
||||
$this->assertTrue(!empty($form[$this->field_name][$langcode][3]), 'Option 3 exists');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a List field for testing allowed values functions.
|
||||
*/
|
||||
class ListDynamicValuesTestCase extends FieldTestCase {
|
||||
protected $field;
|
||||
protected $field_name;
|
||||
protected $instance;
|
||||
protected $test;
|
||||
protected $entity;
|
||||
|
||||
function setUp() {
|
||||
parent::setUp(array('list', 'field_test', 'list_test'));
|
||||
|
||||
$this->field_name = 'test_list';
|
||||
$this->field = array(
|
||||
'field_name' => $this->field_name,
|
||||
'type' => 'list_text',
|
||||
'cardinality' => 1,
|
||||
'settings' => array(
|
||||
'allowed_values_function' => 'list_test_dynamic_values_callback',
|
||||
),
|
||||
);
|
||||
$this->field = field_create_field($this->field);
|
||||
|
||||
$this->instance = array(
|
||||
'field_name' => $this->field_name,
|
||||
'entity_type' => 'test_entity',
|
||||
'bundle' => 'test_bundle',
|
||||
'required' => TRUE,
|
||||
'widget' => array(
|
||||
'type' => 'options_select',
|
||||
),
|
||||
);
|
||||
$this->instance = field_create_instance($this->instance);
|
||||
$this->test = array(
|
||||
'id' => mt_rand(1, 10),
|
||||
// Make sure this does not equal the ID so that
|
||||
// list_test_dynamic_values_callback() always returns 4 values.
|
||||
'vid' => mt_rand(20, 30),
|
||||
'bundle' => 'test_bundle',
|
||||
'label' => $this->randomName(),
|
||||
);
|
||||
$this->entity = call_user_func_array('field_test_create_stub_entity', $this->test);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the List field allowed values function.
|
||||
*/
|
||||
class ListDynamicValuesValidationTestCase extends ListDynamicValuesTestCase {
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'List field dynamic values',
|
||||
'description' => 'Test the List field allowed values function.',
|
||||
'group' => 'Field types',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that allowed values function gets the entity.
|
||||
*/
|
||||
function testDynamicAllowedValues() {
|
||||
// Verify that the test passes against every value we had.
|
||||
foreach ($this->test as $key => $value) {
|
||||
$this->entity->test_list[LANGUAGE_NONE][0]['value'] = $value;
|
||||
try {
|
||||
field_attach_validate('test_entity', $this->entity);
|
||||
$this->pass("$key should pass");
|
||||
}
|
||||
catch (FieldValidationException $e) {
|
||||
// This will display as an exception, no need for a separate error.
|
||||
throw($e);
|
||||
}
|
||||
}
|
||||
// Now verify that the test does not pass against anything else.
|
||||
foreach ($this->test as $key => $value) {
|
||||
$this->entity->test_list[LANGUAGE_NONE][0]['value'] = is_numeric($value) ? (100 - $value) : ('X' . $value);
|
||||
$pass = FALSE;
|
||||
try {
|
||||
field_attach_validate('test_entity', $this->entity);
|
||||
}
|
||||
catch (FieldValidationException $e) {
|
||||
$pass = TRUE;
|
||||
}
|
||||
$this->assertTrue($pass, $key . ' should not pass');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List module UI tests.
|
||||
*/
|
||||
class ListFieldUITestCase extends FieldTestCase {
|
||||
protected $type;
|
||||
protected $hyphen_type;
|
||||
protected $field_name;
|
||||
protected $admin_path;
|
||||
|
||||
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', 'administer fields'));
|
||||
$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, 'Unkeyed lists are accepted.');
|
||||
// Explicit integer keys.
|
||||
$string = "0|Zero\n2|Two";
|
||||
$array = array('0' => 'Zero', '2' => 'Two');
|
||||
$this->assertAllowedValuesInput($string, $array, '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, 'Values can be added and removed.');
|
||||
// Non-integer keys.
|
||||
$this->assertAllowedValuesInput("1.1|One", 'keys must be integers', 'Non integer keys are rejected.');
|
||||
$this->assertAllowedValuesInput("abc|abc", 'keys must be integers', 'Non integer keys are rejected.');
|
||||
// Mixed list of keyed and unkeyed values.
|
||||
$this->assertAllowedValuesInput("Zero\n1|One", 'invalid input', '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', '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, 'Values can be added.');
|
||||
$string = "0|Zero\n1|One";
|
||||
$array = array('0' => 'Zero', '1' => 'One');
|
||||
$this->assertAllowedValuesInput($string, $array, 'Values not in use can be removed.');
|
||||
$this->assertAllowedValuesInput("0|Zero", 'some values are being removed while currently in use', '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, '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, '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, '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, 'Values can be added and removed.');
|
||||
// Non-numeric keys.
|
||||
$this->assertAllowedValuesInput("abc|abc\n", 'each key must be a valid integer or decimal', 'Non numeric keys are rejected.');
|
||||
// Mixed list of keyed and unkeyed values.
|
||||
$this->assertAllowedValuesInput("Zero\n1|One\n", 'invalid input', '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', '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, 'Values can be added.');
|
||||
$string = "0|Zero\n.5|Point five";
|
||||
$array = array('0' => 'Zero', '0.5' => 'Point five');
|
||||
$this->assertAllowedValuesInput($string, $array, 'Values not in use can be removed.');
|
||||
$this->assertAllowedValuesInput("0|Zero", 'some values are being removed while currently in use', '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, '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, 'Unkeyed lists are accepted.');
|
||||
// Explicit keys.
|
||||
$string = "zero|Zero\none|One";
|
||||
$array = array('zero' => 'Zero', 'one' => 'One');
|
||||
$this->assertAllowedValuesInput($string, $array, '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, '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, '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', '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, '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, 'Values can be added.');
|
||||
$string = "Zero\nOne";
|
||||
$array = array('Zero' => 'Zero', 'One' => 'One');
|
||||
$this->assertAllowedValuesInput($string, $array, 'Values not in use can be removed.');
|
||||
$this->assertAllowedValuesInput("Zero", 'some values are being removed while currently in use', '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, '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.", "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, "The 'On' value is stored correctly.");
|
||||
$this->assertFieldByName('off', $off, "The 'Off' value is stored correctly.");
|
||||
$field = field_info_field($this->field_name);
|
||||
$this->assertEqual($field['settings']['allowed_values'], $allowed_values, 'The allowed value is correct');
|
||||
$this->assertFalse(isset($field['settings']['on']), 'The on value is not saved into settings');
|
||||
$this->assertFalse(isset($field['settings']['off']), 'The off value is not saved into settings');
|
||||
}
|
||||
|
||||
/**
|
||||
* List (text) : test 'allowed values function' input.
|
||||
*/
|
||||
function testDynamicListAllowedValuesText() {
|
||||
$this->field_name = 'field_list_text';
|
||||
$this->createListField('list_text', array(
|
||||
'allowed_values_function' => 'list_test_dynamic_values_callback',
|
||||
'allowed_values' => '',
|
||||
));
|
||||
$this->drupalGet($this->admin_path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to create list field of a given type.
|
||||
*
|
||||
* @param string $type
|
||||
* 'list_integer', 'list_float', 'list_text' or 'list_boolean'
|
||||
* @param array $settings
|
||||
*
|
||||
* @throws \FieldException
|
||||
*/
|
||||
protected function createListField($type, $settings = array()) {
|
||||
// Create a test field and instance.
|
||||
$field = array(
|
||||
'field_name' => $this->field_name,
|
||||
'type' => $type,
|
||||
);
|
||||
if (!empty($settings)) {
|
||||
$field['settings'] = $settings;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
11
drupal7/web/modules/field/modules/list/tests/list_test.info
Normal file
11
drupal7/web/modules/field/modules/list/tests/list_test.info
Normal file
|
@ -0,0 +1,11 @@
|
|||
name = "List test"
|
||||
description = "Support module for the List module tests."
|
||||
core = 7.x
|
||||
package = Testing
|
||||
version = VERSION
|
||||
hidden = TRUE
|
||||
|
||||
; Information added by Drupal.org packaging script on 2024-03-06
|
||||
version = "7.100"
|
||||
project = "drupal"
|
||||
datestamp = "1709734591"
|
|
@ -0,0 +1,32 @@
|
|||
<?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;
|
||||
}
|
||||
|
||||
/**
|
||||
* An entity-bound allowed values callback.
|
||||
*/
|
||||
function list_test_dynamic_values_callback($field, $instance, $entity_type, $entity, &$cacheable) {
|
||||
$cacheable = FALSE;
|
||||
// We need the values of the entity as keys.
|
||||
return drupal_map_assoc(array_merge(array($entity->ftlabel), entity_extract_ids($entity_type, $entity)));
|
||||
}
|
12
drupal7/web/modules/field/modules/number/number.info
Normal file
12
drupal7/web/modules/field/modules/number/number.info
Normal file
|
@ -0,0 +1,12 @@
|
|||
name = Number
|
||||
description = Defines numeric field types.
|
||||
package = Core
|
||||
version = VERSION
|
||||
core = 7.x
|
||||
dependencies[] = field
|
||||
files[] = number.test
|
||||
|
||||
; Information added by Drupal.org packaging script on 2024-03-06
|
||||
version = "7.100"
|
||||
project = "drupal"
|
||||
datestamp = "1709734591"
|
45
drupal7/web/modules/field/modules/number/number.install
Normal file
45
drupal7/web/modules/field/modules/number/number.install
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?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,
|
||||
);
|
||||
}
|
430
drupal7/web/modules/field/modules/number/number.module
Normal file
430
drupal7/web/modules/field/modules/number/number.module
Normal file
|
@ -0,0 +1,430 @@
|
|||
<?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']);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($field['type'] == 'number_float') {
|
||||
// Remove the decimal point from float values with decimal
|
||||
// point but no decimal numbers.
|
||||
foreach ($items as $delta => $item) {
|
||||
if (isset($item['value'])) {
|
||||
$items[$delta]['value'] = floatval($item['value']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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'];
|
||||
|
||||
$element = array();
|
||||
|
||||
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']);
|
||||
}
|
201
drupal7/web/modules/field/modules/number/number.test
Normal file
201
drupal7/web/modules/field/modules/number/number.test
Normal file
|
@ -0,0 +1,201 @@
|
|||
<?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', 'administer fields'));
|
||||
$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]", '', '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)), 'Entity was created');
|
||||
$this->assertRaw($value, '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'])),
|
||||
'Correctly failed to save decimal value with more than one decimal point.'
|
||||
);
|
||||
}
|
||||
|
||||
// Try to create entries with minus sign not in the first position.
|
||||
$wrong_entries = array(
|
||||
'3-3',
|
||||
'4-',
|
||||
'1.3-',
|
||||
'1.2-4',
|
||||
'-10-10',
|
||||
);
|
||||
|
||||
foreach ($wrong_entries as $wrong_entry) {
|
||||
$this->drupalGet('test-entity/add/test-bundle');
|
||||
$edit = array(
|
||||
"{$this->field['field_name']}[$langcode][0][value]" => $wrong_entry,
|
||||
);
|
||||
$this->drupalPost(NULL, $edit, t('Save'));
|
||||
$this->assertText(
|
||||
t('Only numbers and the decimal separator (@separator) allowed in ',
|
||||
array('@separator' => $this->field['settings']['decimal_separator'])),
|
||||
'Correctly failed to save decimal value with minus sign in the wrong position.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test number_float field.
|
||||
*/
|
||||
function testNumberFloatField() {
|
||||
$this->field = array(
|
||||
'field_name' => drupal_strtolower($this->randomName()),
|
||||
'type' => 'number_float',
|
||||
'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);
|
||||
|
||||
$langcode = LANGUAGE_NONE;
|
||||
$value = array(
|
||||
'9.' => '9',
|
||||
'.' => '0',
|
||||
'123.55' => '123.55',
|
||||
'.55' => '0.55',
|
||||
'-0.55' => '-0.55',
|
||||
);
|
||||
foreach($value as $key => $value) {
|
||||
$edit = array(
|
||||
"{$this->field['field_name']}[$langcode][0][value]" => $key,
|
||||
);
|
||||
$this->drupalPost('test-entity/add/test-bundle', $edit, t('Save'));
|
||||
$this->assertNoText("PDOException");
|
||||
$this->assertRaw($value, 'Correct value is displayed.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
74
drupal7/web/modules/field/modules/options/options.api.php
Normal file
74
drupal7/web/modules/field/modules/options/options.api.php
Normal file
|
@ -0,0 +1,74 @@
|
|||
<?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
|
||||
* (optional) The instance definition. The hook might be called without an
|
||||
* $instance parameter in contexts where no specific instance can be targeted.
|
||||
* It is recommended to only use instance level properties to filter out
|
||||
* values from a list defined by field level properties.
|
||||
* @param $entity_type
|
||||
* The entity type the field is attached to.
|
||||
* @param $entity
|
||||
* The entity object the field is attached to, or NULL if no entity
|
||||
* exists (e.g. in field settings page).
|
||||
*
|
||||
* @return
|
||||
* The array of options for the field. Array keys are the values to be
|
||||
* 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, $entity_type, $entity) {
|
||||
// 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;
|
||||
}
|
12
drupal7/web/modules/field/modules/options/options.info
Normal file
12
drupal7/web/modules/field/modules/options/options.info
Normal file
|
@ -0,0 +1,12 @@
|
|||
name = Options
|
||||
description = Defines selection, check box and radio button widgets for text and numeric fields.
|
||||
package = Core
|
||||
version = VERSION
|
||||
core = 7.x
|
||||
dependencies[] = field
|
||||
files[] = options.test
|
||||
|
||||
; Information added by Drupal.org packaging script on 2024-03-06
|
||||
version = "7.100"
|
||||
project = "drupal"
|
||||
datestamp = "1709734591"
|
425
drupal7/web/modules/field/modules/options/options.module
Normal file
425
drupal7/web/modules/field/modules/options/options.module
Normal file
|
@ -0,0 +1,425 @@
|
|||
<?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);
|
||||
|
||||
$entity_type = $element['#entity_type'];
|
||||
$entity = $element['#entity'];
|
||||
|
||||
// Prepare the list of options.
|
||||
$options = _options_get_options($field, $instance, $properties, $entity_type, $entity);
|
||||
|
||||
// Put current field values in shape.
|
||||
$default_value = _options_storage_to_form($items, $options, $value_key, $properties);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
// If this is a single-value field, take the first default value, or
|
||||
// default to NULL so that the form element is properly recognized as
|
||||
// not having a default value.
|
||||
if (!$multiple) {
|
||||
$default_value = $default_value ? reset($default_value) : NULL;
|
||||
}
|
||||
|
||||
$element += array(
|
||||
'#type' => $multiple ? 'checkboxes' : 'radios',
|
||||
// Radio buttons need a scalar value.
|
||||
'#default_value' => $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,
|
||||
'strip_tags_and_unescape' => FALSE,
|
||||
'empty_option' => FALSE,
|
||||
'optgroups' => FALSE,
|
||||
);
|
||||
|
||||
$properties = array();
|
||||
|
||||
switch ($type) {
|
||||
case 'select':
|
||||
$properties = array(
|
||||
// Select boxes do not support any HTML tag.
|
||||
'strip_tags_and_unescape' => 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';
|
||||
}
|
||||
elseif (!$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, $entity_type, $entity) {
|
||||
// Get the list of options.
|
||||
$options = (array) module_invoke($field['module'], 'options_list', $field, $instance, $entity_type, $entity);
|
||||
|
||||
// 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 {
|
||||
// The 'strip_tags' option is deprecated. Use 'strip_tags_and_unescape'
|
||||
// when plain text is required (and where the output will be run through
|
||||
// check_plain() before being inserted back into HTML) or 'filter_xss'
|
||||
// when HTML is required.
|
||||
if ($properties['strip_tags']) {
|
||||
$options[$value] = strip_tags($label);
|
||||
}
|
||||
if ($properties['strip_tags_and_unescape']) {
|
||||
$options[$value] = decode_entities(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;
|
||||
}
|
580
drupal7/web/modules/field/modules/options/options.test
Normal file
580
drupal7/web/modules/field/modules/options/options.test
Normal file
|
@ -0,0 +1,580 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Tests for options.module.
|
||||
*/
|
||||
|
||||
class OptionsWidgetsTestCase extends FieldTestCase {
|
||||
protected $card_1;
|
||||
protected $card_2;
|
||||
protected $bool;
|
||||
protected $web_user;
|
||||
|
||||
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(
|
||||
'allowed_values' => array(
|
||||
// Make sure that 0 works as an option.
|
||||
0 => 'Zero',
|
||||
1 => 'One',
|
||||
// Make sure that option text is properly sanitized.
|
||||
2 => 'Some <script>dangerous</script> & unescaped <strong>markup</strong>',
|
||||
// Make sure that HTML entities in option text are not double-encoded.
|
||||
3 => 'Some HTML encoded markup with < & >',
|
||||
),
|
||||
),
|
||||
);
|
||||
$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(
|
||||
'allowed_values' => array(
|
||||
// Make sure that 0 works as an option.
|
||||
0 => 'Zero',
|
||||
1 => 'One',
|
||||
// Make sure that option text is properly sanitized.
|
||||
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(
|
||||
'allowed_values' => array(
|
||||
// Make sure that 1 works as a 'on' value'.
|
||||
1 => 'Zero',
|
||||
// Make sure that option text is properly sanitized.
|
||||
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', 'administer fields'));
|
||||
$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>', '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>', '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', '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 -'))), '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', 'Option text was properly filtered.');
|
||||
$this->assertRaw('Some HTML encoded markup with < & >', 'HTML entities in option text were properly handled and not double-encoded');
|
||||
|
||||
// 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'])), '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)), '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 -'))), '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', 'Option text was properly filtered.');
|
||||
$this->assertRaw('Group 1', '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());
|
||||
|
||||
// Submit form: select the option from optgroup.
|
||||
$edit = array("card_1[$langcode]" => 2);
|
||||
$this->drupalPost(NULL, $edit, t('Save'));
|
||||
$this->assertFieldValues($entity_init, 'card_1', $langcode, array(2));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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', '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', '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 effect 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)), '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', 'Option text was properly filtered.');
|
||||
$this->assertRaw('Group 1', '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>', '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', 'administer fields'));
|
||||
$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 ',
|
||||
'Display setting checkbox available.'
|
||||
);
|
||||
|
||||
$this->assertFieldByXPath(
|
||||
'*//label[@for="edit-' . $this->bool['field_name'] . '-und" and text()="MyOnValue "]',
|
||||
TRUE,
|
||||
'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 ',
|
||||
'Display setting checkbox is available'
|
||||
);
|
||||
$this->assertFieldChecked(
|
||||
'edit-instance-widget-settings-display-label',
|
||||
'Display settings checkbox checked'
|
||||
);
|
||||
$this->assertFieldByXPath(
|
||||
'*//label[@for="edit-' . $this->bool['field_name'] . '-und" and text()="' . $this->bool['field_name'] . ' "]',
|
||||
TRUE,
|
||||
'Display label changes label of the checkbox'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test an options select on a list field with a dynamic allowed values function.
|
||||
*/
|
||||
class OptionsSelectDynamicValuesTestCase extends ListDynamicValuesTestCase {
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Options select dynamic values',
|
||||
'description' => 'Test an options select on a list field with a dynamic allowed values function.',
|
||||
'group' => 'Field types',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the 'options_select' widget (single select).
|
||||
*/
|
||||
function testSelectListDynamic() {
|
||||
// Create an entity.
|
||||
$this->entity->is_new = TRUE;
|
||||
field_test_entity_save($this->entity);
|
||||
// Create a web user.
|
||||
$web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content'));
|
||||
$this->drupalLogin($web_user);
|
||||
|
||||
// Display form.
|
||||
$this->drupalGet('test-entity/manage/' . $this->entity->ftid . '/edit');
|
||||
$options = $this->xpath('//select[@id="edit-test-list-und"]/option');
|
||||
$this->assertEqual(count($options), count($this->test) + 1);
|
||||
foreach ($options as $option) {
|
||||
$value = (string) $option['value'];
|
||||
if ($value != '_none') {
|
||||
$this->assertTrue(array_search($value, $this->test));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
13
drupal7/web/modules/field/modules/text/text.info
Normal file
13
drupal7/web/modules/field/modules/text/text.info
Normal file
|
@ -0,0 +1,13 @@
|
|||
name = Text
|
||||
description = Defines simple text field types.
|
||||
package = Core
|
||||
version = VERSION
|
||||
core = 7.x
|
||||
dependencies[] = field
|
||||
files[] = text.test
|
||||
required = TRUE
|
||||
|
||||
; Information added by Drupal.org packaging script on 2024-03-06
|
||||
version = "7.100"
|
||||
project = "drupal"
|
||||
datestamp = "1709734591"
|
95
drupal7/web/modules/field/modules/text/text.install
Normal file
95
drupal7/web/modules/field/modules/text/text.install
Normal file
|
@ -0,0 +1,95 @@
|
|||
<?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'),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change text field 'format' columns into varchar.
|
||||
*/
|
||||
function text_update_7000() {
|
||||
$spec = array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => FALSE,
|
||||
);
|
||||
$fields = _update_7000_field_read_fields(array(
|
||||
'module' => 'text',
|
||||
'storage_type' => 'field_sql_storage',
|
||||
));
|
||||
foreach ($fields as $field) {
|
||||
if ($field['deleted']) {
|
||||
$table = "field_deleted_data_{$field['id']}";
|
||||
$revision_table = "field_deleted_revision_{$field['id']}";
|
||||
}
|
||||
else {
|
||||
$table = "field_data_{$field['field_name']}";
|
||||
$revision_table = "field_revision_{$field['field_name']}";
|
||||
}
|
||||
$column = $field['field_name'] . '_' . 'format';
|
||||
db_change_field($table, $column, $column, $spec);
|
||||
db_change_field($revision_table, $column, $column, $spec);
|
||||
}
|
||||
}
|
53
drupal7/web/modules/field/modules/text/text.js
Normal file
53
drupal7/web/modules/field/modules/text/text.js
Normal file
|
@ -0,0 +1,53 @@
|
|||
|
||||
(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').first();
|
||||
var $full = $widget.find('.text-full').eq(index).closest('.form-item');
|
||||
var $fullLabel = $full.find('label').first();
|
||||
|
||||
// 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>');
|
||||
var $a = $link.find('a');
|
||||
var toggleClick = true;
|
||||
$link.bind('click', function (e) {
|
||||
if (toggleClick) {
|
||||
$summary.hide();
|
||||
$a.html(Drupal.t('Edit summary'));
|
||||
$link.appendTo($fullLabel);
|
||||
}
|
||||
else {
|
||||
$summary.show();
|
||||
$a.html(Drupal.t('Hide summary'));
|
||||
$link.appendTo($summaryLabel);
|
||||
}
|
||||
toggleClick = !toggleClick;
|
||||
return false;
|
||||
}).appendTo($summaryLabel);
|
||||
|
||||
// If no summary is set, hide the summary field.
|
||||
if ($(this).find('.text-summary').val() == '') {
|
||||
$link.click();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
})(jQuery);
|
621
drupal7/web/modules/field/modules/text/text.module
Normal file
621
drupal7/web/modules/field/modules/text/text.module
Normal file
|
@ -0,0 +1,621 @@
|
|||
<?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('Trimmed limit'),
|
||||
'#type' => 'textfield',
|
||||
'#field_suffix' => t('characters'),
|
||||
'#size' => 10,
|
||||
'#default_value' => $settings['trim_length'],
|
||||
'#element_validate' => array('element_validate_integer_positive'),
|
||||
'#description' => t('If the summary is not set, the trimmed %label field will be shorter than this character limit.', array('%label' => $instance['label'])),
|
||||
'#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('Trimmed limit: @trim_length characters', array('@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"];
|
||||
}
|
||||
if ($item[$column] === '') {
|
||||
return '';
|
||||
}
|
||||
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 the input text is NULL, return unchanged.
|
||||
if (is_null($text)) {
|
||||
return 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();
|
||||
}
|
534
drupal7/web/modules/field/modules/text/text.test
Normal file
534
drupal7/web/modules/field/modules/text/text.test
Normal file
|
@ -0,0 +1,534 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Tests for text.module.
|
||||
*/
|
||||
|
||||
class TextFieldTestCase extends DrupalWebTestCase {
|
||||
protected $instance;
|
||||
protected $admin_user;
|
||||
protected $web_user;
|
||||
protected $field;
|
||||
protected $field_name;
|
||||
|
||||
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]", '', 'Widget is displayed');
|
||||
$this->assertNoFieldByName("{$this->field_name}[$langcode][0][format]", '1', '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)), '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]", '', 'Widget is displayed');
|
||||
$this->assertNoFieldByName("{$this->field_name}[$langcode][0][format]", '', '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)), '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, 'HTML tags are not displayed.');
|
||||
$this->assertRaw(check_plain($value), '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, 'Widget is displayed');
|
||||
$this->assertFieldByName("{$this->field_name}[$langcode][0][format]", NULL, '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)), '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, 'Value is displayed unfiltered');
|
||||
}
|
||||
}
|
||||
|
||||
class TextSummaryTestCase extends DrupalWebTestCase {
|
||||
protected $article_creator;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for the NULL value.
|
||||
*/
|
||||
function testNullSentence() {
|
||||
$summary = text_summary(NULL);
|
||||
$this->assertNull($summary, 'text_summary() casts returned null');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, format_string('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, 'Article with with summary and no body has been submitted.');
|
||||
}
|
||||
}
|
||||
|
||||
class TextTranslationTestCase extends DrupalWebTestCase {
|
||||
protected $format;
|
||||
protected $admin;
|
||||
protected $translator;
|
||||
|
||||
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',
|
||||
'administer fields',
|
||||
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')), '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, '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'), '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], format_string('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], format_string('The body field with delta @delta is hidden.', array('@delta' => 0)));
|
||||
$this->assertText($body[1], format_string('The body field with delta @delta is shown.', array('@delta' => 1)));
|
||||
}
|
||||
}
|
3891
drupal7/web/modules/field/tests/field.test
Normal file
3891
drupal7/web/modules/field/tests/field.test
Normal file
File diff suppressed because it is too large
Load diff
500
drupal7/web/modules/field/tests/field_test.entity.inc
Normal file
500
drupal7/web/modules/field/tests/field_test.entity.inc
Normal file
|
@ -0,0 +1,500 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Defines an entity type.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_entity_info().
|
||||
*/
|
||||
function field_test_entity_info() {
|
||||
// If requested, clear the field cache while this hook is being called. See
|
||||
// testFieldInfoCache().
|
||||
if (variable_get('field_test_clear_info_cache_in_hook_entity_info', FALSE)) {
|
||||
field_info_cache_clear();
|
||||
}
|
||||
|
||||
$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(
|
||||
'label' => 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(
|
||||
'label' => 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(
|
||||
'label' => 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')) + $bundles,
|
||||
'view modes' => $test_entity_modes,
|
||||
),
|
||||
// In this case, the bundle key is not stored in the database.
|
||||
'test_entity_bundle' => array(
|
||||
'label' => 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')) + $bundles,
|
||||
'view modes' => $test_entity_modes,
|
||||
),
|
||||
// @see EntityPropertiesTestCase::testEntityLabel()
|
||||
'test_entity_no_label' => array(
|
||||
'label' => 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(
|
||||
'label' => 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(
|
||||
'label' => 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);
|
||||
}
|
||||
}
|
413
drupal7/web/modules/field/tests/field_test.field.inc
Normal file
413
drupal7/web/modules/field/tests/field_test.field.inc
Normal file
|
@ -0,0 +1,413 @@
|
|||
<?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(
|
||||
'foreign_key_name' => 'shape',
|
||||
),
|
||||
'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) {
|
||||
$args = func_get_args();
|
||||
field_test_memorize(__FUNCTION__, $args);
|
||||
|
||||
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_insert().
|
||||
*/
|
||||
function field_test_field_insert($entity_type, $entity, $field, $instance, $items) {
|
||||
$args = func_get_args();
|
||||
field_test_memorize(__FUNCTION__, $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_update().
|
||||
*/
|
||||
function field_test_field_update($entity_type, $entity, $field, $instance, $items) {
|
||||
$args = func_get_args();
|
||||
field_test_memorize(__FUNCTION__, $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements 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_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) {
|
||||
$args = func_get_args();
|
||||
field_test_memorize(__FUNCTION__, $args);
|
||||
|
||||
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':
|
||||
if (!empty($items)) {
|
||||
$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;
|
||||
}
|
12
drupal7/web/modules/field/tests/field_test.info
Normal file
12
drupal7/web/modules/field/tests/field_test.info
Normal file
|
@ -0,0 +1,12 @@
|
|||
name = "Field API Test"
|
||||
description = "Support module for the Field API tests."
|
||||
core = 7.x
|
||||
package = Testing
|
||||
files[] = field_test.entity.inc
|
||||
version = VERSION
|
||||
hidden = TRUE
|
||||
|
||||
; Information added by Drupal.org packaging script on 2024-03-06
|
||||
version = "7.100"
|
||||
project = "drupal"
|
||||
datestamp = "1709734591"
|
162
drupal7/web/modules/field/tests/field_test.install
Normal file
162
drupal7/web/modules/field/tests/field_test.install
Normal file
|
@ -0,0 +1,162 @@
|
|||
<?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 identifier 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 identifier 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 {
|
||||
$foreign_keys = array();
|
||||
// The 'foreign keys' key is not always used in tests.
|
||||
if (!empty($field['settings']['foreign_key_name'])) {
|
||||
$foreign_keys['foreign keys'] = array(
|
||||
// This is a dummy foreign key definition, references a table that
|
||||
// doesn't exist, but that's not a problem.
|
||||
$field['settings']['foreign_key_name'] => array(
|
||||
'table' => $field['settings']['foreign_key_name'],
|
||||
'columns' => array($field['settings']['foreign_key_name'] => 'id'),
|
||||
),
|
||||
);
|
||||
}
|
||||
return array(
|
||||
'columns' => array(
|
||||
'shape' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 32,
|
||||
'not null' => FALSE,
|
||||
),
|
||||
'color' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 32,
|
||||
'not null' => FALSE,
|
||||
),
|
||||
),
|
||||
) + $foreign_keys;
|
||||
}
|
||||
}
|
284
drupal7/web/modules/field/tests/field_test.module
Normal file
284
drupal7/web/modules/field/tests/field_test.module
Normal file
|
@ -0,0 +1,284 @@
|
|||
<?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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_query_alter().
|
||||
*/
|
||||
function field_test_entity_query_alter(&$query) {
|
||||
if (variable_get('field_test_entity_query_alter_execute_callback', FALSE)) {
|
||||
$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)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements callback_entity_info_label().
|
||||
*
|
||||
* @return
|
||||
* The label of the entity prefixed with "label callback".
|
||||
*/
|
||||
function field_test_entity_label_callback($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');
|
||||
}
|
||||
|
||||
if (isset($output['test_field'])) {
|
||||
$output['test_field'][] = array('#markup' => 'field language is ' . $context['language']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_widget_properties_alter().
|
||||
*/
|
||||
function field_test_field_widget_properties_alter(&$widget, $context) {
|
||||
// Make the alter_test_text field 42 characters for nodes and comments.
|
||||
if (in_array($context['entity_type'], array('node', 'comment')) && ($context['field']['field_name'] == 'alter_test_text')) {
|
||||
$widget['settings']['size'] = 42;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_widget_properties_ENTITY_TYPE_alter().
|
||||
*/
|
||||
function field_test_field_widget_properties_user_alter(&$widget, $context) {
|
||||
// Always use buttons for the alter_test_options field on user forms.
|
||||
if ($context['field']['field_name'] == 'alter_test_options') {
|
||||
$widget['type'] = 'options_buttons';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_widget_form_alter().
|
||||
*/
|
||||
function field_test_field_widget_form_alter(&$element, &$form_state, $context) {
|
||||
switch ($context['field']['field_name']) {
|
||||
case 'alter_test_text':
|
||||
drupal_set_message('Field size: ' . $context['instance']['widget']['settings']['size']);
|
||||
break;
|
||||
|
||||
case 'alter_test_options':
|
||||
drupal_set_message('Widget type: ' . $context['instance']['widget']['type']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_query_TAG_alter() for tag 'efq_table_prefixing_test'.
|
||||
*
|
||||
* @see EntityFieldQueryTestCase::testTablePrefixing()
|
||||
*/
|
||||
function field_test_query_efq_table_prefixing_test_alter(&$query) {
|
||||
// Add an additional join onto the entity base table. This will cause an
|
||||
// exception if the EFQ does not properly prefix the base table.
|
||||
$query->join('test_entity','te2','%alias.ftid = test_entity.ftid');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_query_TAG_alter() for tag 'store_global_test_query'.
|
||||
*/
|
||||
function field_test_query_store_global_test_query_alter($query) {
|
||||
// Save the query in a global variable so that it can be examined by tests.
|
||||
// This can be used by any test which needs to check a query, but see
|
||||
// FieldSqlStorageTestCase::testFieldSqlStorageMultipleConditionsSameColumn()
|
||||
// for an example.
|
||||
$GLOBALS['test_query'] = $query;
|
||||
}
|
368
drupal7/web/modules/field/tests/field_test.storage.inc
Normal file
368
drupal7/web/modules/field/tests/field_test.storage.inc
Normal file
|
@ -0,0 +1,368 @@
|
|||
<?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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 $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) {
|
||||
$row->deleted = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_field_test_storage_data($data);
|
||||
}
|
11
drupal7/web/modules/field/tests/field_test_schema_alter.info
Normal file
11
drupal7/web/modules/field/tests/field_test_schema_alter.info
Normal file
|
@ -0,0 +1,11 @@
|
|||
name = "Field API Schema Alter Test"
|
||||
description = "Support module for the Field API schema alter tests."
|
||||
core = 7.x
|
||||
package = Testing
|
||||
version = VERSION
|
||||
hidden = TRUE
|
||||
|
||||
; Information added by Drupal.org packaging script on 2024-03-06
|
||||
version = "7.100"
|
||||
project = "drupal"
|
||||
datestamp = "1709734591"
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Install, update and uninstall functions for the field_test_schema_alter module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_field_schema_alter().
|
||||
*/
|
||||
function field_test_schema_alter_field_schema_alter(&$schema, $field) {
|
||||
if ($field['type'] == 'test_field') {
|
||||
// Alter the field type.
|
||||
$schema['columns']['value']['type'] = 'float';
|
||||
// Add an additional column of data.
|
||||
$schema['columns']['additional_column'] = array(
|
||||
'description' => "Additional column added to image field table.",
|
||||
'type' => 'varchar',
|
||||
'length' => 128,
|
||||
'not null' => FALSE,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
<?php
|
14
drupal7/web/modules/field/theme/field-rtl.css
Normal file
14
drupal7/web/modules/field/theme/field-rtl.css
Normal file
|
@ -0,0 +1,14 @@
|
|||
|
||||
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;
|
||||
}
|
28
drupal7/web/modules/field/theme/field.css
Normal file
28
drupal7/web/modules/field/theme/field.css
Normal file
|
@ -0,0 +1,28 @@
|
|||
|
||||
/* 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;
|
||||
}
|
64
drupal7/web/modules/field/theme/field.tpl.php
Normal file
64
drupal7/web/modules/field/theme/field.tpl.php
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file field.tpl.php
|
||||
* Default template implementation to display the value of a field.
|
||||
*
|
||||
* This file is not used by Drupal core, which uses theme functions instead for
|
||||
* performance reasons. The markup is the same, though, so if you want to use
|
||||
* template files rather than functions to extend field theming, copy this to
|
||||
* your custom theme. See theme_field() for a discussion of performance.
|
||||
*
|
||||
* 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()
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
?>
|
||||
<!--
|
||||
This file is not used by Drupal core, which uses theme functions instead.
|
||||
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; ?>"<?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>
|
Loading…
Add table
Add a link
Reference in a new issue