? .cvsignore ? sites/default/.cvsignore Index: modules/field/field.info =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.info,v retrieving revision 1.4 diff -u -p -r1.4 field.info --- modules/field/field.info 8 Jun 2009 09:23:51 -0000 1.4 +++ modules/field/field.info 15 Jul 2009 22:09:33 -0000 @@ -1,7 +1,7 @@ ; $Id: field.info,v 1.4 2009/06/08 09:23:51 dries Exp $ name = Field description = Field API to add fields to objects like nodes and users. -package = Core - fields +package = Core version = VERSION core = 7.x files[] = field.module Index: modules/field/modules/field_sql_storage/field_sql_storage.info =================================================================== RCS file: /cvs/drupal/drupal/modules/field/modules/field_sql_storage/field_sql_storage.info,v retrieving revision 1.3 diff -u -p -r1.3 field_sql_storage.info --- modules/field/modules/field_sql_storage/field_sql_storage.info 8 Jun 2009 09:23:51 -0000 1.3 +++ modules/field/modules/field_sql_storage/field_sql_storage.info 15 Jul 2009 22:09:33 -0000 @@ -1,7 +1,7 @@ ; $Id: field_sql_storage.info,v 1.3 2009/06/08 09:23:51 dries Exp $ name = Field SQL storage description = Stores field data in an SQL database. -package = Core - fields +package = Core version = VERSION core = 7.x files[] = field_sql_storage.module Index: modules/field/modules/list/list.info =================================================================== RCS file: /cvs/drupal/drupal/modules/field/modules/list/list.info,v retrieving revision 1.4 diff -u -p -r1.4 list.info --- modules/field/modules/list/list.info 12 Jun 2009 08:39:36 -0000 1.4 +++ modules/field/modules/list/list.info 15 Jul 2009 22:09:33 -0000 @@ -1,7 +1,7 @@ ; $Id: list.info,v 1.4 2009/06/12 08:39:36 dries Exp $ name = List description = Defines list field types. Use with Options to create selection lists. -package = Core - fields +package = Core version = VERSION core = 7.x files[]=list.module Index: modules/field/modules/list/list.module =================================================================== RCS file: /cvs/drupal/drupal/modules/field/modules/list/list.module,v retrieving revision 1.6 diff -u -p -r1.6 list.module --- modules/field/modules/list/list.module 27 May 2009 18:33:56 -0000 1.6 +++ modules/field/modules/list/list.module 15 Jul 2009 22:09:33 -0000 @@ -98,6 +98,181 @@ function list_field_schema($field) { } /** + * Implement hook_field_settings_form(). + */ +function list_field_settings_form($field, $instance) { + $field_type = $field['type']; + + // Alter the description for allowed values slightly depending + // on the type of widget. + $description = '

' . t('Create a list of options as a list or a function name. These values will be the same for %field in all field types.', array('%field' => $instance['label'])) . '

'; + if ($instance['widget']['type'] == 'options_onoff') { + $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') { + $description .= '

' . t("The 'checkboxes/radio buttons' widget will display checkboxes if the cardinality option is selected for this field, otherwise radios will be displayed.") . '

'; + } + + $form = array( + '#element_validate' => array('list_field_settings_form_validate'), + '#prefix' => '
' . $description . '
', + '#field' => $field, + ); + + // Retrieve allowed_values and allowed_values_php, which are non-core + // settings handled by Field UI. + $allowed_values = field_ui_get_setting('allowed_values', 'field', $field); + $allowed_values_php = field_ui_get_setting('allowed_values_php', 'field', $field); + + $settings = $field['settings']; + $allowed_values_function = !empty($settings['allowed_values_function']) && $settings['allowed_values_function'] != 'list_allowed_values_php' ? $settings['allowed_values_function'] : ''; + $form['allowed_values'] = array( + '#type' => 'textarea', + '#title' => t('Allowed values list'), + '#default_value' => $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.
Allowed HTML tags in labels: @tags', array('%type' => $field['type'] == 'list_text' ? 'text' : 'numeric', '@tags' => _field_filter_xss_display_allowed_tags())), + ); + $form['allowed_values_function'] = array( + '#type' => 'textfield', + '#title' => t('Allowed values function'), + '#default_value' => $allowed_values_function, + '#description' => t('The name of a function that will return the allowed values list. If this field is filled out, the array returned by this code will override the allowed values list above.'), + ); + $form['advanced_options'] = array( + '#type' => 'fieldset', + '#title' => t('PHP code for allowed values'), + '#collapsible' => TRUE, + '#collapsed' => empty($allowed_values_php), + ); + if (user_access('use PHP input for field settings')) { + $form['advanced_options']['allowed_values_php'] = array( + '#type' => 'textarea', + '#title' => t('Code'), + '#default_value' => $allowed_values_php, + '#rows' => 6, + '#description' => t("Advanced usage only: PHP code that returns a keyed array of allowed values. Should not include <?php ?> delimiters. If this field is filled out, the array returned by this code will override the allowed values list, and the allowed values function will be set to 'list_allowed_values_php' to execute this custom code. It is strongly recommended that you move this code to a custom function in a custom module and simply identify the custom function in the box above!"), + ); + } + else { + $form['advanced_options']['allowed_values_php'] = array( + '#type' => 'value', + '#value' => $allowed_values_php, + ); + $form['advanced_options']['markup_allowed_values_php'] = array( + '#type' => 'item', + '#title' => t('Code'), + '#value' => !empty($allowed_values_php) ? ''. check_plain($allowed_values_php) .'' : t('<none>'), + '#description' => empty($allowed_values_php) ? t("You're not allowed to input PHP code.") : t('This PHP code was set by an administrator and will override the allowed values list and allowed values functions shown above.'), + ); + } + + return $form; +} + +/** + * Handle Allowed values PHP code. + * + * @todo should we abstract this out of list.module ? + */ +function list_field_settings_form_validate($form, &$form_state) { + $form_values = $form_state['values']; + $field = $form['#field']; + + // Store allowed_values and allowed_values_php in the Field UI table, + // they are not core settings. Field UI stores them and will return the + // right value in response to the hook. + $option = $form_values['field']['settings']['advanced_options']['allowed_values_php']; + field_ui_set_setting('allowed_values_php', 'field', $option, $field); + $option = $form_values['field']['settings']['allowed_values']; + field_ui_set_setting('allowed_values', 'field', $option, $field); + + // Set allowed_values_function, which is a core setting. + $new_values['allowed_values_function'] = $form_values['field']['settings']['allowed_values_function']; + if (empty($new_values['allowed_values_function'])) { + $new_values['allowed_values_function'] = 'list_allowed_values_php'; + } + form_set_value($form, $new_values, $form_state); +} + +/** + * 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']]; +} + +/** + * 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. + * + * @todo should we abstract this out of list.module ? + */ +function list_allowed_values_list($field) { + $allowed_values[$field['field_name']] = array(); + + $list = field_ui_get_setting('allowed_values', 'field', $field); + $list = explode("\n", $list); + $list = array_map('trim', $list); + $list = array_filter($list, 'strlen'); + foreach ($list as $opt) { + // Sanitize the user input with a permissive filter. + $opt = field_filter_xss($opt); + if (strpos($opt, '|') !== FALSE) { + list($key, $value) = explode('|', $opt); + $allowed_values[$field['field_name']][$key] = (isset($value) && $value !=='') ? $value : $key; + } + else { + $allowed_values[$field['field_name']][$opt] = $opt; + } + } + return $allowed_values[$field['field_name']]; +} + +/** + * A custom function to return allowed values from PHP code + * or by exploding a text list. + */ +function list_allowed_values_php($field) { + $allowed_values = array(); + $php = field_ui_get_setting('allowed_values_php', 'field', $field); + if (!empty($php)) { + ob_start(); + $result = eval($php); + if (is_array($result)) { + $allowed_values = $result; + } + ob_end_clean(); + } + else { + $allowed_values = list_allowed_values_list($field); + } + return $allowed_values; +} + +/** * Implement hook_field_validate(). * * Possible error codes: @@ -167,29 +342,3 @@ function theme_field_formatter_list_defa 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/field/modules/number/number.info =================================================================== RCS file: /cvs/drupal/drupal/modules/field/modules/number/number.info,v retrieving revision 1.4 diff -u -p -r1.4 number.info --- modules/field/modules/number/number.info 12 Jun 2009 08:39:36 -0000 1.4 +++ modules/field/modules/number/number.info 15 Jul 2009 22:09:33 -0000 @@ -1,7 +1,7 @@ ; $Id: number.info,v 1.4 2009/06/12 08:39:36 dries Exp $ name = Number description = Defines numeric field types. -package = Core - fields +package = Core version = VERSION core = 7.x files[]=number.module Index: modules/field/modules/number/number.module =================================================================== RCS file: /cvs/drupal/drupal/modules/field/modules/number/number.module,v retrieving revision 1.10 diff -u -p -r1.10 number.module --- modules/field/modules/number/number.module 28 May 2009 16:44:06 -0000 1.10 +++ modules/field/modules/number/number.module 15 Jul 2009 22:09:33 -0000 @@ -88,6 +88,76 @@ function number_field_schema($field) { } /** + * Implement hook_field_settings_form(). + */ +function number_field_settings_form($field, $instance) { + $form = array(); + $settings = $field['settings']; + + 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(). * * Possible error codes: Index: modules/field/modules/options/options.info =================================================================== RCS file: /cvs/drupal/drupal/modules/field/modules/options/options.info,v retrieving revision 1.3 diff -u -p -r1.3 options.info --- modules/field/modules/options/options.info 12 Jun 2009 08:39:36 -0000 1.3 +++ modules/field/modules/options/options.info 15 Jul 2009 22:09:33 -0000 @@ -1,7 +1,7 @@ ; $Id: options.info,v 1.3 2009/06/12 08:39:36 dries Exp $ name = Options description = Defines selection, check box and radio button widgets for text and numeric fields. -package = Core - fields +package = Core version = VERSION core = 7.x files[]=options.module Index: modules/field/modules/text/text.info =================================================================== RCS file: /cvs/drupal/drupal/modules/field/modules/text/text.info,v retrieving revision 1.5 diff -u -p -r1.5 text.info --- modules/field/modules/text/text.info 12 Jun 2009 08:39:37 -0000 1.5 +++ modules/field/modules/text/text.info 15 Jul 2009 22:09:33 -0000 @@ -1,7 +1,7 @@ ; $Id: text.info,v 1.5 2009/06/12 08:39:37 dries Exp $ name = Text description = Defines simple text field types. -package = Core - fields +package = Core version = VERSION core = 7.x files[] = text.module Index: modules/field/modules/text/text.module =================================================================== RCS file: /cvs/drupal/drupal/modules/field/modules/text/text.module,v retrieving revision 1.15 diff -u -p -r1.15 text.module --- modules/field/modules/text/text.module 3 Jul 2009 18:19:29 -0000 1.15 +++ modules/field/modules/text/text.module 15 Jul 2009 22:09:34 -0000 @@ -129,6 +129,46 @@ 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']; + $options = array(0 => t('Plain text'), 1 => t('Filtered text (user selects input format)')); + $form['text_processing'] = array( + '#type' => 'radios', + '#title' => t('Text processing'), + '#default_value' => $settings['text_processing'], + '#options' => $options, + ); + if ($field['type'] == 'text_with_summary') { + $form['display_summary'] = array( + '#type' => 'select', + '#title' => t('Display summary'), + '#options' => array(0 => t('No'), 1 => t('Yes')), + '#default_value' => $settings['display_summary'], + '#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. '), + ); + } + return $form; +} + +/** * Implement hook_field_validate(). * * Possible error codes: @@ -491,6 +531,33 @@ 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(). * * Any FAPI callbacks needed for individual widgets can be declared here, Index: modules/field_ui/field_ui-display-overview-form.tpl.php =================================================================== RCS file: modules/field_ui/field_ui-display-overview-form.tpl.php diff -N modules/field_ui/field_ui-display-overview-form.tpl.php --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/field_ui/field_ui-display-overview-form.tpl.php 15 Jul 2009 22:09:34 -0000 @@ -0,0 +1,48 @@ + +
+ +
+ + + + + + $value): ?> + + + + $value): ?> + + + + + + + + + + $title): ?> + + + + + + +
  + +
