From e998857eb8a5ef4d2ebe381a57c19b1b355fe4ef Mon Sep 17 00:00:00 2001 From: Angie Byron Date: Wed, 19 Aug 2009 13:31:14 +0000 Subject: #516138 by yched, KarenS, quicksketch, bangpound, et al.: CC-FREAKING-K IN CORE! OH YEAH! :D --- modules/field/field.api.php | 57 +++++++++++ modules/field/field.attach.inc | 30 +++++- modules/field/field.crud.inc | 2 +- modules/field/field.default.inc | 7 +- modules/field/field.form.inc | 10 +- modules/field/field.info.inc | 6 ++ modules/field/field.module | 95 +++++++++++++++-- modules/field/modules/list/list.module | 159 +++++++++++++++++++++++------ modules/field/modules/number/number.module | 76 ++++++++++++++ modules/field/modules/text/text.module | 74 ++++++++++++++ 10 files changed, 466 insertions(+), 50 deletions(-) (limited to 'modules/field') diff --git a/modules/field/field.api.php b/modules/field/field.api.php index 39618ea08..f8d2137d9 100644 --- a/modules/field/field.api.php +++ b/modules/field/field.api.php @@ -100,6 +100,63 @@ function hook_fieldable_info_alter(&$info) { $info['node']['cacheable'] = FALSE; } +/** + * Expose "pseudo-field" components on fieldable objects. + * + * Field UI's 'Manage fields' page lets users re-order fields, but also + * non-field components. For nodes, that would be title, menu settings, or + * other elements exposed by contributed modules through hook_form() or + * hook_form_alter(). + * + * Fieldable entities or contributed modules that want to have their components + * supported should expose them using this hook, and use + * field_attach_extra_weight() to retrieve the user-defined weight when + * inserting the component. + * + * @param $bundle + * The name of the bundle being considered. + * @return + * An array of 'pseudo-field' components. The keys are the name of the element + * as it appears in the form structure. The values are arrays with the + * following key/value pairs: + * - label: The human readable name of the component. + * - description: A short description of the component contents. + * - weight: The default weight of the element. + * - view: (optional) The name of the element as it appears in the rendered + * structure, if different from the name in the form. + */ +function hook_field_extra_fields($bundle) { + $extra = array(); + + if ($type = node_type_get_type($bundle)) { + if ($type->has_title) { + $extra['title'] = array( + 'label' => $type->title_label, + 'description' => t('Node module element.'), + 'weight' => -5, + ); + } + if ($bundle == 'poll' && module_exists('poll')) { + $extra['title'] = array( + 'label' => t('Poll title'), + 'description' => t('Poll module title.'), + 'weight' => -5, + ); + $extra['choice_wrapper'] = array( + 'label' => t('Poll choices'), + 'description' => t('Poll module choices.'), + 'weight' => -4, + ); + $extra['settings'] = array( + 'label' => t('Poll settings'), + 'description' => t('Poll module settings.'), + 'weight' => -3, + ); + } + } + return $extra; +} + /** * @} End of "ingroup field_fieldable_type" */ diff --git a/modules/field/field.attach.inc b/modules/field/field.attach.inc index 55241f5af..838c6d93c 100644 --- a/modules/field/field.attach.inc +++ b/modules/field/field.attach.inc @@ -457,6 +457,11 @@ function _field_invoke_multiple_default($op, $obj_type, $objects, &$a = NULL, &$ function field_attach_form($obj_type, $object, &$form, &$form_state) { $form += (array) _field_invoke_default('form', $obj_type, $object, $form, $form_state); + // Add custom weight handling. + list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object); + $form['#pre_render'][] = '_field_extra_weights_pre_render'; + $form['#extra_fields'] = field_extra_fields($bundle); + // Let other modules make changes to the form. foreach (module_implements('field_attach_form') as $module) { $function = $module . '_field_attach_form'; @@ -1043,6 +1048,11 @@ function field_attach_view($obj_type, $object, $build_mode = 'full') { $output = _field_invoke_default('view', $obj_type, $object, $build_mode); + // Add custom weight handling. + list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object); + $output['#pre_render'][] = '_field_extra_weights_pre_render'; + $output['#extra_fields'] = field_extra_fields($bundle); + // Let other modules make changes after rendering the view. drupal_alter('field_attach_view', $output, $obj_type, $object, $build_mode); @@ -1050,6 +1060,24 @@ function field_attach_view($obj_type, $object, $build_mode = 'full') { } +/** + * Retrieve the user-defined weight for a 'pseudo-field' component. + * + * @param $bundle + * The bundle name. + * @param $pseudo_field + * The name of the 'pseudo-field'. + * @return + * The weight for the 'pseudo-field', respecting the user settings stored by + * field.module. + */ +function field_attach_extra_weight($bundle, $pseudo_field) { + $extra = field_extra_fields($bundle); + if (isset($extra[$pseudo_field])) { + return $extra[$pseudo_field]['weight']; + } +} + /** * Implement hook_node_prepare_translation. * @@ -1084,7 +1112,7 @@ function field_attach_create_bundle($bundle) { // Clear the cache. field_cache_clear(); - + menu_rebuild(); foreach (module_implements('field_attach_create_bundle') as $module) { $function = $module . '_field_attach_create_bundle'; $function($bundle); diff --git a/modules/field/field.crud.inc b/modules/field/field.crud.inc index 08afcb695..cea858d53 100644 --- a/modules/field/field.crud.inc +++ b/modules/field/field.crud.inc @@ -637,8 +637,8 @@ function field_read_instances($params = array(), $include_additional = array()) foreach ($params as $key => $value) { $query->condition('fci.' . $key, $value); } - $query->condition('fc.active', 1); if (!isset($include_additional['include_inactive']) || !$include_additional['include_inactive']) { + $query->condition('fc.active', 1); $query->condition('fci.widget_active', 1); } if (!isset($include_additional['include_deleted']) || !$include_additional['include_deleted']) { diff --git a/modules/field/field.default.inc b/modules/field/field.default.inc index 927571e84..2c5e175b4 100644 --- a/modules/field/field.default.inc +++ b/modules/field/field.default.inc @@ -43,11 +43,8 @@ function field_default_submit($obj_type, $object, $field, $instance, &$items, $f function field_default_insert($obj_type, $object, $field, $instance, &$items) { // _field_invoke() populates $items with an empty array if the $object has no // entry for the field, so we check on the $object itself. - if (!property_exists($object, $field['field_name']) && !empty($instance['default_value_function'])) { - $function = $instance['default_value_function']; - if (drupal_function_exists($function)) { - $items = $function($obj_type, $object, $field, $instance); - } + if (empty($object) || !property_exists($object, $field['field_name'])) { + $items = field_get_default_value($obj_type, $object, $field, $instance); } } /** diff --git a/modules/field/field.form.inc b/modules/field/field.form.inc index 10f687a56..0bc155bc4 100644 --- a/modules/field/field.form.inc +++ b/modules/field/field.form.inc @@ -37,13 +37,9 @@ function field_default_form($obj_type, $object, $field, $instance, $items, &$for 'instance' => $instance, ); - // Populate widgets with default values if we're creating a new object. - if (empty($items) && empty($id) && !empty($instance['default_value_function'])) { - $items = array(); - $function = $instance['default_value_function']; - if (drupal_function_exists($function)) { - $items = $function($obj_type, $object, $field, $instance); - } + // Populate widgets with default values when creating a new object. + if (empty($items) && empty($id)) { + $items = field_get_default_value($obj_type, $object, $field, $instance); } $form_element = array(); diff --git a/modules/field/field.info.inc b/modules/field/field.info.inc index 5507f548d..685edd2d4 100644 --- a/modules/field/field.info.inc +++ b/modules/field/field.info.inc @@ -25,6 +25,7 @@ */ function _field_info_cache_clear() { _field_info_collate_types(TRUE); + drupal_static_reset('field_build_modes'); _field_info_collate_fields(TRUE); } @@ -263,6 +264,11 @@ function _field_info_prepare_instance($instance, $field) { // Make sure all expected instance settings are present. $instance['settings'] += field_info_instance_settings($field['type']); + // Set a default value for the instance. + if (field_behaviors_widget('default value', $instance) == FIELD_BEHAVIOR_DEFAULT && !isset($instance['default_value'])) { + $instance['default_value'] = NULL; + } + // Fallback to default widget if widget type is not available. if (!field_info_widget_types($instance['widget']['type'])) { $instance['widget']['type'] = $field_type['default_widget']; diff --git a/modules/field/field.module b/modules/field/field.module index 2be05058d..e1e03b554 100644 --- a/modules/field/field.module +++ b/modules/field/field.module @@ -265,6 +265,32 @@ function field_associate_fields($module) { } } +/** + * Helper function to get the default value for a field on an object. + * + * @param $obj_type + * The type of $object; e.g. 'node' or 'user'. + * @param $object + * The object for the operation. + * @param $field + * The field structure. + * @param $instance + * The instance structure. + */ +function field_get_default_value($obj_type, $object, $field, $instance) { + $items = array(); + if (!empty($instance['default_value_function'])) { + $function = $instance['default_value_function']; + if (drupal_function_exists($function)) { + $items = $function($obj_type, $object, $field, $instance); + } + } + elseif (!empty($instance['default_value'])) { + $items = $instance['default_value']; + } + return $items; +} + /** * Helper function to filter out empty values. * @@ -279,15 +305,15 @@ function field_associate_fields($module) { * TODO D7: poorly named... */ function field_set_empty($field, $items) { - // Filter out empty values. - $filtered = array(); $function = $field['module'] . '_field_is_empty'; + // We ensure the function is loaded, but explicitly break if it is missing. + drupal_function_exists($function); foreach ((array) $items as $delta => $item) { - if (!$function($item, $field)) { - $filtered[] = $item; + if ($function($item, $field)) { + unset($items[$delta]); } } - return $filtered; + return array_values($items); } /** @@ -335,7 +361,7 @@ function _field_sort_items_value_helper($a, $b) { * Registry of available build modes. */ function field_build_modes($obj_type) { - static $info; + $info = &drupal_static(__FUNCTION__, array()); if (!isset($info[$obj_type])) { $info[$obj_type] = module_invoke_all('field_build_modes', $obj_type); @@ -343,6 +369,63 @@ function field_build_modes($obj_type) { return $info[$obj_type]; } +/** + * Registry of pseudo-field components in a given bundle. + * + * @param $bundle_name + * The bundle name. + * @return + * The array of pseudo-field elements in the bundle. + */ +function field_extra_fields($bundle_name) { + $info = &drupal_static(__FUNCTION__, array()); + + if (empty($info)) { + $info = array(); + $bundles = field_info_bundles(); + foreach ($bundles as $bundle => $bundle_label) { + // Gather information about non-field object additions. + $extra = module_invoke_all('field_extra_fields', $bundle); + drupal_alter('field_extra_fields', $extra, $bundle); + + // Add saved weights. + foreach (variable_get("field_extra_weights_$bundle", array()) as $key => $value) { + // Some stored entries might not exist anymore, for instance if uploads + // have been disabled or vocabularies were deleted. + if (isset($extra[$key])) { + $extra[$key]['weight'] = $value; + } + } + $info[$bundle] = $extra; + } + } + if (array_key_exists($bundle_name, $info)) { + return $info[$bundle_name]; + } + else { + return array(); + } +} + +/** + * Pre-render callback to adjust weights of non-field elements on objects. + */ +function _field_extra_weights_pre_render($elements) { + if (isset($elements['#extra_fields'])) { + foreach ($elements['#extra_fields'] as $key => $value) { + // Some core 'fields' use a different key in node forms and in 'view' + // render arrays. Ensure that we are not on a form first. + if (!isset($elements['#build_id']) && isset($value['view']) && isset($elements[$value['view']])) { + $elements[$value['view']]['#weight'] = $value['weight']; + } + elseif (isset($elements[$key])) { + $elements[$key]['#weight'] = $value['weight']; + } + } + } + return $elements; +} + /** * Clear the cached information; called in several places when field * information is changed. diff --git a/modules/field/modules/list/list.module b/modules/field/modules/list/list.module index 409cec782..f7401b837 100644 --- a/modules/field/modules/list/list.module +++ b/modules/field/modules/list/list.module @@ -28,28 +28,28 @@ function list_field_info() { 'list' => array( 'label' => t('List'), 'description' => t('This field stores numeric keys from key/value lists of allowed values where the key is a simple alias for the position of the value, i.e. 0|First option, 1|Second option, 2|Third option.'), - 'settings' => array('allowed_values_function' => ''), + 'settings' => array('allowed_values' => '', 'allowed_values_function' => ''), 'default_widget' => 'options_select', 'default_formatter' => 'list_default', ), 'list_boolean' => array( 'label' => t('Boolean'), 'description' => t('This field stores simple on/off or yes/no options.'), - 'settings' => array('allowed_values_function' => ''), + 'settings' => array('allowed_values' => '', 'allowed_values_function' => ''), 'default_widget' => 'options_select', 'default_formatter' => 'list_default', ), 'list_number' => array( 'label' => t('List (numeric)'), 'description' => t('This field stores keys from key/value lists of allowed numbers where the stored numeric key has significance and must be preserved, i.e. \'Lifetime in days\': 1|1 day, 7|1 week, 31|1 month.'), - 'settings' => array('allowed_values_function' => ''), + 'settings' => array('allowed_values' => '', 'allowed_values_function' => ''), 'default_widget' => 'options_select', 'default_formatter' => 'list_default', ), 'list_text' => array( 'label' => t('List (text)'), 'description' => t('This field stores keys from key/value lists of allowed values where the stored key has significance and must be a varchar, i.e. \'US States\': IL|Illinois, IA|Iowa, IN|Indiana'), - 'settings' => array('allowed_values_function' => ''), + 'settings' => array('allowed_values' => '', 'allowed_values_function' => ''), 'default_widget' => 'options_select', 'default_formatter' => 'list_default', ), @@ -97,6 +97,131 @@ function list_field_schema($field) { ); } +/** + * Implement hook_field_settings_form(). + */ +function list_field_settings_form($field, $instance) { + $settings = $field['settings']; + + $form['allowed_values'] = array( + '#type' => 'textarea', + '#title' => t('Allowed values list'), + '#default_value' => $settings['allowed_values'], + '#required' => FALSE, + '#rows' => 10, + '#description' => '

