Index: modules/field/field.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.api.php,v
retrieving revision 1.26
diff -u -r1.26 field.api.php
--- modules/field/field.api.php 14 Aug 2009 05:16:26 -0000 1.26
+++ modules/field/field.api.php 17 Aug 2009 21:55:39 -0000
@@ -101,6 +101,64 @@
}
/**
+ * Expose 'psuedo-field' components on fieldable objects.
+ *
+ * Field UI's 'Manage fields' page lets users reorder fields, but also
+ * non-field components. For nodes, that would be title, menu settings, or
+ * other elements 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 render
+ * 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"
*/
Index: modules/field/field.form.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.form.inc,v
retrieving revision 1.14
diff -u -r1.14 field.form.inc
--- modules/field/field.form.inc 17 Aug 2009 07:12:16 -0000 1.14
+++ modules/field/field.form.inc 17 Aug 2009 21:55:41 -0000
@@ -37,13 +37,9 @@
'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 if we are creating a new object.
+ if (empty($items) && empty($id)) {
+ $items = field_get_default_value($obj_type, $object, $field, $instance);
}
$form_element = array();
Index: modules/field/field.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.module,v
retrieving revision 1.22
diff -u -r1.22 field.module
--- modules/field/field.module 11 Aug 2009 14:59:40 -0000 1.22
+++ modules/field/field.module 17 Aug 2009 21:55:41 -0000
@@ -266,6 +266,32 @@
}
/**
+ * 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.
*
* On order to keep marker rows in the database, the function ensures
@@ -279,15 +305,15 @@
* 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 @@
* 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);
@@ -344,6 +370,63 @@
}
/**
+ * 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 removed...
+ 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. Check we're 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.
*/
Index: modules/field/field.attach.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.attach.inc,v
retrieving revision 1.36
diff -u -r1.36 field.attach.inc
--- modules/field/field.attach.inc 13 Aug 2009 00:17:47 -0000 1.36
+++ modules/field/field.attach.inc 17 Aug 2009 21:55:40 -0000
@@ -457,6 +457,11 @@
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 @@
$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);
@@ -1051,6 +1061,24 @@
}
/**
+ * Retrieve the user-defined weight for pseudo-field components.
+ *
+ * @param $bundle
+ * The bundle name.
+ * @param $pseudo_field
+ * The name of the 'field'.
+ * @return
+ * The weight for the '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.
*
* TODO D7: We do not yet know if this really belongs in Field API.
@@ -1084,7 +1112,7 @@
// 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);
Index: modules/field/field.default.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.default.inc,v
retrieving revision 1.14
diff -u -r1.14 field.default.inc
--- modules/field/field.default.inc 13 Aug 2009 01:50:00 -0000 1.14
+++ modules/field/field.default.inc 17 Aug 2009 21:55:41 -0000
@@ -43,11 +43,8 @@
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);
}
}
/**
Index: modules/field/field.crud.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.crud.inc,v
retrieving revision 1.25
diff -u -r1.25 field.crud.inc
--- modules/field/field.crud.inc 13 Aug 2009 01:50:00 -0000 1.25
+++ modules/field/field.crud.inc 17 Aug 2009 21:55:41 -0000
@@ -637,8 +637,8 @@
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']) {
Index: modules/field/field.info.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.info.inc,v
retrieving revision 1.12
diff -u -r1.12 field.info.inc
--- modules/field/field.info.inc 11 Aug 2009 14:59:40 -0000 1.12
+++ modules/field/field.info.inc 17 Aug 2009 21:55:41 -0000
@@ -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 @@
// 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'];
Index: modules/field/modules/number/number.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/modules/number/number.module,v
retrieving revision 1.11
diff -u -r1.11 number.module
--- modules/field/modules/number/number.module 1 Aug 2009 06:03:12 -0000 1.11
+++ modules/field/modules/number/number.module 17 Aug 2009 21:55:41 -0000
@@ -88,6 +88,83 @@
}
/**
+ * 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 = array();
+
+ $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().
*
* Possible error codes:
Index: modules/field/modules/text/text.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/modules/text/text.module,v
retrieving revision 1.17
diff -u -r1.17 text.module
--- modules/field/modules/text/text.module 4 Aug 2009 06:38:56 -0000 1.17
+++ modules/field/modules/text/text.module 17 Aug 2009 21:55:42 -0000
@@ -129,6 +129,53 @@
}
/**
+ * Implement hook_field_settings_form().
+ */
+function text_field_settings_form($field, $instance) {
+ $settings = $field['settings'];
+ $form = array();
+
+ $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 = array();
+
+ $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().
*
* Possible error codes:
@@ -467,6 +514,36 @@
}
/**
+ * Implement hook_field_widget_settings_form().
+ */
+function text_field_widget_settings_form($field, $instance) {
+ $widget = $instance['widget'];
+ $settings = $widget['settings'];
+ $form = array();
+
+ 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().
*
* Any FAPI callbacks needed for individual widgets can be declared here,
Index: modules/field/modules/list/list.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/modules/list/list.module,v
retrieving revision 1.7
diff -u -r1.7 list.module
--- modules/field/modules/list/list.module 1 Aug 2009 06:03:12 -0000 1.7
+++ modules/field/modules/list/list.module 17 Aug 2009 21:55:41 -0000
@@ -28,28 +28,28 @@
'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',
),
@@ -98,6 +98,132 @@
}
/**
+ * Implement hook_field_settings_form().
+ */
+function list_field_settings_form($field, $instance) {
+ $settings = $field['settings'];
+ $form = array();
+
+ $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.") . '
' . 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 the 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
+ // automatically will 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().
*
* Possible error codes:
@@ -161,29 +287,3 @@
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']];
-}
Index: modules/taxonomy/taxonomy.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.module,v
retrieving revision 1.495
diff -u -r1.495 taxonomy.module
--- modules/taxonomy/taxonomy.module 11 Aug 2009 15:50:56 -0000 1.495
+++ modules/taxonomy/taxonomy.module 17 Aug 2009 21:55:44 -0000
@@ -65,6 +65,25 @@
}
/**
+ * Implement hook_field_extra_fields().
+ */
+function taxonomy_field_extra_fields($bundle) {
+ $extra = array();
+
+ if ($type = node_type_get_type($bundle)) {
+ if (taxonomy_get_vocabularies($bundle)) {
+ $extra['taxonomy'] = array(
+ 'label' => t('Taxonomy'),
+ 'description' => t('Taxonomy module element.'),
+ 'weight' => -3
+ );
+ }
+ }
+
+ return $extra;
+}
+
+/**
* Implement hook_theme().
*/
function taxonomy_theme() {
@@ -2052,3 +2071,37 @@
function taxonomy_term_title($term) {
return check_plain($term->name);
}
+
+/**
+ * Implement hook_field_settings_form().
+ */
+function taxonomy_field_settings_form($field, $instance) {
+ $form = array();
+
+ // Get the right values for allowed_values_function, which is a core setting.
+ $vocabularies = taxonomy_get_vocabularies();
+ $options = array();
+ foreach ($vocabularies as $vocabulary) {
+ $options[$vocabulary->vid] = $vocabulary->name;
+ }
+ $form['allowed_values'] = array(
+ '#tree' => TRUE,
+ );
+
+ foreach ($field['settings']['allowed_values'] as $delta => $tree) {
+ $form['allowed_values'][$delta]['vid'] = array(
+ '#type' => 'select',
+ '#title' => t('Vocabulary'),
+ '#default_value' => $tree['vid'],
+ '#options' => $options,
+ '#required' => TRUE,
+ '#description' => t('The vocabulary which supplies the options for this field.'),
+ );
+ $form['allowed_values'][$delta]['parent'] = array(
+ '#type' => 'value',
+ '#value' => $tree['parent'],
+ );
+ }
+
+ return $form;
+}
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.1099
diff -u -r1.1099 node.module
--- modules/node/node.module 14 Aug 2009 13:53:01 -0000 1.1099
+++ modules/node/node.module 17 Aug 2009 21:55:43 -0000
@@ -44,6 +44,12 @@
return ' '; // Return a non-null value so that the 'more help' link is shown.
case 'admin/structure/types/add':
return '
' . t('Each piece of content is of a specific content type. Each content type can have different fields, behaviors, and permissions assigned to it.') . '
' . t('This form lets you configure how fields and labels are displayed when %type content is viewed in teaser and full-page mode.', array('%type' => node_type_get_name($arg[3]))) . '
' . t('This form lets you configure how fields should be displayed when rendered %type content in the following contexts.', array('%type' => node_type_get_name($arg[3]))) . '
';
case 'node/%/revisions':
return '
' . t('The revisions let you track differences between multiple versions of a post.') . '
Anonymous user: this role is used for users that don\'t have a user account or that are not authenticated.
Authenticated user: this role is automatically granted to all logged in users.
', array('@permissions' => url('admin/settings/permissions')));
+ case 'admin/settings/user/fields':
+ return '
' . t('This form lets administrators add, edit, and arrange fields for storing user data.') . '
';
+ case 'admin/settings/user/display':
+ return '
' . t('This form lets administrators configure how fields should be displayed when rendering a user profile page.') . '
';
case 'admin/people/search':
return '
' . t('Enter a simple pattern ("*" may be used as a wildcard match) to search for a username or e-mail address. For example, one may search for "br" and Drupal might return "brian", "brad", and "brenda@example.com".') . '
';
}
Index: modules/poll/poll.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/poll/poll.module,v
retrieving revision 1.303
diff -u -r1.303 poll.module
--- modules/poll/poll.module 17 Aug 2009 07:12:16 -0000 1.303
+++ modules/poll/poll.module 17 Aug 2009 21:55:44 -0000
@@ -199,6 +199,28 @@
}
/**
+ * Implement hook_field_extra_fields().
+ */
+function poll_field_extra_fields($bundle) {
+ $extra = array();
+
+ if ($bundle == 'poll') {
+ $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;
+}
+
+/**
* Implement hook_form().
*/
function poll_form($node, $form_state) {
Index: profiles/default/default.info
===================================================================
RCS file: /cvs/drupal/drupal/profiles/default/default.info,v
retrieving revision 1.1
diff -u -r1.1 default.info
--- profiles/default/default.info 15 Jul 2009 02:08:41 -0000 1.1
+++ profiles/default/default.info 17 Aug 2009 21:55:46 -0000
@@ -14,3 +14,4 @@
dependencies[] = dblog
dependencies[] = search
dependencies[] = toolbar
+dependencies[] = field_ui
Index: modules/field_ui/field_ui.api.php
===================================================================
RCS file: modules/field_ui/field_ui.api.php
diff -N modules/field_ui/field_ui.api.php
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ modules/field_ui/field_ui.api.php 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,136 @@
+ 'textfield',
+ '#title' => t('Maximum length'),
+ '#default_value' => $settings['max_length'],
+ '#required' => FALSE,
+ '#element_validate' => array('_element_validate_integer_positive'),
+ '#description' => t('The maximum length of the field in characters. Leave blank for an unlimited size.'),
+ );
+ return $form;
+}
+
+/**
+ * Instance settings form.
+ *
+ * @param $field
+ * The field structure being configured.
+ * @param $instance
+ * The instance structure being configured.
+ * @return
+ * The form definition for the field instance settings.
+ */
+function hook_field_instance_settings_form($field, $instance) {
+ $settings = $instance['settings'];
+ $form = array();
+
+ $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' => 'select',
+ '#title' => t('Display summary'),
+ '#options' => array(
+ t('No'),
+ t('Yes'),
+ ),
+ '#description' => t('Display the summary to allow the user to input a summary value. Hide the summary to automatically fill it with a trimmed portion from the main post. '),
+ '#default_value' => !empty($settings['display_summary']) ? $settings['display_summary'] : 0,
+ );
+ }
+
+ return $form;
+}
+
+/**
+ * Widget settings form.
+ *
+ * @param $field
+ * The field structure being configured.
+ * @param $instance
+ * The instance structure being configured.
+ * @return
+ * The form definition for the widget settings.
+ */
+function hook_field_widget_settings_form($field, $instance) {
+ $widget = $instance['widget'];
+ $settings = $widget['settings'];
+ $form = array();
+
+ if ($widget['type'] == 'text_textfield') {
+ $form['size'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Size of textfield'),
+ '#default_value' => $settings['size'],
+ '#element_validate' => array('_element_validate_integer_positive'),
+ '#required' => TRUE,
+ );
+ }
+ else {
+ $form['rows'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Rows'),
+ '#default_value' => $settings['rows'],
+ '#element_validate' => array('_element_validate_integer_positive'),
+ '#required' => TRUE,
+ );
+ }
+
+ return $form;
+}
+
+/**
+ * Formatter settings form.
+ *
+ * @todo Not implemented yet. The signature below is only prospective, but
+ * providing $instance is not enough, since one $instance holds several display
+ * settings.
+ *
+ * @param $formatter
+ * The type of the formatter being configured.
+ * @param $settings
+ * The current values of the formatter settings.
+ * @param $field
+ * The field structure being configured.
+ * @param $instance
+ * The instance structure being configured.
+ * @return
+ * The form definition for the formatter settings.
+ */
+function hook_field_formatter_settings_form($formatter, $settings, $field, $instance) {
+}
+
+/**
+ * @} End of "ingroup field_ui_field_type"
+ */
Index: modules/field_ui/field_ui.css
===================================================================
RCS file: modules/field_ui/field_ui.css
diff -N modules/field_ui/field_ui.css
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ modules/field_ui/field_ui.css 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,18 @@
+/* $Id$ */
+
+/* 'Manage fields' overview */
+#field-overview .label-add-new-field,
+#field-overview .label-add-existing-field {
+ float: left; /* LTR */
+}
+#field-overview tr.add-new .tabledrag-changed {
+ display: none;
+}
+#field-overview tr.add-new .description {
+ margin-bottom: 0;
+}
+#field-overview .new {
+ font-weight: bold;
+ padding-bottom: .5em;
+}
+
Index: modules/field_ui/field_ui-field-overview-form.tpl.php
===================================================================
RCS file: modules/field_ui/field_ui-field-overview-form.tpl.php
diff -N modules/field_ui/field_ui-field-overview-form.tpl.php
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ modules/field_ui/field_ui-field-overview-form.tpl.php 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ row_type):
+ case 'field': ?>
+
+ label; ?>
+
+
weight . $row->hidden_name; ?>
+
field_name; ?>
+
type; ?>
+
widget_type; ?>
+
edit; ?>
+
delete; ?>
+
+
+ label; ?>
+
+
weight . $row->hidden_name; ?>
+
name; ?>
+
description; ?>
+
edit; ?>
+
delete; ?>
+
+
+
+
+ label; ?>
+
+
+
weight . $row->hidden_name; ?>
+
field_name; ?>
+
type; ?>
+
widget_type; ?>
+
+
+
+
+ label; ?>
+
+
+
weight . $row->hidden_name; ?>
+
field_name; ?>
+
widget_type; ?>
+
+
+
+
+
+
+
Index: modules/field_ui/field_ui.module
===================================================================
RCS file: modules/field_ui/field_ui.module
diff -N modules/field_ui/field_ui.module
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ modules/field_ui/field_ui.module 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,324 @@
+' . t('The Field UI module provides an administrative interface for adding custom fields to content types, users, comments, and other types of data. In the case of content types, a few fields are provided by default, such as the "Summary and Body" field. The Field UI module lets administrators edit or delete the default fields attached to content, as well as create new fields for storing any additional information. Field configuration is accessible through tabs on the content types administration page. (See the node module help page for more information about content types.)', array('@content-types' => url('admin/content/types'), '@node-help' => url('admin/help/node'))) . '';
+ $output .= '
' . t('When adding a custom field to a content type, you determine its type (whether it will contain text, numbers, lists, etc.) and how it will be displayed (either as a text field or text area, a select box, checkboxes, radio buttons, or an auto-complete text field). A field may have multiple values (i.e., a "person" may have multiple e-mail addresses) or a single value (i.e., an "employee" has a single employee identification number).') . '
';
+ $output .= '
' . t('Custom field types may be provided by additional modules. Drupal core includes the following field types:') . '
';
+ $output .= '
';
+ $output .= '
' . t('Number: Adds numeric field types, in integer, decimal or floating point form. You may define a set of allowed inputs, or specify an allowable range of values. A variety of common formats for displaying numeric data are available.') . '
';
+ $output .= '
' . t("Text: Adds text field types. A text field may contain plain text only, or optionally, may use Drupal's input format filters 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, CCK can validate the input to a set of allowed values.") . '
';
+ $output .= '
' . t('List: Provides storage mechanisms to store a list of items. Usually these items are input through a select list, checkboxes, or radio buttons.') . '
';
+ $output .= '
';
+ return $output;
+ case 'admin/reports/fields':
+ return '
' . t('This list shows all fields currently in use for easy reference.') . '
' . t('These settings apply to the %field field everywhere it is used. These settings impact the way that data is stored in the database and cannot be changed once data has been created.', array('%field' => $instance['label'])) . '
';
+
+ // Create a form structure for the field values.
+ $form['field'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('%field field settings', array('%field' => $instance['label'])),
+ '#description' => $description,
+ '#tree' => TRUE,
+ );
+
+ // See if data already exists for this field.
+ // If so, prevent changes to the field settings.
+ $has_data = field_ui_field_has_data($field);
+ if ($has_data) {
+ $form['field']['#description'] = '
' . t('There is data for this field in the database. The field settings can no longer be changed.' . '
') . $form['field']['#description'];
+ }
+
+ // Build the non-configurable field values.
+ $form['field']['field_name'] = array('#type' => 'value', '#value' => $field['field_name']);
+ $form['field']['type'] = array('#type' => 'value', '#value' => $field['type']);
+ $form['field']['module'] = array('#type' => 'value', '#value' => $field['module']);
+ $form['field']['active'] = array('#type' => 'value', '#value' => $field['active']);
+
+ // Add settings provided by the field module.
+ $form['field']['settings'] = array();
+ $additions = module_invoke($field_type['module'], 'field_settings_form', $field, $instance);
+ if (is_array($additions)) {
+ $form['field']['settings'] = $additions;
+ // TODO: Filter this so only the settings that cannot be changed are shown
+ // in this form. For now, treating all settings as changeable, which means
+ // they show up here and in edit form.
+ }
+ if (empty($form['field']['settings'])) {
+ $form['field']['settings'] = array(
+ '#markup' => t('%field has no field settings.', array('%field' => $instance['label'])),
+ );
+ }
+ else {
+ foreach ($form['field']['settings'] as $key => $setting) {
+ if (substr($key, 0, 1) != '#') {
+ $form['field']['settings'][$key]['#disabled'] = $has_data;
+ }
+ }
+ }
+
+ $form['#bundle'] = $bundle;
+
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save field settings'),
+ );
+
+ return $form;
+}
+
+/**
+ * Save a field's settings after editing.
+ */
+function field_ui_field_settings_form_submit($form, &$form_state) {
+ $form_values = $form_state['values'];
+ $field_values = $form_values['field'];
+
+ // Merge incoming form values into the existing field.
+ $field = field_info_field($field_values['field_name']);
+
+ // Don't allow changes to fields with data.
+ if (field_ui_field_has_data($field)) {
+ return;
+ }
+
+ // Remove the 'bundles' element added by field_info_field.
+ // TODO: This is ugly, there should be a better way.
+ unset($field['bundles']);
+ $bundle = $form['#bundle'];
+ $instance = field_info_instance($field['field_name'], $bundle);
+
+ // Update the field.
+ $field = array_merge($field, $field_values);
+ field_ui_update_field($field);
+
+ drupal_set_message(t('Updated field %label field settings.', array('%label' => $instance['label'])));
+ $form_state['redirect'] = field_ui_next_destination($bundle);
+}
+
+/**
+ * Menu callback; select a widget for the field.
+ */
+function field_ui_widget_type_form(&$form_state, $obj_type, $bundle, $instance) {
+ $bundle = field_attach_extract_bundle($obj_type, $bundle);
+ $field = field_read_field($instance['field_name']);
+
+ $field_type = field_info_field_types($field['type']);
+ $widget_type = field_info_widget_types($instance['widget']['type']);
+ $bundles = field_info_bundles();
+ $bundle_label = $bundles[$bundle]['label'];
+
+ $form = array();
+ $form['basic'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Change widget'),
+ );
+ $form['basic']['widget_type'] = array(
+ '#type' => 'select',
+ '#title' => t('Widget type'),
+ '#required' => TRUE,
+ '#options' => field_ui_widget_type_options($field['type']),
+ '#default_value' => $instance['widget']['type'],
+ '#description' => t('The type of form element you would like to present to the user when creating this field in the %type type.', array('%type' => $bundle_label)),
+ );
+
+ $form['#instance'] = $instance;
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Continue'),
+ );
+
+ $form['#validate'] = array();
+ $form['#submit'] = array('field_ui_widget_type_form_submit');
+
+ return $form;
+}
+
+/**
+ * Submit the change in widget type.
+ */
+function field_ui_widget_type_form_submit($form, &$form_state) {
+ $form_values = $form_state['values'];
+ $instance = $form['#instance'];
+ $bundle = $instance['bundle'];
+
+ // Set the right module information.
+ $widget_type = field_info_widget_types($form_values['widget_type']);
+ $widget_module = $widget_type['module'];
+
+ $instance['widget']['type'] = $form_values['widget_type'];
+ $instance['widget']['module'] = $widget_module;
+ try {
+ field_update_instance($instance);
+ drupal_set_message(t('Changed the widget for field %label.', array('%label' => $instance['label'])));
+ }
+ catch (FieldException $e) {
+ drupal_set_message(t('There was a problem changing the widget for field %label.', array('%label' => $instance['label'])));
+ }
+
+
+ $form_state['redirect'] = field_ui_next_destination($bundle);
+}
+
+/**
+ * Menu callback; present a form for removing a field from a content type.
+ */
+function field_ui_field_delete_form(&$form_state, $obj_type, $bundle, $instance) {
+ $bundle = field_attach_extract_bundle($obj_type, $bundle);
+ $field = field_info_field($instance['field_name']);
+ $admin_path = _field_ui_bundle_admin_path($bundle);
+
+ $form = array();
+ $form['bundle'] = array(
+ '#type' => 'value',
+ '#value' => $bundle,
+ );
+ $form['field_name'] = array(
+ '#type' => 'value',
+ '#value' => $field['field_name'],
+ );
+
+ $output = confirm_form($form,
+ t('Are you sure you want to delete the field %field?', array('%field' => $instance['label'])),
+ $admin_path . '/fields',
+ t('If you have any content left in this field, it will be lost. This action cannot be undone.'),
+ t('Delete'), t('Cancel'),
+ 'confirm'
+ );
+
+ if ($field['locked']) {
+ unset($output['actions']['submit']);
+ $output['description']['#markup'] = t('This field is locked and cannot be deleted.');
+ }
+
+ return $output;
+}
+
+/**
+ * Remove a field from a content type.
+ */
+function field_ui_field_delete_form_submit($form, &$form_state) {
+ $form_values = $form_state['values'];
+ $field = field_info_field($form_values['field_name']);
+ $instance = field_info_instance($form_values['field_name'], $form_values['bundle']);
+ $bundles = field_info_bundles();
+ $bundle = $form_values['bundle'];
+ $bundle_label = $bundles[$bundle]['label'];
+
+ if (!empty($bundle) && $field && !$field['locked'] && $form_values['confirm']) {
+ field_delete_instance($field['field_name'], $bundle);
+ // Delete the field if that was the last instance.
+ if (count($field['bundles'] == 1)) {
+ field_delete_field($field['field_name']);
+ }
+ drupal_set_message(t('The field %field has been deleted from the %type content type.', array('%field' => $instance['label'], '%type' => $bundle_label)));
+ }
+ else {
+ drupal_set_message(t('There was a problem removing the %field from the %type content type.', array('%field' => $instance['label'], '%type' => $bundle_label)));
+ }
+
+ $admin_path = _field_ui_bundle_admin_path($bundle);
+ $destinations = array(
+ $admin_path . '/fields/refresh',
+ $admin_path . '/fields',
+ );
+ $form_state['redirect'] = field_ui_get_destinations($destinations);
+}
+
+/**
+ * Menu callback; presents the field instance edit page.
+ */
+function field_ui_field_edit_form(&$form_state, $obj_type, $bundle, $instance) {
+ $bundle = field_attach_extract_bundle($obj_type, $bundle);
+
+ $output = '';
+ $field = field_info_field($instance['field_name']);
+ $form = array();
+ $form['#field'] = $field;
+
+ if (!empty($field['locked'])) {
+ $output = array();
+ $output['locked'] = array(
+ '#markup' => t('The field %field is locked and cannot be edited.', array('%field' => $instance['label'])),
+ );
+ return $output;
+ }
+
+ $field_type = field_info_field_types($field['type']);
+ $widget_type = field_info_widget_types($instance['widget']['type']);
+ $bundles = field_info_bundles();
+
+ $title = isset($instance['label']) ? $instance['label'] : $instance['field_name'];
+ drupal_set_title(check_plain($title));
+
+ // Create a form structure for the instance values.
+ $form['instance'] = array(
+ '#tree' => TRUE,
+ '#type' => 'fieldset',
+ '#title' => t('%type settings', array('%type' => $bundles[$bundle]['label'])),
+ '#description' => t('These settings apply only to the %field field when used in the %type type.', array('%field' => $instance['label'], '%type' => $bundles[$bundle]['label'])),
+ '#pre_render' => array('field_ui_field_edit_instance_pre_render'),
+ );
+
+ // Build the non-configurable instance values.
+ $form['instance']['field_name'] = array(
+ '#type' => 'value',
+ '#value' => $instance['field_name'],
+ );
+ $form['instance']['bundle'] = array(
+ '#type' => 'value',
+ '#value' => $bundle,
+ );
+ $form['instance']['widget']['weight'] = array(
+ '#type' => 'value',
+ '#value' => !empty($instance['widget']['weight']) ? $instance['widget']['weight'] : 0,
+ );
+
+ // Build the configurable instance values.
+ $form['instance']['label'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Label'),
+ '#default_value' => !empty($instance['label']) ? $instance['label'] : $field['field_name'],
+ '#required' => TRUE,
+ '#description' => t('The human-readable label for this field.'),
+ '#weight' => -20,
+ );
+ $form['instance']['required'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Required'),
+ '#default_value' => !empty($instance['required']),
+ '#description' => t('Check if a value must be provided.'),
+ '#weight' => -10,
+ );
+
+ $form['instance']['description'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Help text'),
+ '#default_value' => !empty($instance['description']) ? $instance['description'] : '',
+ '#rows' => 5,
+ '#description' => t('Instructions to present to the user below this field on the editing form. Allowed HTML tags: @tags', array('@tags' => _field_filter_xss_display_allowed_tags())),
+ '#required' => FALSE,
+ '#weight' => 0,
+ );
+
+ // Build the widget component of the instance.
+ $form['instance']['widget']['type'] = array(
+ '#type' => 'value',
+ '#value' => $instance['widget']['type'],
+ );
+ $form['instance']['widget']['module'] = array(
+ '#type' => 'value',
+ '#value' => $widget_type['module'],
+ );
+ $form['instance']['widget']['active'] = array(
+ '#type' => 'value',
+ '#value' => !empty($field['instance']['widget']['active']) ? 1 : 0,
+ );
+
+ // Add additional field instance settings from the field module.
+ $additions = module_invoke($field['module'], 'field_instance_settings_form', $field, $instance);
+ if (is_array($additions)) {
+ $form['instance']['settings'] = $additions;
+ }
+
+ // Add additional widget settings from the widget module.
+ $additions = module_invoke($widget_type['module'], 'field_widget_settings_form', $field, $instance);
+ if (is_array($additions)) {
+ $form['instance']['widget']['settings'] = $additions;
+ $form['instance']['widget']['active']['#value'] = 1;
+ }
+
+ // Add handling for default value if not provided by any other module.
+ if (field_behaviors_widget('default value', $instance) == FIELD_BEHAVIOR_DEFAULT && empty($instance['default_value_function'])) {
+ // Store the original default value for use in programmed forms. The
+ // '#default_value' property is used instead of '#value' so programmed
+ // values can override whatever we set here.
+ $form['instance']['default_value'] = array(
+ '#type' => 'value',
+ '#default_value' => $instance['default_value'],
+ );
+
+ // We can't tell at the time we build the form if this is a programmed form
+ // or not, so we always end up adding the default value widget even if we
+ // won't use it.
+ field_ui_default_value_widget($field, $instance, $form, $form_state);
+ }
+
+ $info = field_info_field_types($field['type']);
+ $description = '
' . t('These settings apply to the %field field everywhere it is used.', array('%field' => $instance['label'])) . '
';
+
+ // Create a form structure for the field values.
+ $form['field'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('%field field settings', array('%field' => $instance['label'])),
+ '#description' => $description,
+ '#tree' => TRUE,
+ );
+
+ // Build the configurable field values.
+ $description = t('Maximum number of values users can enter for this field.');
+ if (field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_DEFAULT) {
+ $description .= ' ' . t("'Unlimited' will provide an 'Add more' button so the users can add as many values as they like.");
+ }
+ $form['field']['cardinality'] = array(
+ '#type' => 'select',
+ '#title' => t('Number of values'),
+ '#options' => array(FIELD_CARDINALITY_UNLIMITED => t('Unlimited')) + drupal_map_assoc(range(1, 10)),
+ '#default_value' => $field['cardinality'],
+ '#description' => $description,
+ );
+
+ // Add additional field settings from the field module.
+ $additions = module_invoke($field['module'], 'field_settings_form', $field, $instance);
+ if (is_array($additions)) {
+ $form['field']['settings'] = $additions;
+ // TODO: Filter in only settings that can be changed.
+ }
+
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save settings'),
+ );
+ return $form;
+}
+
+/**
+ * Prerender function for the instance settings.
+ *
+ * Combine the instance, widget, and other settings into a single fieldset so
+ * that elements within each group can be shown at different weights as if they
+ * all had the same parent.
+ */
+function field_ui_field_edit_instance_pre_render($element) {
+ // Merge the widget settings into the main form.
+ if (isset($element['widget']['settings'])) {
+ foreach (element_children($element['widget']['settings']) as $key) {
+ $element['widget_' . $key] = $element['widget']['settings'][$key];
+ }
+ unset($element['widget']['settings']);
+ }
+
+ // Merge the instance settings into the main form.
+ if (isset($element['settings'])) {
+ foreach (element_children($element['settings']) as $key) {
+ $element['instance_' . $key] = $element['settings'][$key];
+ }
+ unset($element['settings']);
+ }
+
+ return $element;
+}
+
+/**
+ * Build default value fieldset.
+ */
+function field_ui_default_value_widget($field, $instance, &$form, &$form_state) {
+ $form['instance']['default_value_widget'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Default value'),
+ '#collapsible' => FALSE,
+ '#tree' => TRUE,
+ '#description' => t('The default value for this field, used when creating new content.')
+ );
+
+ // Make sure the default value is not a required field.
+ $instance['required'] = FALSE;
+ $instance['description'] = '';
+ $items = $instance['default_value'];
+ // Set up form info that the default value widget will need.
+ $form['#fields'] = array(
+ $field['field_name'] => array(
+ 'field' => $field,
+ 'instance' => $instance,
+ ),
+ );
+ drupal_function_exists('field_default_form');
+ // TODO: Allow multiple values (requires more work on 'add more' JS handler).
+ $widget_form = field_default_form(NULL, NULL, $field, $instance, $items, $form, $form_state, 0);
+ $form['instance']['default_value_widget'] += $widget_form;
+ $form['#fields'][$field['field_name']]['form_path'] = array('instance', 'default_value_widget', $field['field_name']);
+}
+
+/**
+ * Validate a field's settings.
+ */
+function field_ui_field_edit_form_validate($form, &$form_state) {
+ $form_values = $form_state['values'];
+ $instance = $form_values['instance'];
+ $field = field_info_field($instance['field_name']);
+ $field_type = field_info_field_types($field['type']);
+ $widget_type = field_info_widget_types($instance['widget']['type']);
+
+ // Do no validation here. Assume field and widget modules are
+ // handling their own validation of form settings.
+
+ // If field.module is handling the default value,
+ // validate the result using the field validation.
+ if (field_behaviors_widget('default value', $instance) == FIELD_BEHAVIOR_DEFAULT) {
+
+ // If this is a programmed form, get rid of the default value widget,
+ // we have the default values already.
+ if (!empty($form_state['programmed'])) {
+ form_set_value(array('#parents' => array('instance', 'widget', 'default_value_widget')), NULL, $form_state);
+ return;
+ }
+
+ if (!empty($form_values['instance']['widget']['default_value_widget'])) {
+ // Fields that handle their own multiple values may use an expected
+ // value as the top-level key, so just pop off the top element.
+ $key = array_shift(array_keys($form_values['instance']['widget']['default_value_widget']));
+ $default_value = $form_values['instance']['widget']['default_value_widget'][$key];
+ $is_code = FALSE;
+ form_set_value(array('#parents' => array('instance', 'widget', 'default_value')), $default_value, $form_state);
+ }
+
+ if (isset($default_value)) {
+ $node = array();
+ $node[$field['field_name']] = $default_value;
+ $field['required'] = FALSE;
+ $field_function = $field_type['module'] . '_field';
+ $errors_before = form_get_errors();
+
+ // Widget now does its own validation, should be no need
+ // to add anything for widget validation here.
+ if (drupal_function_exists($field_function)) {
+ $field_function('validate', $node, $field, $default_value, $form, NULL);
+ }
+ // The field validation routine won't set an error on the right field,
+ // so set it here.
+ $errors_after = form_get_errors();
+ if (count($errors_after) > count($errors_before)) {
+ form_set_error('default_value', t('The default value is invalid.'));
+ }
+ }
+ }
+}
+
+/**
+ * Save instance settings after editing.
+ */
+function field_ui_field_edit_form_submit($form, &$form_state) {
+ $form_values = $form_state['values'];
+ $instance = $form_values['instance'];
+
+ // Update any field settings that have changed.
+ $field = field_info_field($instance['field_name']);
+ // Remove the 'bundles' element added by field_info_field.
+ // TODO: This is ugly, there should be a better way.
+ unset($field['bundles']);
+ $field = array_merge($field, $form_state['values']['field']);
+ field_ui_update_field($field);
+
+ // Move the default value from the sample widget to the default value field.
+ if (isset($instance['default_value_widget'])) {
+ $instance['default_value'] = $instance['default_value_widget'][$instance['field_name']];
+ unset($instance['default_value_widget']);
+ }
+
+ // Update the instance settings.
+ module_load_include('inc', 'field', 'includes/field.crud');
+ field_update_instance($instance);
+
+ drupal_set_message(t('Saved %label configuration.', array('%label' => $instance['label'])));
+
+ $form_state['redirect'] = field_ui_next_destination($instance['bundle']);
+}
+
+/**
+ * Helper functions to handle multipage redirects.
+ */
+function field_ui_get_destinations($destinations) {
+ $query = array();
+ $path = array_shift($destinations);
+ if ($destinations) {
+ $query['destinations'] = $destinations;
+ }
+ return array($path, $query);
+}
+
+/**
+ * Return the next redirect path in a multipage sequence.
+ */
+function field_ui_next_destination($bundle) {
+ $destinations = !empty($_REQUEST['destinations']) ? $_REQUEST['destinations'] : array();
+ if (!empty($destinations)) {
+ unset($_REQUEST['destinations']);
+ return field_ui_get_destinations($destinations);
+ }
+ else {
+ $admin_path = _field_ui_bundle_admin_path($bundle);
+ return $admin_path . '/fields';
+ }
+}
+
+/**
+ * Menu callback; rebuild the menu after adding new fields.
+ *
+ * Dummy function to force a page refresh so menu_rebuild() will work right
+ * when creating a new field that creates a new menu item.
+ */
+function field_ui_field_menu_refresh($obj_type, $bundle) {
+ $bundle = field_attach_extract_bundle($obj_type, $bundle);
+
+ menu_rebuild();
+ $destinations = field_ui_next_destination($bundle);
+ if (is_array($destinations)) {
+ $path = array_shift($destinations);
+ drupal_goto($path, $destinations);
+ }
+ else {
+ drupal_goto($destinations);
+ }
+}
+
+/**
+ * Helper function to order fields when theming overview forms.
+ */
+function _field_ui_overview_order(&$form, $field_rows) {
+ // Put weight and parenting values into a $dummy render structure
+ // and let drupal_render figure out the corresponding row order.
+ $dummy = array();
+ // Field rows: account for weight.
+ foreach ($field_rows as $name) {
+ $dummy[$name] = array(
+ '#markup' => $name . ' ',
+ '#type' => 'markup',
+ '#weight' => $form[$name]['weight']['#value'],
+ );
+ }
+ return $dummy ? explode(' ', trim(drupal_render($dummy))) : array();
+}
+
+/**
+ * Helper form element validator: integer.
+ */
+function _element_validate_integer($element, &$form_state) {
+ $value = $element['#value'];
+ if ($value !== '' && (!is_numeric($value) || intval($value) != $value)) {
+ form_error($element, t('%name must be an integer.', array('%name' => $element['#title'])));
+ }
+}
+
+/**
+ * Helper form element validator: integer > 0.
+ */
+function _element_validate_integer_positive($element, &$form_state) {
+ $value = $element['#value'];
+ if ($value !== '' && (!is_numeric($value) || intval($value) != $value || $value <= 0)) {
+ form_error($element, t('%name must be a positive integer.', array('%name' => $element['#title'])));
+ }
+}
+
+/**
+ * Helper form element validator: number.
+ */
+function _element_validate_number($element, &$form_state) {
+ $value = $element['#value'];
+ if ($value != '' && !is_numeric($value)) {
+ form_error($element, t('%name must be a number.', array('%name' => $element['#title'])));
+ }
+}