indentation; ?>human_name; ?>{$context}->label; ?>{$context}->type; ?>
+ + 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 15 Jul 2009 22:09:34 -0000 @@ -0,0 +1,112 @@ + +
+ +
+ + + + + + + + + + + + + + + row_type): + case 'field': ?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ indentation; ?> + label; ?> + weight . $row->parent . $row->hidden_name; ?>field_name; ?>type; ?>widget_type; ?>configure; ?>  remove; ?> + indentation; ?> + label; ?> + weight . $row->parent . $row->hidden_name; ?>group_name; ?>group_type; ?>configure; ?>  remove; ?> + indentation; ?> + label; ?> + weight . $row->parent . $row->hidden_name; ?>description; ?>configure; ?>  remove; ?> + indentation; ?> +
+
+ label; ?> +
+
 
weight . $row->parent . $row->hidden_name; ?>
 
field_name; ?>
 
type; ?>
 
widget_type; ?>
+ indentation; ?> +
+
+ label; ?> +
+
 
weight . $row->parent . $row->hidden_name; ?>
 
field_name; ?>
 
widget_type; ?>
+ indentation; ?> +
+
+ label; ?> +
+
 
weight . $row->parent . $row->hidden_name; ?>
 
group_name; ?>
 
group_type; ?>
 
group_option; ?>
+ + + Index: modules/field_ui/field_ui-rtl.css =================================================================== RCS file: modules/field_ui/field_ui-rtl.css diff -N modules/field_ui/field_ui-rtl.css --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/field_ui/field_ui-rtl.css 15 Jul 2009 22:09:34 -0000 @@ -0,0 +1,9 @@ +/* $Id$ */ + +/* 'Manage fields' overview */ +#field-overview .label-add-new-field, +#field-overview .label-add-existing-field, +#field-overview .label-add-new-group { + float: right; +} + 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 15 Jul 2009 22:09:34 -0000 @@ -0,0 +1,112 @@ + 'textfield', + '#title' => t('Maximum length'), + '#default_value' => !empty($settings['max_length']) && is_numeric($settings['max_length']) ? $settings['max_length'] : $defaults['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']; + $options = array(0 => t('Plain text'), 1 => t('Filtered text (user selects input format)')); + $form['text_processing'] = array( + '#type' => 'radios', + '#title' => t('Text processing'), + '#default_value' => !empty($settings['text_processing']) && is_numeric($settings['text_processing']) ? $settings['text_processing'] : $defaults['text_processing'], + '#options' => $options, + ); + if ($field['type'] == 'text_with_summary') { + $form['display_summary'] = array( + '#type' => 'select', + '#options' => array(0 => t('No'), 1 => t('Yes')), + '#title' => t('Display summary'), + '#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']; + 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) { +} 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 15 Jul 2009 22:09:34 -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.info =================================================================== RCS file: modules/field_ui/field_ui.info diff -N modules/field_ui/field_ui.info --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/field_ui/field_ui.info 15 Jul 2009 22:09:34 -0000 @@ -0,0 +1,8 @@ +; $Id$ +name = Field UI +description = User Interface for the Field API. +package = Core +core = 7.x +files[] = field_ui.module +files[] = field_ui.install +files[] = field_ui.pages.inc Index: modules/field_ui/field_ui.install =================================================================== RCS file: modules/field_ui/field_ui.install diff -N modules/field_ui/field_ui.install --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/field_ui/field_ui.install 15 Jul 2009 22:09:34 -0000 @@ -0,0 +1,67 @@ + array( + 'field_name' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'description' => 'The name of the field.', + ), + 'bundle' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => FALSE, + 'description' => 'The name of the bundle, NULL for field settings.', + ), + 'setting_type' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'description' => 'The type of setting (field, instance, widget, display).', + ), + 'setting' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'description' => 'The name of the setting.', + ), + 'setting_option' => array( + 'type' => 'text', + 'size' => 'medium', + 'not null' => FALSE, + 'description' => 'The custom value for this setting.', + ), + ), + ); + // @todo indexes + return $schema; +} + Index: modules/field_ui/field_ui.js =================================================================== RCS file: modules/field_ui/field_ui.js diff -N modules/field_ui/field_ui.js --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/field_ui/field_ui.js 15 Jul 2009 22:09:34 -0000 @@ -0,0 +1,85 @@ +// $Id$ + +(function($) { + +Drupal.behaviors.fieldManageFields = { + attach: function(context) { + attachUpdateSelects(context); + } +}; + +function attachUpdateSelects(context) { + var widgetTypes = Drupal.settings.fieldWidgetTypes; + var fields = Drupal.settings.fields; + + // Store the default text of widget selects. + $('#field-overview .widget-type-select', context).each(function() { + this.initialValue = this.options[0].text; + }); + + // 'Field type' select updates its 'Widget' select. + $('#field-overview .field-type-select', context).each(function() { + this.targetSelect = $('.widget-type-select', $(this).parents('tr').eq(0)); + + $(this).change(function() { + var selectedFieldType = this.options[this.selectedIndex].value; + var options = (selectedFieldType in widgetTypes) ? widgetTypes[selectedFieldType] : [ ]; + this.targetSelect.fieldPopulateOptions(options); + }); + + // Trigger change on initial pageload to get the right widget options + // when field type comes pre-selected (on failed validation). + $(this).trigger('change'); + }); + + // 'Existing field' select updates its 'Widget' select and 'Label' textfield. + $('#field-overview .field-select', context).each(function() { + this.targetSelect = $('.widget-type-select', $(this).parents('tr').eq(0)); + this.targetTextfield = $('.label-textfield', $(this).parents('tr').eq(0)); + + $(this).change(function(e, updateText) { + var updateText = (typeof(updateText) == 'undefined') ? true : updateText; + var selectedField = this.options[this.selectedIndex].value; + var selectedFieldType = (selectedField in fields) ? fields[selectedField].type : null; + var selectedFieldWidget = (selectedField in fields) ? fields[selectedField].widget : null + var options = (selectedFieldType && (selectedFieldType in widgetTypes)) ? widgetTypes[selectedFieldType] : [ ]; + this.targetSelect.fieldPopulateOptions(options, selectedFieldWidget); + + if (updateText) { + $(this.targetTextfield).attr('value', (selectedField in fields) ? fields[selectedField].label : ''); + } + }); + + // Trigger change on initial pageload to get the right widget options + // and label when field type comes pre-selected (on failed validation). + $(this).trigger('change', false); + }); +} + +jQuery.fn.fieldPopulateOptions = function(options, selected) { + return this.each(function() { + var disabled = false; + if (options.length == 0) { + options = [this.initialValue]; + disabled = true; + } + + // If possible, keep the same widget selected when changing field type. + // This is based on textual value, since the internal value might be + // different (options_buttons vs. node_reference_buttons). + var previousSelectedText = this.options[this.selectedIndex].text; + + var html = ''; + jQuery.each(options, function(value, text) { + // Figure out which value should be selected. The 'selected' param + // takes precedence. + var is_selected = ((typeof(selected) !== 'undefined' && value == selected) || (typeof(selected) == 'undefined' && text == previousSelectedText)); + html += ''; + }); + + $(this) + .html(html) + .attr('disabled', disabled ? 'disabled' : ''); + }); +} +})(jQuery); 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 15 Jul 2009 22:09:35 -0000 @@ -0,0 +1,668 @@ +' . t('The content module, a required component of the Content Construction Kit (CCK), allows administrators to associate custom fields with content types. In Drupal, content types are used to define the characteristics of a post, including the title and description of the fields displayed on its add and edit pages. Using the content module (and the other helper modules included in CCK), custom fields beyond the default "Title" and "Body" may be added. CCK features are 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, or references to other objects) and how it will be displayed (either as a text field or area, a select box, checkbox, radio button, or autocompleting 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). As you add and edit fields, CCK automatically adjusts the structure of the database as necessary. CCK also provides a number of other features, including intelligent caching for your custom data, an import and export facility for content type definitions, and integration with other contributed modules.') . '

'; + // $output .= '

' . t('Custom field types are provided by a set of optional modules included with CCK (each module provides a different type). The modules page allows you to enable or disable CCK components. A default installation of CCK includes:', array('@modules' => url('admin/build/modules'))) . '

'; + // $output .= ''; + // $output .= '

' . t('For more information, see the online handbook entry for CCK or the CCK project page.', array('@handbook-cck' => 'http://drupal.org/handbook/modules/cck', '@project-cck' => 'http://drupal.org/project/cck')) . '

