Commit e998857e authored by webchick's avatar webchick

#516138 by yched, KarenS, quicksketch, bangpound, et al.: CC-FREAKING-K IN CORE! OH YEAH! :D

parent 24289301
......@@ -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"
*/
......
......@@ -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);
......
......@@ -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']) {
......
......@@ -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);
}
}
/**
......
......@@ -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();
......
......@@ -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'];
......
......@@ -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.
......
......@@ -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' => '<p>' . 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'))) . '</p>',
'#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'] .= '<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'] .= 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']];
}
......@@ -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 '&euro; '. 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().
*
......
......@@ -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[