' . t('The possible values this field can contain. Enter one value per line, in the format key|label. The key is the value that will be stored in the database, and must be a %type value. The label is optional, and the key will be used as the label if no label is specified.', array('%type' => $field['type'] == 'list_text' ? t('text') : t('numeric'))) . '

', + '#element_validate' => array('list_allowed_values_validate'), + '#list_field_type' => $field['type'], + '#access' => empty($settings['allowed_values_function']), + ); + + // Alter the description for allowed values depending on the widget type. + if ($instance['widget']['type'] == 'options_onoff') { + $form['allowed_values']['#description'] .= '

' . t("For a 'single on/off checkbox' widget, define the 'off' value first, then the 'on' value in the Allowed values section. Note that the checkbox will be labeled with the label of the 'on' value.") . '

'; + } + elseif ($instance['widget']['type'] == 'options_buttons') { + $form['allowed_values']['#description'] .= '

' . t("The 'checkboxes/radio buttons' widget will display checkboxes if the Number of values option is greater than 1 for this field, otherwise radios will be displayed.") . '

'; + } + $form['allowed_values']['#description'] .= t('Allowed HTML tags in labels: @tags', array('@tags' => _field_filter_xss_display_allowed_tags())); + + $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; +} + +/** + * Create an array of allowed values for this field. + */ +function list_allowed_values($field) { + $allowed_values = drupal_static(__FUNCTION__, array()); + + if (isset($allowed_values[$field['field_name']])) { + return $allowed_values[$field['field_name']]; + } + + $allowed_values[$field['field_name']] = array(); + + $function = $field['settings']['allowed_values_function']; + if (!empty($function) && drupal_function_exists($function)) { + $allowed_values[$field['field_name']] = $function($field); + } + elseif (!empty($field['settings']['allowed_values'])) { + $allowed_values[$field['field_name']] = list_allowed_values_list($field['settings']['allowed_values'], $field['type'] == 'list'); + } + + return $allowed_values[$field['field_name']]; +} + +/** + * Create an array of the allowed values for this field. + * + * Explode a string with keys and labels separated with '|' and with each new + * value on its own line. + * + * @param $string_values + * The list of choices as a string. + * @param $position_keys + * Boolean value indicating whether to generate keys based on the position of + * the value if a key is not manually specified, effectively generating + * integer-based keys. This should only be TRUE for fields that have a type of + * "list". Otherwise the value will be used as the key if not specified. + */ +function list_allowed_values_list($string_values, $position_keys = FALSE) { + $allowed_values = array(); + + $list = explode("\n", $string_values); + $list = array_map('trim', $list); + $list = array_filter($list, 'strlen'); + foreach ($list as $key => $value) { + // Sanitize the user input with a permissive filter. + $value = field_filter_xss($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; + } + $allowed_values[$key] = (isset($value) && $value !== '') ? $value : $key; + } + + return $allowed_values; +} + +/** + * Element validate callback; check that the entered values are valid. + */ +function list_allowed_values_validate($element, &$form_state) { + $values = list_allowed_values_list($element['#value'], $element['#list_field_type'] == 'list'); + $field_type = $element['#list_field_type']; + foreach ($values as $key => $value) { + if ($field_type == 'list_number' && !is_numeric($key)) { + form_error($element, t('The entered available values are not valid. Each key must be a valid integer or decimal.')); + break; + } + elseif ($field_type == 'list_text' && strlen($key) > 255) { + form_error($element, t('The entered available values are not valid. Each key must be a string less than 255 characters.')); + break; + } + elseif ($field_type == 'list' && (!preg_match('/^-?\d+$/', $key))) { + form_error($element, t('The entered available values are not valid. All specified keys must be integers.')); + break; + } + } +} + /** * Implement hook_field_validate(). * @@ -161,29 +286,3 @@ function theme_field_formatter_list_default($element) { function theme_field_formatter_list_key($element) { return $element['#item']['safe']; } - -/** - * Create an array of the allowed values for this field. - * - * Call the allowed_values_function to retrieve the allowed - * values array. - * - * TODO Rework this to create a method of selecting plugable allowed values lists. - */ -function list_allowed_values($field) { - static $allowed_values; - - if (isset($allowed_values[$field['field_name']])) { - return $allowed_values[$field['field_name']]; - } - - $allowed_values[$field['field_name']] = array(); - - if (isset($field['settings']['allowed_values_function'])) { - $function = $field['settings']['allowed_values_function']; - if (drupal_function_exists($function)) { - $allowed_values[$field['field_name']] = $function($field); - } - } - return $allowed_values[$field['field_name']]; -} diff --git a/modules/field/modules/number/number.module b/modules/field/modules/number/number.module index eb98aed5b..52b3685f3 100644 --- a/modules/field/modules/number/number.module +++ b/modules/field/modules/number/number.module @@ -87,6 +87,82 @@ function number_field_schema($field) { ); } +/** + * Implement hook_field_settings_form(). + */ +function number_field_settings_form($field, $instance) { + $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.'), + ); + $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.'), + ); + $form['decimal'] = array( + '#type' => 'select', + '#title' => t('Decimal marker'), + '#options' => array( + '.' => 'decimal point', + ',' => 'comma', + ' ' => 'space', + ), + '#default_value' => $settings['decimal'], + '#description' => t('The character users will input to mark the decimal point in forms.'), + ); + } + + return $form; +} + +/** + * Implement 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 suffixed to the value, like ' m', ' kb/s'. Leave blank for none. Separate singular and plural values with a pipe ('pound|pounds')."), + ); + + return $form; +} + /** * Implement hook_field_validate(). * diff --git a/modules/field/modules/text/text.module b/modules/field/modules/text/text.module index b123602fd..ba69c310b 100644 --- a/modules/field/modules/text/text.module +++ b/modules/field/modules/text/text.module @@ -128,6 +128,51 @@ function text_field_schema($field) { ); } +/** + * Implement hook_field_settings_form(). + */ +function text_field_settings_form($field, $instance) { + $settings = $field['settings']; + + $form['max_length'] = array( + '#type' => 'textfield', + '#title' => t('Maximum length'), + '#default_value' => $settings['max_length'], + '#required' => FALSE, + '#description' => t('The maximum length of the field in characters. Leave blank for an unlimited size.'), + '#element_validate' => array('_element_validate_integer_positive'), + ); + + return $form; +} + +/** + * Implement 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 input 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 format.'), + ); + } + + return $form; +} + /** * Implement hook_field_validate(). * @@ -466,6 +511,35 @@ function text_field_widget_info() { ); } +/** + * Implement 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; +} + /** * Implement FAPI hook_elements(). * -- cgit v1.2.3