'; + $output = ''; + return $output; + case 'admin/build/fields': + return t('The list below shows all fields currently in use for easy reference.'); + } +} + +/** + * Implement hook_permission(). + */ +function field_ui_permisssion() { + return array( + 'use PHP input for field settings' => array( + 'title' => t('Use PHP input for field settings (dangerous - grant with care)'), + 'description' => t('Enter PHP code in the field for the field settings that allow it. Warning: Give to trusted roles only; this permission has security implications.'), + ), + ); +} + +/** + * Implement hook_menu(). + */ +function field_ui_menu() { + $items['admin/build/fields'] = array( + 'title' => 'Fields', + 'description' => 'Overview of fields on all object types.', + 'page callback' => 'field_ui_fields_list', + 'access arguments' => array('administer content types'), + 'type' => MENU_NORMAL_ITEM, + ); + + // Make sure this doesn't fire until field_bundles is working, + // and tables are updated, needed to avoid errors on initial installation. + if (!defined('MAINTENANCE_MODE')) { + // Create tabs for all possible bundles. + foreach (field_info_fieldable_types() as $obj_type => $info) { + foreach ($info['bundles'] as $bundle_name => $bundle_info) { + if (isset($bundle_info['admin'])) { + // Extract informations from the bundle description. + $path = $bundle_info['admin']['path']; + $bundle_arg = isset($bundle_info['admin']['bundle argument']) ? $bundle_info['admin']['bundle argument'] : $bundle_name; + $access = array_intersect_key($bundle_info['admin'], drupal_map_assoc(array('access callback', 'access arguments'))); + + $items["$path/fields"] = array( + 'title' => 'Manage fields', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('field_ui_field_overview_form', $obj_type, $bundle_arg), + 'type' => MENU_LOCAL_TASK, + 'weight' => 1, + ) + $access; + // A dummy function to trigger a page refresh so that + // field menus get rebuilt correctly when new fields are added. + $items["$path/fields/refresh"] = array( + 'title' => 'Refresh menu', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('field_ui_field_menu_refresh', $obj_type, $bundle_arg), + 'type' => MENU_CALLBACK, + 'weight' => 1, + ) + $access; + $items["$path/display"] = array( + 'title' => 'Display fields', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('field_ui_display_overview_form', $obj_type, $bundle_arg), + 'type' => MENU_LOCAL_TASK, + 'weight' => 2, + ) + $access; + + // 'Display fields' tab and context secondary tabs. + $tabs = field_ui_build_modes_tabs($obj_type); + foreach ($tabs as $key => $tab) { + $items["$path/display/$key"] = array( + 'title' => $tab['title'], + 'page arguments' => array('field_ui_display_overview_form', $obj_type, $bundle_arg, $key), + 'type' => $key == 'basic' ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK, + 'weight' => $key == 'basic' ? 0 : 1, + ) + $access; + } + + // Add tabs for any instances that are already created. + $instances = field_info_instances($bundle_name); + foreach ($instances as $instance) { + $field_name = $instance['field_name']; + $items["$path/fields/$field_name"] = array( + 'title' => $instance['label'], + 'page callback' => 'drupal_get_form', + 'page arguments' => array('field_ui_field_edit_form', $obj_type, $bundle_arg, $field_name), + 'type' => MENU_LOCAL_TASK, + ) + $access; + $items["$path/fields/$field_name/edit"] = array( + 'title' => 'Configure instance settings', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('field_ui_field_edit_form', $obj_type, $bundle_arg, $field_name), + 'type' => MENU_DEFAULT_LOCAL_TASK, + ) + $access; + $items["$path/fields/$field_name/field-settings"] = array( + 'title' => 'Configure field settings', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('field_ui_field_settings_form', $obj_type, $bundle_arg, $field_name), + 'type' => MENU_LOCAL_TASK, + ) + $access; + $items["$path/fields/$field_name/widget-type"] = array( + 'title' => 'Change widget type', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('field_ui_widget_type_form', $obj_type, $bundle_arg, $field_name), + 'type' => MENU_LOCAL_TASK, + ) + $access; + $items["$path/fields/$field_name/remove"] = array( + 'title' => 'Remove instance', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('field_ui_field_remove_form', $obj_type, $bundle_arg, $field_name), + 'type' => MENU_LOCAL_TASK, + ) + $access; + } + } + } + } + } + return $items; +} + +/** + * Implement hook_theme(). + */ +function field_ui_theme() { + return array( + 'field_ui_field_overview_form' => array( + 'arguments' => array('form' => NULL), + 'file' => 'field_ui.pages.inc', + 'template' => 'field_ui-field-overview-form', + ), + 'field_ui_display_overview_form' => array( + 'arguments' => array('form' => NULL), + 'file' => 'field_ui.pages.inc', + 'template' => 'field_ui-display-overview-form', + ), + ); +} + +/** + * Implement hook_field_attach_form(). + * + * @todo move 'extra fields' handling in field.module ? + */ +function field_ui_field_attach_form($obj_type, &$object, &$form, &$form_state) { + // Add identifier to the object to be used to alter extra fields in the form. + list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object); + $form['#fieldable'] = TRUE; + $form['#bundle'] = $bundle; + $form['#pre_render'][] = 'field_ui_alter_extra_weights'; + $form['#extra_fields'] = field_ui_extra_field_values($bundle); +} + +/** + * Implement hook_field_attach_view(). + * + * @todo move 'extra fields' handling in field.module ? + */ +function field_ui_field_attach_view($output, $obj_type, &$object, $teaser) { + // Add identifier to the object to be used to alter extra fields in the view. + list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object); + $object->content['#fieldable'] = TRUE; + $object->content['#bundle'] = $bundle; + + $object->content['#pre_render'][] = 'feld_ui_alter_extra_weights'; + $object->content['#extra_fields'] = field_ui_extra_field_values($bundle); + + return $output; +} + +/** + * Pre-render callback to adjust weights of non-field elements on objects. + * + * @todo move this to field.module + */ +function field_ui_alter_extra_weights($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; +} + +// @todo Move to user_field_extra_fields() +// @todo Add other user elements here. +// How to do this for users? The form elements are totally different +// than the view elements. +function _field_ui_user_extra_fields() { + $extra = array(); + $extra['account'] = array( + 'label' => 'User name and password', + 'description' => t('User module form element'), + 'weight' => -10 + ); + $extra['timezone'] = array( + 'label' => 'Timezone', + 'description' => t('User module form element.'), + 'weight' => 6 + ); + $extra['summary'] = array( + 'label' => 'History', + 'description' => t('User module view element.'), + 'weight' => 5 + ); + return $extra; +} + +// @todo Move to node_field_extra_fields() +function _field_ui_node_extra_fields($type_name) { + $extra = array(); + if ($type = node_type_get_type($type_name)) { + if ($type->has_title) { + $extra['title'] = array( + 'label' => $type->title_label, + 'description' => t('Node module element.'), + 'weight' => -5 + ); + } + $extra['revision_information'] = array( + 'label' => t('Revision information'), + 'description' => t('Node module form.'), + 'weight' => 20 + ); + $extra['author'] = array( + 'label' => t('Authoring information'), + 'description' => t('Node module form.'), + 'weight' => 20 + ); + $extra['options'] = array( + 'label' => t('Publishing options'), + 'description' => t('Node module form.'), + 'weight' => 25 + ); + if (module_exists('comment') && variable_get("comment_$type_name", 2) != 0) { + $extra['comment_settings'] = array( + 'label' => t('Comment settings'), + 'description' => t('Comment module form.'), + 'weight' => 30 + ); + } + if (module_exists('locale') && variable_get("language_content_type_$type_name", 0)) { + $extra['language'] = array( + 'label' => t('Language'), + 'description' => t('Locale module element.'), + 'weight' => 0 + ); + } + if (module_exists('menu')) { + $extra['menu'] = array( + 'label' => t('Menu settings'), + 'description' => t('Menu module element.'), + 'weight' => -2 + ); + } + if (module_exists('taxonomy') && taxonomy_get_vocabularies($type_name)) { + $extra['taxonomy'] = array( + 'label' => t('Taxonomy'), + 'description' => t('Taxonomy module element.'), + 'weight' => -3 + ); + } + if (module_exists('book')) { + $extra['book'] = array( + 'label' => t('Book'), + 'description' => t('Book module element.'), + 'weight' => 10 + ); + } + if ($type_name == '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 + ); + } + if (module_exists('upload') && variable_get("upload_$type_name", TRUE)) { + $extra['attachments'] = array( + 'label' => t('File attachments'), + 'description' => t('Upload module element.'), + 'weight' => 30, + 'view' => 'files' + ); + } + } + + return $extra; +} + +/** + * Retrieve the user-defined weight for non-field object components. + * + * Field UI's 'Manage fields' page lets users reorder fields, but also non-field + * items (title, menu settings, other hook_node-added elements by contributed + * modules...). + * Contrib modules that want to have their 'fields' supported need to expose + * them with hook_fields_extra_fields, and use this function to retrieve the + * user-defined weight. + * + * @param $type_name + * The content type name. + * @param $pseudo_field_name + * The name of the 'field'. + * @return + * The weight for the 'field', respecting the user settings stored + * by field.module. + */ +function field_ui_extra_field_weight($bundle_name, $pseudo_field_name) { + $extra = field_ui_extra_field_values($bundle_name); + if (isset($extra[$pseudo_field_name])) { + return $extra[$pseudo_field_name]['weight']; + } +} + +function field_ui_extra_field_values($bundle_name) { + static $info = 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_ui_extra_fields', $bundle); + drupal_alter('field_ui_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(); + } +} + +/** + * Group available build modes on tabs on the 'Display fields' page. + * + * @todo remove this completely and use vertical tabs ? + */ +function field_ui_build_modes_tabs($obj_type, $tab_selector = NULL) { + static $info; + + if (!isset($info[$obj_type])) { + $info[$obj_type] = module_invoke_all('field_ui_build_modes_tabs'); + // Collect titles, and filter out non active modes. + $active_modes = field_build_modes($obj_type); + foreach ($info[$obj_type] as $tab => $values) { + $modes = array(); + foreach ($info[$obj_type][$tab]['build modes'] as $mode) { + if (isset($active_modes[$mode])) { + $modes[$mode] = $active_modes[$mode]; + } + } + if ($modes) { + $info[$obj_type][$tab]['build modes'] = $modes; + } + else { + unset($info[$obj_type][$tab]); + } + } + } + if ($tab_selector) { + return isset($info[$obj_type][$tab_selector]) ? $info[$obj_type][$tab_selector]['build modes'] : array(); + } + else { + return $info[$obj_type]; + } +} + +/** + * Implement hook_field_ui_build_modes_tabs(), on behalf of other core modules. + * + * @return + * An array describing the build modes defined by the module, grouped by tabs. + * + * Expected format: + * array( + * // A module can add its render modes to a tab defined by another module. + * 'tab1' => array( + * 'title' => t('The human-readable title of the tab'), + * 'build modes' => array('mymodule_mode1', 'mymodule_mode2'), + * ), + * 'tab2' => array( + * // ... + * ), + * ); + */ +function field_ui_field_ui_build_modes_tabs() { + $modes = array( + 'basic' => array( + 'title' => t('Basic'), + 'build modes' => array('teaser', 'full'), + ), + 'rss' => array( + 'title' => t('RSS'), + 'build modes' => array('rss'), + ), + 'print' => array( + 'title' => t('Print'), + 'build modes' => array('print'), + ), + 'search' => array( + 'title' => t('Search'), + 'build modes' => array('search_index', 'search_result'), + ), + ); + return $modes; +} + +/** + * The Field API doesn't allow field updates, so we create a method here to + * update field if no data is created yet. + * + * @see field_create_field() + */ +function field_ui_update_field($field) { + $field_types = field_info_field_types(); + $module = $field_types[$field['type']]['module']; + + $defaults = field_info_field_settings($field['type']); + $field['settings'] = array_merge($defaults, (array) $field['settings']); + $data = $field; + unset($data['id'], $data['columns'], $data['field_name'], $data['type'], $data['locked'], $data['module'], $data['cardinality'], $data['active'], $data['deleted']); + $field['data'] = $data; + + drupal_write_record('field_config', $field, array('field_name')); + + // Clear caches + field_cache_clear(TRUE); +} + +/** + * Implement hook_field_attach_create_bundle(). + */ +function field_ui_field_attach_create_bundle($bundle) { + // @todo Fix this. + // Trying to get the Manage Fields screen for a new content type to + // work immediately after the new type is created. Even this won't do it, + // MF screen is still 'Page not found' after the new type is created. + menu_rebuild(); + field_cache_clear(); +} + +/** + * Implement hook_field_attach_rename_bundle(). + */ +function field_ui_field_attach_rename_bundle($bundle_old, $bundle_new) { + if ($bundle_old !== $bundle_new && $extra = variable_get("field_extra_weights_$bundle_old", array())) { + variable_set("field_extra_weights_$bundle_new", $extra); + variable_del("field_extra_weights_$bundle_old"); + } +} + +/** + * Implement hook_field_attach_delete_bundle(). + */ +function field_ui_field_attach_delete_bundle($bundle) { + variable_del('field_extra_weights_' . $bundle); +} + +/** + * Helper function to create the right administration path for a bundle. + */ +function _field_ui_bundle_admin_path($bundle_name) { + $bundles = field_info_bundles(); + $bundle_info = $bundles[$bundle_name]; + return isset($bundle_info['admin']['real path']) ? $bundle_info['admin']['real path'] : $bundle_info['admin']['path']; +} + +/** + * We store all settings in a flat text field, but some settings + * will be arrays that need to be serialized and unserialized, + * like the default_value. + */ +function field_ui_serialized_settings() { + return array('default_value'); +} + +/** + * Helper function to retrieve field settings stored by Field UI. + * + * @todo Is this really needed ? why can't we store settings in the main + * field definitions ? + * + * Field UI uses the 'field_ui_field_settings' table to store custom settings + * not used by core. + * + * Field settings will have no $instance nor a db bundle column. + */ +function field_ui_get_setting($setting, $setting_type, $field, $instance = NULL) { + if ($setting_type == 'field' || empty($instance)) { + $value = db_select('field_ui_field_settings', 'fs')->fields('fs', array('setting_option')) + ->condition('fs.setting', $setting) + ->condition('fs.setting_type', $setting_type) + ->condition('fs.field_name', $field['field_name']) + ->execute()->fetchField(); + } + else { + $value = db_select('field_ui_field_settings', 'fs')->fields('fs', array('setting_option')) + ->condition('fs.setting', $setting) + ->condition('fs.setting_type', $setting_type) + ->condition('fs.field_name', $field['field_name']) + ->condition('fs.bundle', $instance['bundle']) + ->execute()->fetchField(); + } + + if (in_array($setting, field_ui_serialized_settings())) { + $value = unserialize($value); + } + return $value; +} + +/** + * Helper function to set field settings stored by Field UI. + * + * @todo Is this really needed ? why can't we store settings in the main + * field definitions ? + * + * Field UI uses the 'field_ui_field_settings' table to store custom settings + * not used by core. + * + * Field settings will have no $instance nor a db bundle column. + */ +function field_ui_set_setting($setting, $setting_type, $value, $field, $instance = NULL) { + // Delete any prior values. + $bundle = ($setting_type == 'field' || empty($instance)) ? NULL : $instance['bundle']; + if ($setting_type == 'field' || empty($instance)) { + db_delete('field_ui_field_settings') + ->condition('field_name', $field['field_name']) + ->condition('setting', $setting) + ->condition('setting_type', $setting_type) + ->execute(); + } + else { + db_delete('field_ui_field_settings') + ->condition('field_name', $field['field_name']) + ->condition('bundle', $bundle) + ->condition('setting', $setting) + ->condition('setting_type', $setting_type) + ->execute(); + } + // Create the new values. + if (in_array($setting, field_ui_serialized_settings())) { + $value = serialize($value); + } + $record = array( + 'field_name' => $field['field_name'], + 'bundle' => $bundle, + 'setting' => $setting, + 'setting_option' => $value, + 'setting_type' => $setting_type, + ); + $primary_keys = array(); + drupal_write_record('field_ui_field_settings', $record, $primary_keys); +} + +/** + * Implement hook_field_default_value(). + * + * Helper function to return the correct default value for a field + * on behalf of fields managed in the Field UI. + * + * @param $obj_type + * The object type. + * @param $object + * The object. + * @param $field + * The field array. + * @param $instance + * The field array. + * @return + * The default value for that field. + */ +function field_ui_field_default_value($obj_type, $object, $field, $instance) { + $default_value = array(); + $default_setting = field_ui_get_setting('default_value', 'instance', $field, $instance); + $default_setting_php = field_ui_get_setting('default_value_php', 'instance', $field, $instance); + if (!empty($default_setting_php)) { + ob_start(); + $result = eval($default_setting_php); + if (is_array($result)) { + $default_value = $result; + } + } + elseif (!empty($default_setting)) { + $default_value = $default_setting; + } + return (array) $default_value; +} + +/** + * Helper function to identify inactive fields. + * + * @todo + */ +function field_ui_inactive_fields($type_name = NULL) { +// module_load_include('inc', 'field', 'includes/field.crud'); +// if (!empty($type_name)) { +// $param = array('type_name' => $type_name); +// $inactive = array($type_name => array()); +// } +// else { +// $param = array(); +// $inactive = array(); +// } +// $all = field_field_instance_read($param, TRUE); +// $active = array_keys(field_fields()); +// foreach ($all as $field) { +// if (!in_array($field['field_name'], $active)) { +// $inactive[$field['type_name']][$field['field_name']] = field_field_instance_expand($field); +// } +// } +// if (!empty($type_name)) { +// return $inactive[$type_name]; +// } +// return $inactive; +} Index: modules/field_ui/field_ui.pages.inc =================================================================== RCS file: modules/field_ui/field_ui.pages.inc diff -N modules/field_ui/field_ui.pages.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/field_ui/field_ui.pages.inc 15 Jul 2009 22:09:36 -0000 @@ -0,0 +1,1757 @@ + $info) { + foreach ($info as $field_name => $instance) { + $field = field_info_field($field_name); + $admin_path = _field_ui_bundle_admin_path($bundle); + $rows[$field_name]['data'][0] = $field['locked'] ? t('@field_name (Locked)', array('@field_name' => $field_name)) : $field_name; + $rows[$field_name]['data'][1] = t($field_types[$field['type']]['label']); + $rows[$field_name]['data'][2][] = l($bundles[$bundle]['label'], $admin_path . '/fields'); + $rows[$field_name]['class'] = $field['locked'] ? 'menu-disabled' : ''; + } + } + foreach ($rows as $field_name => $cell) { + $rows[$field_name]['data'][2] = implode(', ', $cell['data'][2]); + } + if (empty($rows)) { + $output = t('No fields have been defined for any content type yet.'); + } + else { + // Sort rows by field name. + ksort($rows); + $output = theme('table', $header, $rows); + } + return $output; +} + +/** + * Helper function to display a message about inactive fields. + */ +function field_ui_inactive_message($bundle) { + // @todo adapt to new D7 APIs + $inactive_fields = field_ui_inactive_fields($bundle); + if (!empty($inactive_fields)) { + $field_types = _field_field_types(); + $widget_types = _field_widget_types($bundle); + drupal_set_message(t('This bundle has inactive fields. Inactive fields are not included in lists of available fields until their modules are enabled.'), 'error'); + foreach ($inactive_fields as $field_name => $field) { + drupal_set_message(t('!field (!field_name) is an inactive !field_type field that uses a !widget_type widget.', array( + '!field' => $field['label'], + '!field_name' => $field['field_name'], + '!field_type' => array_key_exists($field['type'], $field_types) ? $field_types[$field['type']]['label'] : $field['type'], + '!widget_type' => array_key_exists($field['widget']['type'], $widget_types) ? $widget_types[$field['widget']['type']]['label'] : $field['widget']['type'], + ))); + } + } +} + +/** + * Menu callback; listing of fields for a content type. + * + * Allows fields to be reordered and nested in fieldgroups using + * JS drag-n-drop. Non-field form elements can also be moved around. + */ +function field_ui_field_overview_form(&$form_state, $obj_type, $bundle) { + $bundle = field_attach_extract_bundle($obj_type, $bundle); + + field_ui_inactive_message($bundle); + $admin_path = _field_ui_bundle_admin_path($bundle); + + // When displaying the form, make sure the list of fields + // is up-to-date. + if (empty($form_state['post'])) { + field_cache_clear(); + } + + // Gather bundle information. + $instances = field_info_instances($bundle); + $field_types = field_info_field_types(); + $widget_types = field_info_widget_types(); + + $extra = field_ui_extra_field_values($bundle); + + $groups = $group_options = array(); + if (module_exists('fieldgroup')) { + $groups = fieldgroup_groups($bundle); + $group_types = fieldgroup_types(); + $group_options = _fieldgroup_groups_label($bundle); + // Add the ability to group under the newly created row. + $group_options['_add_new_group'] = '_add_new_group'; + } + + // Store the default weights as we meet them, to be able to put the + //'add new' rows after them. + $weights = array(); + + $form = array( + '#tree' => TRUE, + '#bundle' => $bundle, + '#fields' => array_keys($instances), + '#groups' => array_keys($groups), + '#extra' => array_keys($extra), + '#field_rows' => array(), + '#group_rows' => array(), + ); + + // Fields. + foreach ($instances as $name => $instance) { + $field = field_info_field($instance['field_name']); + $admin_field_path = $admin_path . '/fields/' . $instance['field_name']; + $weight = $instance['weight']; + $form[$name] = array( + 'label' => array( + '#markup' => check_plain($instance['label']) + ), + 'field_name' => array( + '#markup' => $instance['field_name'] + ), + 'type' => array( + '#markup' => l(t($field_types[$field['type']]['label']), $admin_field_path . '/field-settings', array('attributes' => array('title' => t('Edit field settings.')))) + ), + 'widget_type' => array( + '#markup' => l(t($widget_types[$instance['widget']['type']]['label']), $admin_field_path . '/widget-type', array('attributes' => array('title' => t('Change widget type.')))) + ), + 'configure' => array( + '#markup' => l(t('Configure'), $admin_field_path, array('attributes' => array('title' => t('Edit instance settings.')))) + ), + 'remove' => array( + '#markup' => l(t('Remove'), $admin_field_path . '/remove', array('attributes' => array('title' => t('Remove instance.')))) + ), + 'weight' => array( + '#type' => 'textfield', + '#default_value' => $weight, + '#size' => 3 + ), + 'parent' => array( + '#type' => 'select', + '#options' => $group_options, + '#default_value' => '' + ), + 'prev_parent' => array( + '#type' => 'hidden', + '#value' => '' + ), + 'hidden_name' => array( + '#type' => 'hidden', + '#default_value' => $instance['field_name'] + ), + '#leaf' => TRUE, + '#row_type' => 'field', + // @todo: is this really needed ? + 'field' => array( + '#type' => 'value', + '#value' => $field + ), + ); + + if (!empty($instance['locked'])) { + $form[$name]['configure'] = array('#value' => t('Locked')); + $form[$name]['remove'] = array(); + $form[$name]['#disabled_row'] = TRUE; + } + $form['#field_rows'][] = $name; + $weights[] = $weight; + } + + // Groups. + foreach ($groups as $name => $group) { + $weight = $group['weight']; + $form[$name] = array( + 'label' => array( + '#markup' => check_plain($group['label']) + ), + 'group_name' => array( + '#markup' => $group['group_name'] + ), + 'group_type' => array( + '#markup' => t($group_types[$group['group_type']]) + ), + 'configure' => array( + '#markup' => l(t('Configure'), $admin_path . '/groups/' . $group['group_name']) + ), + 'remove' => array( + '#markup' => l(t('Remove'), $admin_path . '/groups/' . $group['group_name'] . '/remove') + ), + 'weight' => array( + '#type' => 'textfield', + '#default_value' => $weight, + '#size' => 3 + ), + 'parent' => array( + '#type' => 'hidden', + '#default_value' => '' + ), + 'hidden_name' => array( + '#type' => 'hidden', + '#default_value' => $group['group_name'] + ), + '#root' => TRUE, + '#row_type' => 'group', + 'group' => array( + '#type' => 'value', + '#value' => $group + ), + ); + // Adjust child fields rows. + foreach ($group['fields'] as $field_name => $field) { + $form[$field_name]['parent']['#default_value'] = $name; + $form[$field_name]['prev_parent']['#value'] = $name; + } + $form['#group_rows'][] = $name; + $weights[] = $weight; + } + + // Non-field elements. + foreach ($extra as $name => $label) { + $weight = $extra[$name]['weight']; + $form[$name] = array( + 'label' => array( + '#markup' => t($extra[$name]['label']) + ), + 'description' => array( + '#markup' => isset($extra[$name]['description']) ? $extra[$name]['description'] : '' + ), + 'weight' => array( + '#type' => 'textfield', + '#default_value' => $weight, + '#size' => 3 + ), + 'parent' => array( + '#type' => 'hidden', + '#default_value' => '' + ), + 'configure' => array( + '#markup' => isset($extra[$name]['configure']) ? $extra[$name]['configure'] : '' + ), + 'remove' => array( + '#markup' => isset($extra[$name]['remove']) ? $extra[$name]['remove'] : '' + ), + 'hidden_name' => array( + '#type' => 'hidden', + '#default_value' => $name + ), + '#leaf' => TRUE, + '#root' => TRUE, + '#disabled_row' => TRUE, + '#row_type' => 'extra', + ); + $form['#field_rows'][] = $name; + $weights[] = $weight; + } + + // Additional row : add new field. + $weight = !empty($weights) ? max($weights) + 1 : 0; + $field_type_options = field_ui_field_type_options(); + $widget_type_options = field_ui_widget_type_options(NULL, TRUE); + if ($field_type_options && $widget_type_options) { + array_unshift($field_type_options, t('- Select a field type -')); + array_unshift($widget_type_options, t('- Select a widget -')); + $name = '_add_new_field'; + $form[$name] = array( + 'label' => array( + '#type' => 'textfield', + '#size' => 15, + '#description' => t('Label'), + ), + 'field_name' => array( + '#type' => 'textfield', + // This field should stay LTR even for RTL languages. + '#field_prefix' => 'field_', + '#field_suffix' => '‎', + '#attributes' => array('dir'=>'ltr'), + '#size' => 15, + '#description' => t('Field name (a-z, 0-9, _)'), + ), + 'type' => array( + '#type' => 'select', + '#options' => $field_type_options, + '#description' => t('Type of data to store.'), + ), + 'widget_type' => array( + '#type' => 'select', + '#options' => $widget_type_options, + '#description' => t('Form element to edit the data.'), + ), + 'weight' => array( + '#type' => 'textfield', + '#default_value' => $weight, + '#size' => 3 + ), + 'parent' => array( + '#type' => 'select', + '#options' => $group_options, + '#default_value' => '' + ), + 'hidden_name' => array( + '#type' => 'hidden', + '#default_value' => $name + ), + '#leaf' => TRUE, + '#add_new' => TRUE, + '#row_type' => 'add_new_field', + ); + $form['#field_rows'][] = $name; + } + + // Additional row : add existing field. + $existing_field_options = field_ui_existing_field_options($bundle); + if ($existing_field_options && $widget_type_options) { + $weight++; + array_unshift($existing_field_options, t('- Select an existing field -')); + $name = '_add_existing_field'; + $form[$name] = array( + 'label' => array( + '#type' => 'textfield', + '#size' => 15, + '#description' => t('Label'), + ), + 'field_name' => array( + '#type' => 'select', + '#options' => $existing_field_options, + '#description' => t('Field to share'), + ), + 'widget_type' => array( + '#type' => 'select', + '#options' => $widget_type_options, + '#description' => t('Form element to edit the data.'), + ), + 'weight' => array( + '#type' => 'textfield', + '#default_value' => $weight, + '#size' => 3 + ), + 'parent' => array( + '#type' => 'select', + '#options' => $group_options, + '#default_value' => '' + ), + 'hidden_name' => array( + '#type' => 'hidden', + '#default_value' => $name + ), + '#leaf' => TRUE, + '#add_new' => TRUE, + '#row_type' => 'add_existing_field', + ); + $form['#field_rows'][] = $name; + } + + // Additional row : add new group. + if (module_exists('fieldgroup')) { + $weight++; + $name = '_add_new_group'; + $form[$name] = array( + 'label' => array( + '#type' => 'textfield', + '#size' => 15, + '#description' => t('Label'), + ), + 'group_name' => array( + '#type' => 'textfield', + // This field should stay LTR even for RTL languages. + '#field_prefix' => 'group_', + '#field_suffix' => '‎', + '#attributes' => array('dir'=>'ltr'), + '#size' => 15, + '#description' => t('Group name (a-z, 0-9, _)'), + ), + 'group_option' => array( + '#type' => 'hidden', + '#value' => '', + ), + 'group_type' => array( + '#type' => 'hidden', + '#value' => 'standard', + ), + 'weight' => array( + '#type' => 'textfield', + '#default_value' => $weight, + '#size' => 3 + ), + 'parent' => array( + '#type' => 'hidden', + '#default_value' => '' + ), + 'hidden_name' => array( + '#type' => 'hidden', + '#default_value' => $name + ), + '#root' => TRUE, + '#add_new' => TRUE, + '#row_type' => 'add_new_group', + ); + $form['#group_rows'][] = $name; + } + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save') + ); + return $form; +} + +/** + * Theme preprocess function for field_ui-field-overview-form.tpl.php. + */ +function template_preprocess_field_ui_field_overview_form(&$vars) { + $form = &$vars['form']; + + drupal_add_css(drupal_get_path('module', 'field_ui') . '/field_ui.css'); + drupal_add_tabledrag('field-overview', 'match', 'parent', 'group-parent', 'group-parent', 'field-name', TRUE, 1); + drupal_add_tabledrag('field-overview', 'order', 'sibling', 'field-weight'); + drupal_add_js(drupal_get_path('module', 'field_ui') . '/field_ui.js'); + // Add settings for the update selects behavior. + $js_fields = array(); + foreach (field_ui_existing_field_options($form['#bundle']) as $field_name => $fields) { + $field = field_info_field($field_name); + $instance = field_info_instance($field_name, $form['#bundle']); + $js_fields[$field_name] = array('label' => $instance['label'], 'type' => $field['type'], 'widget' => $instance['widget']['type']); + } + drupal_add_js(array('fieldWidgetTypes' => field_ui_widget_type_options(), 'fields' => $js_fields), 'setting'); + + // @todo abstract texts over bundles / entity types + switch ($form['#bundle']) { + case 'user': + $vars['help'] = t('Add fields to the user, and arrange them on user display and input forms.'); + break; + default: + $vars['help'] = t('Add fields to the Content type, and arrange them on content display and input forms.'); + } + if (module_exists('fieldgroup')) { + $vars['help'] .= '
' . t('You can add a field to a group by dragging it below and to the right of the group.'); + } + + $order = _field_ui_overview_order($form, $form['#field_rows'], $form['#group_rows']); + $rows = array(); + + // Identify the 'new item' keys in the form, they look like + // _add_new_field, add_new_group. + $keys = array_keys($form); + $add_rows = array(); + foreach ($keys as $key) { + if (substr($key, 0, 4) == '_add') { + $add_rows[] = $key; + } + } + while ($order) { + $key = reset($order); + $element = &$form[$key]; + + // Only display the 'Add' separator if the 'add' rows are still + // at the end of the table. + if (!isset($added_separator)) { + $remaining_rows = array_diff($order, $add_rows); + if (empty($remaining_rows) && empty($element['#depth'])) { + $row = new stdClass(); + $row->row_type = 'separator'; + $row->class = 'tabledrag-leaf region'; + $rows[] = $row; + $added_separator = TRUE; + } + } + + $row = new stdClass(); + + // Add target classes for the tabledrag behavior. + $element['weight']['#attributes']['class'] = 'field-weight'; + $element['parent']['#attributes']['class'] = 'group-parent'; + $element['hidden_name']['#attributes']['class'] = 'field-name'; + // Add target classes for the update selects behavior. + switch ($element['#row_type']) { + case 'add_new_field': + $element['type']['#attributes']['class'] = 'field-type-select'; + $element['widget_type']['#attributes']['class'] = 'widget-type-select'; + break; + case 'add_existing_field': + $element['field_name']['#attributes']['class'] = 'field-select'; + $element['widget_type']['#attributes']['class'] = 'widget-type-select'; + $element['label']['#attributes']['class'] = 'label-textfield'; + break; + } + foreach (element_children($element) as $child) { + $row->{$child} = drupal_render($element[$child]); + } + $row->label_class = 'label-' . strtr($element['#row_type'], '_', '-'); + $row->row_type = $element['#row_type']; + $row->indentation = theme('indentation', isset($element['#depth']) ? $element['#depth'] : 0); + $row->class = 'draggable'; + $row->class .= isset($element['#disabled_row']) ? ' menu-disabled' : ''; + $row->class .= isset($element['#add_new']) ? ' add-new' : ''; + $row->class .= isset($element['#leaf']) ? ' tabledrag-leaf' : ''; + $row->class .= isset($element['#root']) ? ' tabledrag-root' : ''; + + $rows[] = $row; + array_shift($order); + } + $vars['rows'] = $rows; + $vars['submit'] = drupal_render_children($form); +} + +function field_ui_field_overview_form_validate($form, &$form_state) { + _field_ui_field_overview_form_validate_add_new($form, $form_state); + _field_ui_field_overview_form_validate_add_existing($form, $form_state); +} + +/** + * Helper function for field_ui_field_overview_form_validate. + * + * Validate the 'add new field' row. + */ +function _field_ui_field_overview_form_validate_add_new($form, &$form_state) { + $field = $form_state['values']['_add_new_field']; + + // Validate if any information was provided in the 'add new field' row. + if (array_filter(array($field['label'], $field['field_name'], $field['type'], $field['widget_type']))) { + // No label. + if (!$field['label']) { + form_set_error('_add_new_field][label', t('Add new field: you need to provide a label.')); + } + + // No field name. + if (!$field['field_name']) { + form_set_error('_add_new_field][field_name', t('Add new field: you need to provide a field name.')); + } + // Field name validation. + else { + $field_name = $field['field_name']; + + // Add the 'field_' prefix. + if (substr($field_name, 0, 6) != 'field_') { + $field_name = 'field_' . $field_name; + form_set_value($form['_add_new_field']['field_name'], $field_name, $form_state); + } + + // Invalid field name. + if (!preg_match('!^field_[a-z0-9_]+$!', $field_name)) { + form_set_error('_add_new_field][field_name', t('Add new field: the field name %field_name is invalid. The name must include only lowercase unaccentuated letters, numbers, and underscores.', array('%field_name' => $field_name))); + } + if (strlen($field_name) > 32) { + form_set_error('_add_new_field][field_name', t("Add new field: the field name %field_name is too long. The name is limited to 32 characters, including the 'field_' prefix.", array('%field_name' => $field_name))); + } + + // Field name already exists. + // We need to check inactive fields as well, so we can't use field_fields(). + module_load_include('inc', 'content', 'includes/content.crud'); + $fields = field_read_fields(array(), TRUE); + $used = FALSE; + foreach ($fields as $existing_field) { + $used |= ($existing_field['field_name'] == $field_name); + } + if ($used) { + form_set_error('_add_new_field][field_name', t('Add new field: the field name %field_name already exists.', array('%field_name' => $field_name))); + } + } + + // No field type. + if (!$field['type']) { + form_set_error('_add_new_field][type', t('Add new field: you need to select a field type.')); + } + + // No widget type. + if (!$field['widget_type']) { + form_set_error('_add_new_field][widget_type', t('Add new field: you need to select a widget.')); + } + // Wrong widget type. + elseif ($field['type']) { + $widget_types = field_ui_widget_type_options($field['type']); + if (!isset($widget_types[$field['widget_type']])) { + form_set_error('_add_new_field][widget_type', t('Add new field: invalid widget.')); + } + } + } +} + +/** + * Helper function for field_ui_field_overview_form_validate. + * + * Validate the 'add existing field' row. + */ +function _field_ui_field_overview_form_validate_add_existing($form, &$form_state) { + // The form element might be absent if no existing fields can be added to + // this content type + if (isset($form_state['values']['_add_existing_field'])) { + $field = $form_state['values']['_add_existing_field']; + + // Validate if any information was provided in the 'add existing field' row. + if (array_filter(array($field['label'], $field['field_name'], $field['widget_type']))) { + // No label. + if (!$field['label']) { + form_set_error('_add_existing_field][label', t('Add existing field: you need to provide a label.')); + } + + // No existing field. + if (!$field['field_name']) { + form_set_error('_add_existing_field][field_name', t('Add existing field: you need to select a field.')); + } + + // No widget type. + if (!$field['widget_type']) { + form_set_error('_add_existing_field][widget_type', t('Add existing field: you need to select a widget.')); + } + // Wrong widget type. + elseif ($field['field_name'] && ($existing_field = field_info_field($field['field_name']))) { + $widget_types = field_ui_widget_type_options($existing_field['type']); + if (!isset($widget_types[$field['widget_type']])) { + form_set_error('_add_existing_field][widget_type', t('Add existing field: invalid widget.')); + } + } + } + } +} + +function field_ui_field_overview_form_submit($form, &$form_state) { + $form_values = $form_state['values']; + $bundle = $form['#bundle']; + $admin_path = _field_ui_bundle_admin_path($bundle); + + // Update field weights. + $extra = array(); + foreach ($form_values as $key => $values) { + if (in_array($key, $form['#fields'])) { + db_query("UPDATE {field_config_instance} SET weight = %d WHERE bundle = '%s' AND field_name = '%s'", + $values['weight'], $bundle, $key); + } + elseif (in_array($key, $form['#extra'])) { + $extra[$key] = $values['weight']; + } + } + + if ($extra) { + variable_set("field_extra_weights_$bundle", $extra); + } + else { + variable_del("field_extra_weights_$bundle"); + } + + $destinations = array(); + + // Create new field. + $field = array(); + if (!empty($form_values['_add_new_field']['field_name'])) { + $values = $form_values['_add_new_field']; + + $field = array( + 'field_name' => $values['field_name'], + 'type' => $values['type'], + ); + $instance = array( + 'field_name' => $field['field_name'], + 'bundle' => $bundle, + 'label' => $values['label'], + 'weight' => $values['weight'], + 'widget' => array( + 'type' => $values['widget_type'], + ), + ); + + // Create the field and instance. + try { + field_create_field($field); + field_create_instance($instance); + + // Rebuild the menu so we can navigate to the field settings screen. + //field_clear_type_cache(TRUE); + //menu_rebuild(); + $destinations[] = $admin_path . '/fields/refresh'; + $destinations[] = $admin_path . '/fields/' . $field['field_name'] . '/field-settings'; + $destinations[] = $admin_path . '/fields/' . $field['field_name'] . '/edit'; + + // Store new field information for fieldgroup submit handler. + $form_state['fields_added']['_add_new_field'] = $field['field_name']; + } + catch (Exception $e) { + drupal_set_message(t('There was a problem creating field %label: @message.', array( + '%label' => $values['label'], '@message' => $e->getMessage()))); + } + } + + // Add existing field. + if (!empty($form_values['_add_existing_field']['field_name'])) { + $values = $form_values['_add_existing_field']; + $field = field_info_field($values['field_name']); + if (!empty($field['locked'])) { + drupal_set_message(t('The field %label cannot be added because it is locked.', array('%label' => $field['field_name']))); + } + else { + $instance = array( + 'field_name' => $field['field_name'], + 'bundle' => $bundle, + 'label' => $values['label'], + 'weight' => $values['weight'], + 'widget' => array( + 'type' => $values['widget_type'], + ), + ); + + try { + field_create_instance($instance); + $destinations[] = $admin_path . '/fields/refresh'; + $destinations[] = $admin_path . '/fields/' . $instance['field_name'] . '/edit'; + // Store new field information for fieldgroup submit handler. + $form_state['fields_added']['_add_existing_field'] = $instance['field_name']; + } + catch (Exception $e) { + drupal_set_message(t('There was a problem creating field instance %label: @message.', array( + '%label' => $field['label'], '@message' => $e->getMessage()))); + } + } + } + + if ($destinations) { + $destinations[] = urldecode(substr(drupal_get_destination(), 12)); + unset($_REQUEST['destination']); + $form_state['redirect'] = field_ui_get_destinations($destinations); + } + + field_cache_clear(); +} + +/** + * Menu callback; presents a listing of fields display settings for a content type. + * + * Form includes form widgets to select which fields appear for teaser, full node + * and how the field labels should be rendered. + */ +function field_ui_display_overview_form(&$form_state, $obj_type, $bundle, $build_modes_selector = 'basic') { + $bundle = field_attach_extract_bundle($obj_type, $bundle); + + field_ui_inactive_message($bundle); + $admin_path = _field_ui_bundle_admin_path($bundle); + + // Gather type information. + $entity = field_info_bundle_entity($bundle); + $instances = field_info_instances($bundle); + $field_types = field_info_field_types(); + + $groups = $group_options = array(); + if (module_exists('fieldgroup')) { + $groups = fieldgroup_groups($bundle); + $group_options = _fieldgroup_groups_label($bundle); + } + + $build_modes = field_ui_build_modes_tabs($entity, $build_modes_selector); + + $form = array( + '#tree' => TRUE, + '#bundle' => $bundle, + '#fields' => array_keys($instances), + '#groups' => array_keys($groups), + '#contexts' => $build_modes_selector, + ); + + if (empty($instances)) { + drupal_set_message(t('There are no fields configured yet. You can add new fields on the Manage fields page.', array('@link' => url($admin_path . '/fields'))), 'warning'); + return $form; + } + + // Fields. + $label_options = array( + 'above' => t('Above'), + 'inline' => t('Inline'), + 'hidden' => t(''), + ); + foreach ($instances as $name => $instance) { + $field = field_info_field($instance['field_name']); + $weight = $instance['weight']; + + $form[$name] = array( + 'human_name' => array( + '#markup' => check_plain($instance['label']) + ), + 'weight' => array( + '#type' => 'value', + '#value' => $weight + ), + 'parent' => array( + '#type' => 'value', + '#value' => '' + ), + ); + $defaults = $instance['display']; + + $formatter_options = field_ui_formatter_options($field['type']); + $formatter_options['hidden'] = t(''); + foreach ($build_modes as $build_mode => $label) { + $display = isset($instance['display'][$build_mode]) ? $instance['display'][$build_mode] : $instance['display']['full']; + $form[$name][$build_mode]['label'] = array( + '#type' => 'select', + '#options' => $label_options, + '#default_value' => $display['label'], + ); + $form[$name][$build_mode]['type'] = array( + '#type' => 'select', + '#options' => $formatter_options, + '#default_value' => $display['type'], + ); + } + } + + // Groups. + $label_options = array( + 'above' => t('Above'), + 'hidden' => t(''), + ); + $options = array( + 'no_style' => t('no styling'), + 'simple' => t('simple'), + 'fieldset' => t('fieldset'), + 'fieldset_collapsible' => t('fieldset - collapsible'), + 'fieldset_collapsed' => t('fieldset - collapsed'), + 'hidden' => t(''), + ); + foreach ($groups as $name => $group) { + $defaults = $group['settings']['display']; + $weight = $group['weight']; + + $form[$name] = array( + 'human_name' => array('#markup' => check_plain($group['label'])), + 'weight' => array('#type' => 'value', '#value' => $weight), + ); + foreach ($build_modes as $build_mode => $label) { + $form[$name][$build_mode]['label'] = array( + '#type' => 'select', + '#options' => $label_options, + '#default_value' => isset($defaults[$key]['label']) ? $defaults[$build_mode]['label'] : 'above', + ); + $form[$name][$build_mode]['format'] = array( + '#type' => 'select', + '#options' => $options, + '#default_value' => isset($defaults[$key]['format']) ? $defaults[$build_mode]['format'] : 'fieldset', + ); + } + foreach ($group['fields'] as $field_name => $field) { + $form[$field_name]['parent']['#value'] = $name; + } + } + + $form['submit'] = array('#type' => 'submit', '#value' => t('Save')); + return $form; +} + + +/** + * Theme preprocess function for field_ui-display-overview-form.tpl.php. + */ +function template_preprocess_field_ui_display_overview_form(&$vars) { + $form = &$vars['form']; + + $contexts_selector = $form['#contexts']; + $vars['basic'] = $contexts_selector == 'basic'; + + $vars['contexts'] = field_ui_build_modes_tabs(field_info_bundle_entity($form['#bundle']), $contexts_selector); + + // @todo abstract texts over bundles / entity types + switch ($form['#bundle']) { + case 'user': + $help = t('Configure how user fields and field labels should be displayed.'); + break; + + default: + if ($contexts_selector == 'basic') { + $help = t("Configure how this content type's fields and field labels should be displayed when it's viewed in teaser and full-page mode."); + } + else { + $help = t("Configure how this content type's fields should be displayed when it's rendered in the following contexts."); + } + } + + $vars['help'] = $help; + + $order = _field_ui_overview_order($form, $form['#fields'], $form['#groups']); + if (empty($order)) { + $vars['rows'] = array(); + $vars['submit'] = ''; + return; + } + $rows = array(); + foreach ($order as $key) { + $element = &$form[$key]; + $row = new stdClass(); + foreach (element_children($element) as $child) { + if (array_key_exists('label', $element[$child])) { + $row->{$child}->label = drupal_render($element[$child]['label']); + $row->{$child}->type = drupal_render($element[$child]['type']); + } + else { + $row->{$child} = drupal_render($element[$child]); + } + } + $row->label_class = in_array($key, $form['#groups']) ? 'label-group' : 'label-field'; + $row->indentation = theme('indentation', isset($element['#depth']) ? $element['#depth'] : 0); + $rows[] = $row; + } + + $vars['rows'] = $rows; + $vars['submit'] = drupal_render_children($form); +} + +/** + * Submit handler for the display overview form. + */ +function field_ui_display_overview_form_submit($form, &$form_state) { + module_load_include('inc', 'field', 'includes/field.crud'); + $form_values = $form_state['values']; + foreach ($form_values as $key => $values) { + // Groups are handled in fieldgroup_display_overview_form_submit(). + if (in_array($key, $form['#fields'])) { + $instance = field_info_instance($key, $form['#bundle']); + unset($values['weight'], $values['parent']); + $instance['display'] = array_merge($instance['display'], $values); + field_update_instance($instance); + } + } + drupal_set_message(t('Your settings have been saved.')); +} + +/** + * Return an array of field_type options. + */ +function field_ui_field_type_options() { + static $options; + + if (!isset($options)) { + $options = array(); + $field_types = field_info_field_types(); + $field_type_options = array(); + foreach ($field_types as $name => $field_type) { + // Skip field types which have no widget types. + if (field_ui_widget_type_options($name)) { + $options[$name] = $field_type['label']; + } + } + asort($options); + } + return $options; +} + +/** + * Return an array of widget type options for a field type. + * + * If no field type is provided, returns a nested array of + * all widget types, keyed by field type human name. + */ +function field_ui_widget_type_options($field_type = NULL, $by_label = FALSE) { + static $options; + + if (!isset($options)) { + $options = array(); + foreach (field_info_widget_types() as $name => $widget_type) { + foreach ($widget_type['field types'] as $widget_field_type) { + $options[$widget_field_type][$name] = $widget_type['label']; + } + } + } + + if ($field_type) { + return !empty($options[$field_type]) ? $options[$field_type] : array(); + } + elseif ($by_label) { + $field_types = field_info_field_types(); + $options_by_label = array(); + foreach ($options as $field_type => $widgets) { + $options_by_label[$field_types[$field_type]['label']] = $widgets; + } + return $options_by_label; + } + else { + return $options; + } +} + +/** + * Return an array of formatter options for a field type. + * + * If no field type is provided, returns a nested array of + * all formatters, keyed by field type. + */ +function field_ui_formatter_options($field_type = NULL) { + static $options; + + if (!isset($options)) { + $options = array(); + foreach (field_info_formatter_types() as $name => $formatter) { + foreach ($formatter['field types'] as $formatter_field_type) { + $options[$formatter_field_type][$name] = $formatter['label']; + } + } + } + + if ($field_type) { + return !empty($options[$field_type]) ? $options[$field_type] : array(); + } + else { + return $options; + } +} + +/** + * Return an array of existing field to be added to a bundle. + */ +function field_ui_existing_field_options($bundle) { + $options = array(); + $field_types = field_info_field_types(); + foreach (field_info_instances() as $bundle_name => $instances) { + // No need to look in the currenht bundle. + if ($bundle_name != $bundle) { + foreach ($instances as $instance) { + $field = field_info_field($instance['field_name']); + // Don't show locked fields, or fields that are already in the current + // bundle. + if (empty($field['locked']) && !field_info_instance($field['field_name'], $bundle)) { + $text = t('@type: @field (@label)', array('@type' => $field_types[$field['type']]['label'], '@label' => t($instance['label']), '@field' => $instance['field_name'])); + $options[$instance['field_name']] = (drupal_strlen($text) > 80) ? truncate_utf8($text, 77) . '...' : $text; + } + } + } + } + // Sort the list by type, then by field name, then by label. + asort($options); + return $options; +} + +/** + * Helper function to determine if a field has data in the database. + */ +function field_ui_field_has_data($field_name) { + $has_data = FALSE; + if (drupal_function_exists('field_db_tablename')) { + $table = field_db_tablename($field_name); + if (db_table_exists($table)) { + $query = db_select($table, 'f', array('fetch' => PDO::FETCH_ASSOC)); + $query->fields('f', array('entity_id')); + $query->condition('f.deleted', 0); + $results = $query->execute()->fetchAssoc(); + if (!empty($results)) { + $has_data = TRUE; + } + } + } + return $has_data; +} + +/** + * Menu callback; presents the field settings edit page. + */ +function field_ui_field_settings_form(&$form_state, $obj_type, $bundle, $field_name) { + $bundle = field_attach_extract_bundle($obj_type, $bundle); + + $instance = field_info_field($field_name, $bundle); + $field = field_info_field($field_name); + + // When a field is first created, we have to get data from the db. + if (!isset($instance['label'])) { + $instance = field_read_instance($field_name, $bundle); + $field = field_read_field($field_name); + } + + $field_type = field_info_field_types($field['type']); + + $info_function = $field['module'] . '_field_info'; + $info = $info_function(); + $description = '

' . $info[$field['type']]['label'] . ': '; + $description .= $info[$field['type']]['description'] . '

'; + $form['#prefix'] = '
' . $description . '
'; + + $description = '

' . 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_name); + 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_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']; + + // Don't allow changes to fields with data. + if (field_ui_field_has_data($field_values['field_name'])) { + return; + } + + // Merge incoming form values into the existing field. + $field = field_info_field($field_values['field_name']); + // 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($form_state, $bundle); +} + +/** + * Menu callback; select a widget for the field. + */ +function field_ui_widget_type_form(&$form_state, $obj_type, $bundle, $field_name) { + $bundle = field_attach_extract_bundle($obj_type, $bundle); + + $instance = field_read_instance($field_name, $bundle); + $field = field_read_field($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']; + + if (drupal_function_exists('field_update_instance')) { + $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']))); + } + } + else { + 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($form_state, $bundle); +} + +/** + * Menu callback; present a form for removing a field from a content type. + */ +function field_ui_field_remove_form(&$form_state, $obj_type, $bundle, $field_name) { + $bundle = field_attach_extract_bundle($obj_type, $bundle); + + $field = field_info_field($field_name); + $instance = field_info_instance($field_name, $bundle); + $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_name, + ); + + $output = confirm_form($form, + t('Are you sure you want to remove 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('Remove'), t('Cancel'), + 'confirm' + ); + + if ($field['locked']) { + unset($output['actions']['submit']); + $output['description']['#markup'] = t('This field is locked and cannot be removed.'); + } + + return $output; +} + +/** + * Remove a field from a content type. + */ +function field_ui_field_remove_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 ($field['locked']) { + return; + } + + $error = FALSE; + if (!empty($bundle) && $field && $form_values['confirm'] && drupal_function_exists('field_delete_instance')) { + try { + field_delete_instance($form_values['field_name'], $form_values['bundle']); + } + catch (FieldException $e) { + $error = TRUE; + } + } + else { + $error = TRUE; + } + if ($error) { + drupal_set_message(t('There was a problem removing the %field from %type.', array('%field' => $instance['label'], '%type' => $bundle_label))); + } + else { + drupal_set_message(t('The field %field has been removed from %type.', array('%field' => $instance['label'], '%type' => $bundle_label))); + } + $form_state['redirect'] = field_ui_next_destination($form_state, $bundle); +} + +/** + * Menu callback; presents the field instance edit page. + */ +function field_ui_field_edit_form(&$form_state, $obj_type, $bundle, $field_name) { + $bundle = field_attach_extract_bundle($obj_type, $bundle); + + $output = ''; + $instance = field_read_instance($field_name, $bundle); + $field = field_read_field($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'])), + ); + + // Build the non-configurable instance values. + $form['instance']['field_name'] = array('#type' => 'value', '#value' => $field_name); + $form['instance']['bundle'] = array('#type' => 'value', '#value' => $bundle); + $form['instance']['weight'] = array('#type' => 'value', '#value' => !empty($instance['weight']) ? $instance['weight'] : 0); + + // Build the configurable instance values. + $form['instance']['label'] = array( + '#type' => 'textfield', + '#title' => t('Label'), + '#default_value' => !empty($instance['label']) ? $instance['label'] : $field_name, + '#required' => TRUE, + '#description' => t('The human-readable label for this field.'), + ); + $form['instance']['required'] = array( + '#type' => 'checkbox', + '#title' => t('Required'), + '#default_value' => !empty($instance['required']), + '#description' => t('Check if a value must be provided.'), + ); + + $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, + ); + + // Build the widget component of the instance. + $form['instance']['widget'] = array( + 'type' => array('#type' => 'value', '#value' => $instance['widget']['type']), + 'module' => array('#type' => 'value', '#value' => $widget_type['module']), + '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. + // Field modules that want to provide their own default value handling + // or provide no default values should populate $instance['default_value_function'] + // with some function name to make sure Field UI doesn't try to intervene. + if (empty($instance['default_value_function']) || $instance['default_value_function'] == 'field_ui_field_default_value') { + // Store the original default value for use in programmed forms. + // Set '#default_value' instead of '#value' so programmed values + // can override whatever we set here. + // @todo this may not apply now that default values handling has changed. + $default_value = field_ui_get_setting('default_value', 'instance', $field, $instance); + $default_value_php = field_ui_get_setting('default_value_php', 'instance', $field, $instance); + $form['instance']['default_value'] = array( + '#type' => 'value', + '#default_value' => $default_value, + ); + $form['instance']['default_value_php'] = array( + '#type' => 'value', + '#default_value' => $default_value_php, + ); + $form['instance']['default_value_function'] = array( + '#type' => 'value', + '#default_value' => 'field_ui_field_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 = '

' . $info['label'] . ': '; + $description .= $info['description'] . '

'; + $form['#prefix'] = '
' . $description . '
'; + + $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; +} + +/** + * Build default value fieldset. + */ +function field_ui_default_value_widget($field, $instance, &$form, &$form_state) { + $default_value = field_ui_get_setting('default_value', 'instance', $field, $instance); + $default_value_php = field_ui_get_setting('default_value_php', 'instance', $field, $instance); + $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['label'] = t('Default value'); + $instance['description'] = ''; + $items = $default_value; + // Set up form info that the default value widget will need to find in the form. + $form['#fields'] = array( + $field['field_name'] => array( + 'field' => $field, + 'instance' => $instance, + ), + ); + drupal_function_exists('field_default_form'); + // @todo allow multiple values (still requires some 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']); + + // Advanced: PHP code. + $form['instance']['default_value_widget']['advanced_options'] = array( + '#type' => 'fieldset', + '#title' => t('PHP code'), + '#collapsible' => TRUE, + '#collapsed' => empty($default_value_php), + ); + + if (user_access('use PHP input for field settings')) { + foreach (array_keys($field['columns']) as $column) { + $columns[] = t("'@column' => value for @column", array('@column' => $column)); + } + $sample = t("return array(\n 0 => array(@columns),\n // You'll usually want to stop here. Provide more values\n // if you want your 'default value' to be multi-valued:\n 1 => array(@columns),\n 2 => ...\n);", array('@columns' => implode(', ', $columns))); + + $form['instance']['default_value_widget']['advanced_options']['default_value_php'] = array( + '#type' => 'textarea', + '#title' => t('Code'), + '#default_value' => isset($default_value_php) ? $default_value_php : '', + '#rows' => 6, + '#tree' => TRUE, + '#description' => t('Advanced usage only: PHP code that returns a default value. Should not include <?php ?> delimiters. If this field is filled out, the value returned by this code will override any value specified above. Expected format:
!sample
To figure out the expected format, you can use the devel load tab provided by devel module on a %type content page. The option to embed PHP code in the field definition is provided for backwards compatibility and could be deprecated in the future. It is strongly recommended that you move this code to a custom function in a custom module and simply identify the custom function in the box above!', array( + '!sample' => $sample, + '@link_devel' => 'http://www.drupal.org/project/devel', + '%type' => $instance['bundle'])), + ); + } + else { + $form['instance']['default_value_widget']['advanced_options']['markup_default_value_php'] = array( + '#type' => 'item', + '#title' => t('Code'), + '#value' => !empty($default_value_php) ? '' . check_plain($default_value_php) . '' : t('<none>'), + '#description' => empty($default_value_php) ? t("You're not allowed to input PHP code.") : t('This PHP code was set by an administrator and will override any value specified above.'), + ); + } +} + +/** + * 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 (isset($form_values['instance']['widget']['default_value_php']) && + ($php = trim($form_values['instance']['widget']['default_value_php']))) { + ob_start(); + $return = eval($php); + ob_end_clean(); + if (!is_array($return)) { + $error = TRUE; + } + else { + foreach ($return as $item) { + if (!is_array($item)) { + $error = TRUE; + break; + } + } + } + if ($error) { + foreach (array_keys($field['columns']) as $column) { + $columns[] = t("'@column' => value for @column", array('@column' => $column)); + } + $sample = t("return array(\n 0 => array(@columns),\n You'll usually want to stop here. Provide more values\n if you want your 'default value' to be multi-valued:\n 1 => array(@columns),\n 2 => ...\n);", array('@columns' => implode(', ', $columns))); + + form_set_error('default_value_php', t('The default value PHP code returned an incorrect value.
Expected format:
!sample
Returned value: @value', array( + '!sample' => $sample, + '@value' => print_r($return, TRUE)))); + return; + } + else { + $default_value = $return; + $is_code = TRUE; + form_set_value(array('#parents' => array('instance', 'widget', 'default_value_php')), $php, $form_state); + form_set_value(array('#parents' => array('instance', 'widget', 'default_value')), array(), $form_state); + } + } + elseif (!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_php')), '', $form_state); + form_set_value(array('#parents' => array('instance', 'widget', 'default_value')), $default_value, $form_state); + } + if (isset($default_value)) { + // @todo update this code for D7. + $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)) { + if (trim($form_values['default_value_php'])) { + form_set_error('default_value_php', t("The PHP code for 'default value' returned @value, which is invalid.", array( + '@value' => print_r($default_value, TRUE)))); + } + else { + 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); + + field_ui_set_setting('default_value', 'instance', $instance['default_value_widget'][$instance['field_name']], $field, $instance); + field_ui_set_setting('default_value_php', 'instance', $instance['default_value_widget']['advanced_options']['default_value_php'], $field, $instance); + + // Make sure the default value widget does not get stored. + if (isset($instance['default_value_widget'])) { + unset($instance['default_value_widget']); + unset($instance['default_value']); + unset($instance['default_value_php']); + } + + // @todo move this to text.module and number.module ? + // Make sure the allowed values fieldset does not get stored. + if (isset($instance['settings']['allowed_values_fieldset'])) { + $instance['settings'] += $instance['settings']['allowed_values_fieldset']; + unset($instance['settings']['allowed_values_fieldset']); + } + + // 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($form_state, $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); +} + +function field_ui_next_destination(&$form_state, $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'; + } +} + +/** + * 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(&$form_state, $obj_type, $bundle) { + $bundle = field_attach_extract_bundle($obj_type, $bundle); + + menu_rebuild(); + $destinations = field_ui_next_destination($form_state, $bundle); + if (is_array($destinations)) { + $path = array_shift($destinations); + drupal_goto($path, $destinations); + } + else { + drupal_goto($destinations); + } +} + +/** + * Helper function to order fields and groups when theming (preprocessing) + * overview forms. + * + * The $form is passed by reference because we assign depths as parenting + * relationships are sorted out. + */ +function _field_ui_overview_order(&$form, $field_rows, $group_rows) { + // Put weight and parenting values into a $dummy render structure + // and let drupal_render figure out the corresponding row order. + $dummy = array(); + // Group rows: account for weight. + if (module_exists('fieldgroup')) { + foreach ($group_rows as $name) { + $dummy[$name] = array('#markup' => $name . ' ', '#weight' => $form[$name]['weight']['#value']); + } + } + // Field rows : account for weight and parenting. + foreach ($field_rows as $name) { + $dummy[$name] = array('#markup' => $name . ' ', '#type' => 'markup', '#weight' => $form[$name]['weight']['#value']); + if (module_exists('fieldgroup')) { + if ($parent = $form[$name]['parent']['#value']) { + $form[$name]['#depth'] = 1; + $dummy[$parent][$name] = $dummy[$name]; + unset($dummy[$name]); + } + } + } + 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']))); + } +} Index: modules/field_ui/text.inc =================================================================== RCS file: modules/field_ui/text.inc diff -N modules/field_ui/text.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/field_ui/text.inc 15 Jul 2009 22:09:36 -0000 @@ -0,0 +1,69 @@ + 'textfield', + '#title' => t('Maximum length'), + '#default_value' => !empty($settings['max_length']) && is_numeric($settings['max_length']) ? $settings['max_length'] : $defaults['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; +} + +/** + * Implement hook_field_instance_settings_form(). + */ +function text_field_instance_settings_form($field, $instance) { + $settings = $instance['settings']; + $options = array(0 => t('Plain text'), 1 => t('Filtered text (user selects input format)')); + $form['text_processing'] = array( + '#type' => 'radios', + '#title' => t('Text processing'), + '#default_value' => !empty($settings['text_processing']) && is_numeric($settings['text_processing']) ? $settings['text_processing'] : $defaults['text_processing'], + '#options' => $options, + ); + if ($field['type'] == 'text_with_summary') { + $form['display_summary'] = array( + '#type' => 'select', + '#options' => array(0 => t('No'), 1 => t('Yes')), + '#title' => t('Display summary'), + '#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; +} + +/** + * 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'], + '#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; +} Index: profiles/default/default.info =================================================================== RCS file: /cvs/drupal/drupal/profiles/default/default.info,v retrieving revision 1.1 diff -u -p -r1.1 default.info --- profiles/default/default.info 15 Jul 2009 02:08:41 -0000 1.1 +++ profiles/default/default.info 15 Jul 2009 22:09:36 -0000 @@ -14,3 +14,4 @@ dependencies[] = taxonomy dependencies[] = dblog dependencies[] = search dependencies[] = toolbar +dependencies[] = field_ui