summaryrefslogtreecommitdiff
path: root/modules/field_ui
diff options
context:
space:
mode:
Diffstat (limited to 'modules/field_ui')
-rw-r--r--modules/field_ui/field_ui-display-overview-form.tpl.php55
-rw-r--r--modules/field_ui/field_ui-field-overview-form.tpl.php94
-rw-r--r--modules/field_ui/field_ui-rtl.css8
-rw-r--r--modules/field_ui/field_ui.admin.inc1398
-rw-r--r--modules/field_ui/field_ui.api.php135
-rw-r--r--modules/field_ui/field_ui.css18
-rw-r--r--modules/field_ui/field_ui.info8
-rw-r--r--modules/field_ui/field_ui.js84
-rw-r--r--modules/field_ui/field_ui.module327
9 files changed, 2127 insertions, 0 deletions
diff --git a/modules/field_ui/field_ui-display-overview-form.tpl.php b/modules/field_ui/field_ui-display-overview-form.tpl.php
new file mode 100644
index 000000000..d4c2d903f
--- /dev/null
+++ b/modules/field_ui/field_ui-display-overview-form.tpl.php
@@ -0,0 +1,55 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Default theme implementation to configure field display settings.
+ *
+ * Available variables:
+ *
+ * - $form: The complete form for the field display settings.
+ * - $contexts: An associative array of the available contexts for these fields.
+ * On the node field display settings this defaults to including "teaser" and
+ * "full" as the available contexts.
+ * - $rows: The field display settings form broken down into rendered rows for
+ * printing as a table.
+ * - $submit: The rendered submit button for this form.
+ *
+ * @see field_ui_display_overview_form()
+ * @see template_preprocess_field_ui_display_overview_form()
+ */
+?>
+<?php if ($rows): ?>
+ <table id="field-display-overview" class="sticky-enabled">
+ <thead>
+ <tr>
+ <th>&nbsp;</th>
+ <?php foreach ($contexts as $key => $value): ?>
+ <th colspan="2"><?php print $value; ?>
+ <?php endforeach; ?>
+ </tr>
+ <tr>
+ <th><?php print t('Field'); ?></th>
+ <?php foreach ($contexts as $key => $value): ?>
+ <th><?php print t('Label'); ?></th>
+ <th><?php print t('Format'); ?></th>
+ <?php endforeach; ?>
+ </tr>
+ </thead>
+ <tbody>
+ <?php
+ $count = 0;
+ foreach ($rows as $row): ?>
+ <tr class="<?php print $count % 2 == 0 ? 'odd' : 'even'; ?>">
+ <td><span class="<?php print $row->label_class; ?>"><?php print $row->human_name; ?></span></td>
+ <?php foreach ($contexts as $context => $title): ?>
+ <td><?php print $row->{$context}->label; ?></td>
+ <td><?php print $row->{$context}->type; ?></td>
+ <?php endforeach; ?>
+ </tr>
+ <?php $count++;
+ endforeach; ?>
+ </tbody>
+ </table>
+ <?php print $submit; ?>
+<?php endif; ?>
diff --git a/modules/field_ui/field_ui-field-overview-form.tpl.php b/modules/field_ui/field_ui-field-overview-form.tpl.php
new file mode 100644
index 000000000..178e51513
--- /dev/null
+++ b/modules/field_ui/field_ui-field-overview-form.tpl.php
@@ -0,0 +1,94 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Default theme implementation to configure field settings.
+ *
+ * Available variables:
+ *
+ * - $form: The complete overview form for the field settings.
+ * - $contexts: An associative array of the available contexts for these fields.
+ * On the node field display settings this defaults to including "teaser" and
+ * "full" as the available contexts.
+ * - $rows: The field overview form broken down into rendered rows for printing
+ * as a table.
+ * - $submit: The rendered submit button for this form.
+ *
+ * @see field_ui_field_overview_form()
+ * @see template_preprocess_field_ui_field_overview_form()
+ */
+?>
+<table id="field-overview" class="sticky-enabled">
+ <thead>
+ <tr>
+ <th><?php print t('Label'); ?></th>
+ <th><?php print t('Weight'); ?></th>
+ <th><?php print t('Name'); ?></th>
+ <th><?php print t('Field'); ?></th>
+ <th><?php print t('Widget'); ?></th>
+ <th colspan="2"><?php print t('Operations'); ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php
+ $count = 0;
+ foreach ($rows as $row): ?>
+ <tr class="<?php print $count % 2 == 0 ? 'odd' : 'even'; ?> <?php print $row->class ?>">
+ <?php
+ switch ($row->row_type):
+ case 'field': ?>
+ <td>
+ <span class="<?php print $row->label_class; ?>"><?php print $row->label; ?></span>
+ </td>
+ <td><?php print $row->weight . $row->hidden_name; ?></td>
+ <td><?php print $row->field_name; ?></td>
+ <td><?php print $row->type; ?></td>
+ <td><?php print $row->widget_type; ?></td>
+ <td><?php print $row->edit; ?></td>
+ <td><?php print $row->delete; ?></td>
+ <?php break;
+
+ case 'extra': ?>
+ <td>
+ <span class="<?php print $row->label_class; ?>"><?php print $row->label; ?></span>
+ </td>
+ <td><?php print $row->weight . $row->hidden_name; ?></td>
+ <td><?php print $row->name; ?></td>
+ <td colspan="2"><?php print $row->description; ?></td>
+ <td><?php print $row->edit; ?></td>
+ <td><?php print $row->delete; ?></td>
+ <?php break;
+
+ case 'add_new_field': ?>
+ <td>
+ <div class="<?php print $row->label_class; ?>">
+ <div class="new"><?php print t('Add new field'); ?></div>
+ <?php print $row->label; ?>
+ </div>
+ </td>
+ <td><div class="new">&nbsp;</div><?php print $row->weight . $row->hidden_name; ?></td>
+ <td colspan="2"><div class="new">&nbsp;</div><?php print $row->field_name; ?></td>
+ <td><div class="new">&nbsp;</div><?php print $row->type; ?></td>
+ <td colspan="2"><div class="new">&nbsp;</div><?php print $row->widget_type; ?></td>
+ <?php break;
+
+ case 'add_existing_field': ?>
+ <td>
+ <div class="<?php print $row->label_class; ?>">
+ <div class="new"><?php print t('Add existing field'); ?></div>
+ <?php print $row->label; ?>
+ </div>
+ </td>
+ <td><div class="new">&nbsp;</div><?php print $row->weight . $row->hidden_name; ?></td>
+ <td colspan="3"><div class="new">&nbsp;</div><?php print $row->field_name; ?></td>
+ <td colspan="2"><div class="new">&nbsp;</div><?php print $row->widget_type; ?></td>
+ <?php break;
+ endswitch; ?>
+ </tr>
+ <?php $count++;
+ endforeach; ?>
+ </tbody>
+</table>
+
+<?php print $submit; ?>
diff --git a/modules/field_ui/field_ui-rtl.css b/modules/field_ui/field_ui-rtl.css
new file mode 100644
index 000000000..6823e1e0b
--- /dev/null
+++ b/modules/field_ui/field_ui-rtl.css
@@ -0,0 +1,8 @@
+/* $Id$ */
+
+/* 'Manage fields' overview */
+#field-overview .label-add-new-field,
+#field-overview .label-add-existing-field {
+ float: right;
+}
+
diff --git a/modules/field_ui/field_ui.admin.inc b/modules/field_ui/field_ui.admin.inc
new file mode 100644
index 000000000..caaa2d062
--- /dev/null
+++ b/modules/field_ui/field_ui.admin.inc
@@ -0,0 +1,1398 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Administrative interface for custom field type creation.
+ */
+
+/**
+ * Menu callback; lists all defined fields for quick reference.
+ */
+function field_ui_fields_list() {
+ $instances = field_info_instances();
+ $field_types = field_info_field_types();
+ $bundles = field_info_bundles();
+ $header = array(t('Field name'), t('Field type'), t('Used in'));
+ $rows = array();
+ foreach ($instances as $bundle => $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) {
+ $inactive_instances = field_ui_inactive_instances($bundle);
+ if (!empty($inactive_instances)) {
+ $field_types = field_info_field_types();
+ $widget_types = field_info_widget_types();
+
+ foreach ($inactive_instances as $field_name => $instance) {
+ $list[] = t('%field (@field_name) field requires the %widget_type widget provided by %widget_module module', array(
+ '%field' => $instance['label'],
+ '@field_name' => $instance['field_name'],
+ '%widget_type' => array_key_exists($instance['widget']['type'], $widget_types) ? $widget_types[$instance['widget']['type']]['label'] : $instance['widget']['type'],
+ '%widget_module' => $instance['widget']['module'],
+ ));
+ }
+ drupal_set_message(t('Inactive fields are not shown unless their providing modules are enabled. The following fields are not enabled: !list', array('!list' => theme('item_list', $list))), 'error');
+ }
+}
+
+/**
+ * Menu callback; listing of fields for a content type.
+ *
+ * Allows fields and pseudo-fields to be re-ordered.
+ */
+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_extra_fields($bundle);
+
+ // Store each default weight so that we can add the 'add new' rows after them.
+ $weights = array();
+
+ $form = array(
+ '#tree' => TRUE,
+ '#bundle' => $bundle,
+ '#fields' => array_keys($instances),
+ '#extra' => array_keys($extra),
+ '#field_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['widget']['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.')))),
+ ),
+ 'edit' => array(
+ '#markup' => l(t('edit'), $admin_field_path, array('attributes' => array('title' => t('Edit instance settings.')))),
+ ),
+ 'delete' => array(
+ '#markup' => l(t('delete'), $admin_field_path . '/delete', array('attributes' => array('title' => t('Delete instance.')))),
+ ),
+ 'weight' => array(
+ '#type' => 'textfield',
+ '#default_value' => $weight,
+ '#size' => 3,
+ ),
+ 'hidden_name' => array(
+ '#type' => 'hidden',
+ '#default_value' => $instance['field_name'],
+ ),
+ '#row_type' => 'field',
+ );
+
+ if (!empty($instance['locked'])) {
+ $form[$name]['edit'] = array('#value' => t('Locked'));
+ $form[$name]['delete'] = array();
+ $form[$name]['#disabled_row'] = TRUE;
+ }
+ $form['#field_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']),
+ ),
+ 'name' => array(
+ '#markup' => $name,
+ ),
+ 'description' => array(
+ '#markup' => isset($extra[$name]['description']) ? $extra[$name]['description'] : '',
+ ),
+ 'weight' => array(
+ '#type' => 'textfield',
+ '#default_value' => $weight,
+ '#size' => 3,
+ ),
+ 'edit' => array(
+ '#markup' => isset($extra[$name]['edit']) ? $extra[$name]['edit'] : '',
+ ),
+ 'delete' => array(
+ '#markup' => isset($extra[$name]['delete']) ? $extra[$name]['delete'] : '',
+ ),
+ 'hidden_name' => array(
+ '#type' => 'hidden',
+ '#default_value' => $name,
+ ),
+ '#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' => '<span dir="ltr">field_',
+ '#field_suffix' => '</span>&lrm;',
+ '#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,
+ ),
+ 'hidden_name' => array(
+ '#type' => 'hidden',
+ '#default_value' => $name,
+ ),
+ '#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,
+ ),
+ 'hidden_name' => array(
+ '#type' => 'hidden',
+ '#default_value' => $name,
+ ),
+ '#add_new' => TRUE,
+ '#row_type' => 'add_existing_field',
+ );
+ $form['#field_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', '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');
+
+ $order = _field_ui_overview_order($form, $form['#field_rows']);
+ $rows = array();
+
+ // Identify the 'new item' keys in the form.
+ $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];
+ $row = new stdClass();
+
+ // Add target classes for the tabledrag behavior.
+ $element['weight']['#attributes']['class'] = 'field-weight';
+ $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->class = 'draggable';
+ $row->class .= isset($element['#add_new']) ? ' add-new' : '';
+ $row->class .= isset($element['#disabled_row']) ? ' menu-disabled' : '';
+
+ $rows[] = $row;
+ array_shift($order);
+ }
+ $vars['rows'] = $rows;
+ $vars['submit'] = drupal_render_children($form);
+}
+
+/**
+ * Validate handler for the field overview 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']))) {
+ // Missing label.
+ if (!$field['label']) {
+ form_set_error('_add_new_field][label', t('Add new field: you need to provide a label.'));
+ }
+
+ // Missing 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_info_fields().
+ $fields = field_read_fields(array('field_name' => $field_name), array('include_inactive' => TRUE));
+ if ($fields) {
+ form_set_error('_add_new_field][field_name', t('Add new field: the field name %field_name already exists.', array('%field_name' => $field_name)));
+ }
+ }
+
+ // Missing field type.
+ if (!$field['type']) {
+ form_set_error('_add_new_field][type', t('Add new field: you need to select a field type.'));
+ }
+
+ // Missing 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']))) {
+ // Missing label.
+ if (!$field['label']) {
+ form_set_error('_add_existing_field][label', t('Add existing field: you need to provide a label.'));
+ }
+
+ // Missing existing field name.
+ if (!$field['field_name']) {
+ form_set_error('_add_existing_field][field_name', t('Add existing field: you need to select a field.'));
+ }
+
+ // Missing 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.'));
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Submit handler for the field overview form.
+ */
+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'])) {
+ $instance = field_read_instance($key, $bundle);
+ $instance['widget']['weight'] = $values['weight'];
+ foreach($instance['display'] as $build_mode => $display) {
+ $instance['display'][$build_mode]['weight'] = $values['weight'];
+ }
+ field_update_instance($instance);
+ }
+ 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'],
+ 'widget' => array(
+ 'type' => $values['widget_type'],
+ 'weight' => $values['weight'],
+ ),
+ );
+
+ // Create the field and instance.
+ try {
+ field_create_field($field);
+ field_create_instance($instance);
+
+ $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 any additional submit handlers.
+ $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' => $instance['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' => $values['label'])));
+ }
+ else {
+ $instance = array(
+ 'field_name' => $field['field_name'],
+ 'bundle' => $bundle,
+ 'label' => $values['label'],
+ 'widget' => array(
+ 'type' => $values['widget_type'],
+ 'weight' => $values['weight'],
+ ),
+ );
+
+ try {
+ field_create_instance($instance);
+ $destinations[] = $admin_path . '/fields/refresh';
+ $destinations[] = $admin_path . '/fields/' . $instance['field_name'] . '/edit';
+ // Store new field information for any additional submit handlers.
+ $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' => $instance['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.
+ *
+ * This form includes form widgets to select which fields appear in teaser and
+ * full build modes, 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();
+ $build_modes = field_ui_build_modes_tabs($entity, $build_modes_selector);
+
+ $form = array(
+ '#tree' => TRUE,
+ '#bundle' => $bundle,
+ '#fields' => array_keys($instances),
+ '#contexts' => $build_modes_selector,
+ );
+
+ if (empty($instances)) {
+ drupal_set_message(t('There are no fields yet added. You can add new fields on the <a href="@link">Manage fields</a> page.', array('@link' => url($admin_path . '/fields'))), 'warning');
+ return $form;
+ }
+
+ // Fields.
+ $label_options = array(
+ 'above' => t('Above'),
+ 'inline' => t('Inline'),
+ 'hidden' => t('<Hidden>'),
+ );
+ foreach ($instances as $name => $instance) {
+ $field = field_info_field($instance['field_name']);
+ $weight = $instance['widget']['weight'];
+
+ $form[$name] = array(
+ 'human_name' => array('#markup' => check_plain($instance['label'])),
+ 'weight' => array('#type' => 'value', '#value' => $weight),
+ );
+ $defaults = $instance['display'];
+
+ $formatter_options = field_ui_formatter_options($field['type']);
+ $formatter_options['hidden'] = t('<Hidden>');
+ 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'],
+ );
+ }
+ }
+
+ $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['contexts'] = field_ui_build_modes_tabs(field_info_bundle_entity($form['#bundle']), $contexts_selector);
+
+ $order = _field_ui_overview_order($form, $form['#fields']);
+ 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 = 'label-field';
+ $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) {
+ if (in_array($key, $form['#fields'])) {
+ $instance = field_info_instance($key, $form['#bundle']);
+ unset($values['weight']);
+ $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() {
+ $options = &drupal_static(__FUNCTION__);
+
+ 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) {
+ $options = &drupal_static(__FUNCTION__);
+
+ if (!isset($options)) {
+ $options = array();
+ $field_types = field_info_field_types();
+ foreach (field_info_widget_types() as $name => $widget_type) {
+ foreach ($widget_type['field types'] as $widget_field_type) {
+ // Check that the field type exists.
+ if (isset($field_types[$widget_field_type])) {
+ $options[$widget_field_type][$name] = $widget_type['label'];
+ }
+ }
+ }
+ }
+
+ if (isset($field_type)) {
+ return !empty($options[$field_type]) ? $options[$field_type] : array();
+ }
+ if ($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;
+ }
+ 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) {
+ $options = &drupal_static(__FUNCTION__);
+
+ if (!isset($options)) {
+ $field_types = field_info_field_types();
+ $options = array();
+ foreach (field_info_formatter_types() as $name => $formatter) {
+ foreach ($formatter['field types'] as $formatter_field_type) {
+ // Check that the field type exists.
+ if (isset($field_types[$formatter_field_type])) {
+ $options[$formatter_field_type][$name] = $formatter['label'];
+ }
+ }
+ }
+ }
+
+ if ($field_type) {
+ return !empty($options[$field_type]) ? $options[$field_type] : array();
+ }
+ 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 current bundle.
+ if ($bundle_name != $bundle) {
+ foreach ($instances as $instance) {
+ $field = field_info_field($instance['field_name']);
+ // Don't show locked fields or fields 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 field name.
+ asort($options);
+ return $options;
+}
+
+/**
+ * Helper function to determine if a field has data in the database.
+ */
+function field_ui_field_has_data($field) {
+ $results = field_attach_query($field['id'], array(), 1);
+ return !empty($results);
+}
+
+/**
+ * Menu callback; presents the field settings edit page.
+ */
+function field_ui_field_settings_form(&$form_state, $obj_type, $bundle, $instance) {
+ $bundle = field_attach_extract_bundle($obj_type, $bundle);
+ $field = field_info_field($instance['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['field_name'], $bundle);
+ $field = field_read_field($field['field_name']);
+ }
+
+ $field_type = field_info_field_types($field['type']);
+
+ $info_function = $field['module'] . '_field_info';
+ $info = $info_function();
+ $description = '<p><strong>' . $info[$field['type']]['label'] . ':</strong> ';
+ $description .= $info[$field['type']]['description'] . '</p>';
+ $form['#prefix'] = '<div class="description">' . $description . '</div>';
+
+ $description = '<p>' . 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'])) . '</p>';
+
+ // Create a form structure for the field values.
+ $form['field'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('%field field settings', array('%field' => $instance['label'])),
+ '#description' => $description,
+ '#tree' => TRUE,
+ );
+
+ // See if data already exists for this field.
+ // If so, prevent changes to the field settings.
+ $has_data = field_ui_field_has_data($field);
+ if ($has_data) {
+ $form['field']['#description'] = '<div class=error>' . t('There is data for this field in the database. The field settings can no longer be changed.' . '</div>') . $form['field']['#description'];
+ }
+
+ // Build the non-configurable field values.
+ $form['field']['field_name'] = array('#type' => 'value', '#value' => $field['field_name']);
+ $form['field']['type'] = array('#type' => 'value', '#value' => $field['type']);
+ $form['field']['module'] = array('#type' => 'value', '#value' => $field['module']);
+ $form['field']['active'] = array('#type' => 'value', '#value' => $field['active']);
+
+ // Add settings provided by the field module.
+ $form['field']['settings'] = array();
+ $additions = module_invoke($field_type['module'], 'field_settings_form', $field, $instance);
+ if (is_array($additions)) {
+ $form['field']['settings'] = $additions;
+ // @todo Filter this so only the settings that cannot be changed are shown
+ // in this form. For now, treating all settings as changeable, which means
+ // they show up here and in edit form.
+ }
+ if (empty($form['field']['settings'])) {
+ $form['field']['settings'] = array(
+ '#markup' => t('%field has no field settings.', array('%field' => $instance['label'])),
+ );
+ }
+ else {
+ foreach ($form['field']['settings'] as $key => $setting) {
+ if (substr($key, 0, 1) != '#') {
+ $form['field']['settings'][$key]['#disabled'] = $has_data;
+ }
+ }
+ }
+ $form['#bundle'] = $bundle;
+
+ $form['submit'] = array('#type' => 'submit', '#value' => t('Save field settings'));
+ return $form;
+}
+
+/**
+ * Save a field's settings after editing.
+ */
+function field_ui_field_settings_form_submit($form, &$form_state) {
+ $form_values = $form_state['values'];
+ $field_values = $form_values['field'];
+
+ // Merge incoming form values into the existing field.
+ $field = field_info_field($field_values['field_name']);
+
+ // Do not allow changes to fields with data.
+ if (field_ui_field_has_data($field)) {
+ return;
+ }
+
+ // Remove the 'bundles' element added by field_info_field.
+ // @todo This is ugly, there must be a better way.
+ unset($field['bundles']);
+ $bundle = $form['#bundle'];
+ $instance = field_info_instance($field['field_name'], $bundle);
+
+ // Update the field.
+ $field = array_merge($field, $field_values);
+ field_ui_update_field($field);
+
+ drupal_set_message(t('Updated field %label field settings.', array('%label' => $instance['label'])));
+ $form_state['redirect'] = field_ui_next_destination($bundle);
+}
+
+/**
+ * Menu callback; select a widget for the field.
+ */
+function field_ui_widget_type_form(&$form_state, $obj_type, $bundle, $instance) {
+ $bundle = field_attach_extract_bundle($obj_type, $bundle);
+ $field = field_read_field($instance['field_name']);
+
+ $field_type = field_info_field_types($field['type']);
+ $widget_type = field_info_widget_types($instance['widget']['type']);
+ $bundles = field_info_bundles();
+ $bundle_label = $bundles[$bundle]['label'];
+
+ $form['basic'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Change widget'),
+ );
+ $form['basic']['widget_type'] = array(
+ '#type' => 'select',
+ '#title' => t('Widget type'),
+ '#required' => TRUE,
+ '#options' => field_ui_widget_type_options($field['type']),
+ '#default_value' => $instance['widget']['type'],
+ '#description' => t('The type of form element you would like to present to the user when creating this field in the %type type.', array('%type' => $bundle_label)),
+ );
+
+ $form['#instance'] = $instance;
+ $form['submit'] = array('#type' => 'submit', '#value' => t('Continue'));
+
+ $form['#validate'] = array();
+ $form['#submit'] = array('field_ui_widget_type_form_submit');
+
+ return $form;
+}
+
+/**
+ * Submit the change in widget type.
+ */
+function field_ui_widget_type_form_submit($form, &$form_state) {
+ $form_values = $form_state['values'];
+ $instance = $form['#instance'];
+ $bundle = $instance['bundle'];
+
+ // Set the right module information.
+ $widget_type = field_info_widget_types($form_values['widget_type']);
+ $widget_module = $widget_type['module'];
+
+ $instance['widget']['type'] = $form_values['widget_type'];
+ $instance['widget']['module'] = $widget_module;
+ try {
+ field_update_instance($instance);
+ drupal_set_message(t('Changed the widget for field %label.', array('%label' => $instance['label'])));
+ }
+ catch (FieldException $e) {
+ drupal_set_message(t('There was a problem changing the widget for field %label.', array('%label' => $instance['label'])));
+ }
+
+ $form_state['redirect'] = field_ui_next_destination($bundle);
+}
+
+/**
+ * Menu callback; present a form for removing a field from a content type.
+ */
+function field_ui_field_delete_form(&$form_state, $obj_type, $bundle, $instance) {
+ $bundle = field_attach_extract_bundle($obj_type, $bundle);
+ $field = field_info_field($instance['field_name']);
+ $admin_path = _field_ui_bundle_admin_path($bundle);
+
+ $form['bundle'] = array('#type' => 'value', '#value' => $bundle);
+ $form['field_name'] = array('#type' => 'value', '#value' => $field['field_name']);
+
+ $output = confirm_form($form,
+ t('Are you sure you want to delete the field %field?', array('%field' => $instance['label'])),
+ $admin_path . '/fields',
+ t('If you have any content left in this field, it will be lost. This action cannot be undone.'),
+ t('Delete'), t('Cancel'),
+ 'confirm'
+ );
+
+ if ($field['locked']) {
+ unset($output['actions']['submit']);
+ $output['description']['#markup'] = t('This field is <strong>locked</strong> and cannot be deleted.');
+ }
+
+ return $output;
+}
+
+/**
+ * Remove a field from a content type.
+ */
+function field_ui_field_delete_form_submit($form, &$form_state) {
+ $form_values = $form_state['values'];
+ $field = field_info_field($form_values['field_name']);
+ $instance = field_info_instance($form_values['field_name'], $form_values['bundle']);
+ $bundles = field_info_bundles();
+ $bundle = $form_values['bundle'];
+ $bundle_label = $bundles[$bundle]['label'];
+
+ if (!empty($bundle) && $field && !$field['locked'] && $form_values['confirm']) {
+ field_delete_instance($field['field_name'], $bundle);
+ // Delete the field if that was the last instance.
+ if (count($field['bundles'] == 1)) {
+ field_delete_field($field['field_name']);
+ }
+ drupal_set_message(t('The field %field has been deleted from the %type content type.', array('%field' => $instance['label'], '%type' => $bundle_label)));
+ }
+ else {
+ drupal_set_message(t('There was a problem removing the %field from the %type content type.', array('%field' => $instance['label'], '%type' => $bundle_label)));
+ }
+
+ $admin_path = _field_ui_bundle_admin_path($bundle);
+ $destinations = array(
+ $admin_path . '/fields/refresh',
+ $admin_path . '/fields',
+ );
+ $form_state['redirect'] = field_ui_get_destinations($destinations);
+}
+
+/**
+ * Menu callback; presents the field instance edit page.
+ */
+function field_ui_field_edit_form(&$form_state, $obj_type, $bundle, $instance) {
+ $bundle = field_attach_extract_bundle($obj_type, $bundle);
+
+ $field = field_info_field($instance['field_name']);
+ $form['#field'] = $field;
+
+ if (!empty($field['locked'])) {
+ $form['locked'] = array(
+ '#markup' => t('The field %field is locked and cannot be edited.', array('%field' => $instance['label'])),
+ );
+ return $form;
+ }
+
+ $field_type = field_info_field_types($field['type']);
+ $widget_type = field_info_widget_types($instance['widget']['type']);
+ $bundles = field_info_bundles();
+
+ $title = isset($instance['label']) ? $instance['label'] : $instance['field_name'];
+ drupal_set_title(check_plain($title));
+
+ // Create a form structure for the instance values.
+ $form['instance'] = array(
+ '#tree' => TRUE,
+ '#type' => 'fieldset',
+ '#title' => t('%type settings', array('%type' => $bundles[$bundle]['label'])),
+ '#description' => t('These settings apply only to the %field field when used in the %type type.', array('%field' => $instance['label'], '%type' => $bundles[$bundle]['label'])),
+ '#pre_render' => array('field_ui_field_edit_instance_pre_render'),
+ );
+
+ // Build the non-configurable instance values.
+ $form['instance']['field_name'] = array(
+ '#type' => 'value',
+ '#value' => $instance['field_name'],
+ );
+ $form['instance']['bundle'] = array(
+ '#type' => 'value',
+ '#value' => $bundle,
+ );
+ $form['instance']['widget']['weight'] = array(
+ '#type' => 'value',
+ '#value' => !empty($instance['widget']['weight']) ? $instance['widget']['weight'] : 0,
+ );
+
+ // Build the configurable instance values.
+ $form['instance']['label'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Label'),
+ '#default_value' => !empty($instance['label']) ? $instance['label'] : $field['field_name'],
+ '#required' => TRUE,
+ '#description' => t('The human-readable label for this field.'),
+ '#weight' => -20,
+ );
+ $form['instance']['required'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Required'),
+ '#default_value' => !empty($instance['required']),
+ '#description' => t('Check if a value must be provided.'),
+ '#weight' => -10,
+ );
+
+ $form['instance']['description'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Help text'),
+ '#default_value' => !empty($instance['description']) ? $instance['description'] : '',
+ '#rows' => 5,
+ '#description' => t('Instructions to present to the user below this field on the editing form.<br />Allowed HTML tags: @tags', array('@tags' => _field_filter_xss_display_allowed_tags())),
+ '#required' => FALSE,
+ '#weight' => 0,
+ );
+
+ // Build the widget component of the instance.
+ $form['instance']['widget']['type'] = array(
+ '#type' => 'value',
+ '#value' => $instance['widget']['type'],
+ );
+ $form['instance']['widget']['module'] = array(
+ '#type' => 'value',
+ '#value' => $widget_type['module'],
+ );
+ $form['instance']['widget']['active'] = array(
+ '#type' => 'value',
+ '#value' => !empty($field['instance']['widget']['active']) ? 1 : 0,
+ );
+
+ // Add additional field instance settings from the field module.
+ $additions = module_invoke($field['module'], 'field_instance_settings_form', $field, $instance);
+ if (is_array($additions)) {
+ $form['instance']['settings'] = $additions;
+ }
+
+ // Add additional widget settings from the widget module.
+ $additions = module_invoke($widget_type['module'], 'field_widget_settings_form', $field, $instance);
+ if (is_array($additions)) {
+ $form['instance']['widget']['settings'] = $additions;
+ $form['instance']['widget']['active']['#value'] = 1;
+ }
+
+ // Add handling for default value if not provided by any other module.
+ if (field_behaviors_widget('default value', $instance) == FIELD_BEHAVIOR_DEFAULT && empty($instance['default_value_function'])) {
+ // Store the original default value for use in programmed forms. The
+ // '#default_value' property is used instead of '#value' so programmed
+ // values can override whatever we set here.
+ $form['instance']['default_value'] = array(
+ '#type' => 'value',
+ '#default_value' => $instance['default_value'],
+ );
+
+ // We cannot 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
+ // will not use it.
+ field_ui_default_value_widget($field, $instance, $form, $form_state);
+ }
+
+ $info = field_info_field_types($field['type']);
+ $description = '<p><strong>' . $info['label'] . ':</strong> ';
+ $description .= $info['description'] . '</p>';
+ $form['#prefix'] = '<div class="description">' . $description . '</div>';
+
+ $description = '<p>' . t('These settings apply to the %field field everywhere it is used.', array('%field' => $instance['label'])) . '</p>';
+
+ // 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 .= '<br/>' . 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)) {
+ // @todo Filter additional settings by whether they can be changed.
+ $form['field']['settings'] = $additions;
+ }
+
+ $form['submit'] = array('#type' => 'submit', '#value' => t('Save settings'));
+ return $form;
+}
+
+/**
+ * Pre-render function for field instance settings.
+ *
+ * Combines the instance, widget, and other settings into a single fieldset so
+ * that elements within each group can be shown at different weights as if they
+ * all had the same parent.
+ */
+function field_ui_field_edit_instance_pre_render($element) {
+ // Merge the widget settings into the main form.
+ if (isset($element['widget']['settings'])) {
+ foreach (element_children($element['widget']['settings']) as $key) {
+ $element['widget_' . $key] = $element['widget']['settings'][$key];
+ }
+ unset($element['widget']['settings']);
+ }
+
+ // Merge the instance settings into the main form.
+ if (isset($element['settings'])) {
+ foreach (element_children($element['settings']) as $key) {
+ $element['instance_' . $key] = $element['settings'][$key];
+ }
+ unset($element['settings']);
+ }
+
+ return $element;
+}
+
+/**
+ * Build default value fieldset.
+ */
+function field_ui_default_value_widget($field, $instance, &$form, &$form_state) {
+ $form['instance']['default_value_widget'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Default value'),
+ '#collapsible' => FALSE,
+ '#tree' => TRUE,
+ '#description' => t('The default value for this field, used when creating new content.'),
+ );
+
+ // Make sure the default value is not a required field.
+ $instance['required'] = FALSE;
+ $instance['description'] = '';
+ $items = $instance['default_value'];
+ // Set up form info that the default value widget will need.
+ $form['#fields'] = array(
+ $field['field_name'] => array(
+ 'field' => $field,
+ 'instance' => $instance,
+ ),
+ );
+ drupal_function_exists('field_default_form');
+ // @todo Allow multiple values (requires more work on 'add more' JS handler).
+ $widget_form = field_default_form(NULL, NULL, $field, $instance, $items, $form, $form_state, 0);
+ $form['instance']['default_value_widget'] += $widget_form;
+ $form['#fields'][$field['field_name']]['form_path'] = array(
+ 'instance',
+ 'default_value_widget',
+ $field['field_name'],
+ );
+}
+
+/**
+ * Validate a field's settings.
+ */
+function field_ui_field_edit_form_validate($form, &$form_state) {
+ $form_values = $form_state['values'];
+ $instance = $form_values['instance'];
+ $field = field_info_field($instance['field_name']);
+ $field_type = field_info_field_types($field['type']);
+ $widget_type = field_info_widget_types($instance['widget']['type']);
+
+ // Do no validation here. Assume field and widget modules are handling their
+ // own validation of form settings.
+
+ // If field.module is handling the default value, validate the result using
+ // the field validation.
+ if (field_behaviors_widget('default value', $instance) == FIELD_BEHAVIOR_DEFAULT) {
+ // If this is a programmed form, get rid of the default value widget, we
+ // have the default values already.
+ if (!empty($form_state['programmed'])) {
+ form_set_value(array('#parents' => array('instance', 'widget', 'default_value_widget')), NULL, $form_state);
+ return;
+ }
+
+ if (!empty($form_values['instance']['widget']['default_value_widget'])) {
+ // Fields that handle their own multiple values may use an expected value
+ // as the top-level key, so just pop off the top element.
+ $key = array_shift(array_keys($form_values['instance']['widget']['default_value_widget']));
+ $default_value = $form_values['instance']['widget']['default_value_widget'][$key];
+ $is_code = FALSE;
+ form_set_value(array('#parents' => array('instance', 'widget', 'default_value')), $default_value, $form_state);
+ }
+
+ if (isset($default_value)) {
+ $node = array();
+ $node[$field['field_name']] = $default_value;
+ $field['required'] = FALSE;
+ $field_function = $field_type['module'] . '_field';
+ $errors_before = form_get_errors();
+
+ // Widget now does its own validation, should be no need to add anything
+ // for widget validation here.
+ if (drupal_function_exists($field_function)) {
+ $field_function('validate', $node, $field, $default_value, $form, NULL);
+ }
+ // The field validation routine won't set an error on the right field, so
+ // set it here.
+ $errors_after = form_get_errors();
+ if (count($errors_after) > count($errors_before)) {
+ form_set_error('default_value', t('The default value is invalid.'));
+ }
+ }
+ }
+}
+
+/**
+ * Save instance settings after editing.
+ */
+function field_ui_field_edit_form_submit($form, &$form_state) {
+ $form_values = $form_state['values'];
+ $instance = $form_values['instance'];
+
+ // Update any field settings that have changed.
+ $field = field_info_field($instance['field_name']);
+ // Remove the 'bundles' element added by field_info_field.
+ // @todo This is ugly, there must be a better way.
+ unset($field['bundles']);
+ $field = array_merge($field, $form_state['values']['field']);
+ field_ui_update_field($field);
+
+ // Move the default value from the sample widget to the default value field.
+ if (isset($instance['default_value_widget'])) {
+ $instance['default_value'] = $instance['default_value_widget'][$instance['field_name']];
+ unset($instance['default_value_widget']);
+ }
+
+ // Update the instance settings.
+ module_load_include('inc', 'field', 'includes/field.crud');
+ field_update_instance($instance);
+
+ drupal_set_message(t('Saved %label configuration.', array('%label' => $instance['label'])));
+
+ $form_state['redirect'] = field_ui_next_destination($instance['bundle']);
+}
+
+/**
+ * Helper functions to handle multipage redirects.
+ */
+function field_ui_get_destinations($destinations) {
+ $query = array();
+ $path = array_shift($destinations);
+ if ($destinations) {
+ $query['destinations'] = $destinations;
+ }
+ return array($path, $query);
+}
+
+/**
+ * Return the next redirect path in a multipage sequence.
+ */
+function field_ui_next_destination($bundle) {
+ $destinations = !empty($_REQUEST['destinations']) ? $_REQUEST['destinations'] : array();
+ if (!empty($destinations)) {
+ unset($_REQUEST['destinations']);
+ return field_ui_get_destinations($destinations);
+ }
+ $admin_path = _field_ui_bundle_admin_path($bundle);
+ return $admin_path . '/fields';
+}
+
+/**
+ * Menu callback; rebuild the menu after adding new fields.
+ *
+ * Dummy function to force a page refresh so menu_rebuild() will work right
+ * when creating a new field that creates a new menu item.
+ */
+function field_ui_field_menu_refresh($obj_type, $bundle) {
+ $bundle = field_attach_extract_bundle($obj_type, $bundle);
+
+ menu_rebuild();
+ $destinations = field_ui_next_destination($bundle);
+ if (is_array($destinations)) {
+ $path = array_shift($destinations);
+ drupal_goto($path, $destinations);
+ }
+ drupal_goto($destinations);
+}
+
+/**
+ * Helper function to order fields when theming overview forms.
+ */
+function _field_ui_overview_order(&$form, $field_rows) {
+ // Put weight and parenting values into a $dummy render structure and let
+ // drupal_render() figure out the corresponding row order.
+ $dummy = array();
+ // Field rows: account for weight.
+ foreach ($field_rows as $name) {
+ $dummy[$name] = array(
+ '#markup' => $name . ' ',
+ '#type' => 'markup',
+ '#weight' => $form[$name]['weight']['#value'],
+ );
+ }
+ return $dummy ? explode(' ', trim(drupal_render($dummy))) : array();
+}
+
+/**
+ * Helper form element validator: integer.
+ */
+function _element_validate_integer($element, &$form_state) {
+ $value = $element['#value'];
+ if ($value !== '' && (!is_numeric($value) || intval($value) != $value)) {
+ form_error($element, t('%name must be an integer.', array('%name' => $element['#title'])));
+ }
+}
+
+/**
+ * Helper form element validator: integer > 0.
+ */
+function _element_validate_integer_positive($element, &$form_state) {
+ $value = $element['#value'];
+ if ($value !== '' && (!is_numeric($value) || intval($value) != $value || $value <= 0)) {
+ form_error($element, t('%name must be a positive integer.', array('%name' => $element['#title'])));
+ }
+}
+
+/**
+ * Helper form element validator: number.
+ */
+function _element_validate_number($element, &$form_state) {
+ $value = $element['#value'];
+ if ($value != '' && !is_numeric($value)) {
+ form_error($element, t('%name must be a number.', array('%name' => $element['#title'])));
+ }
+}
diff --git a/modules/field_ui/field_ui.api.php b/modules/field_ui/field_ui.api.php
new file mode 100644
index 000000000..4c53faa39
--- /dev/null
+++ b/modules/field_ui/field_ui.api.php
@@ -0,0 +1,135 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Hooks provided by the Field UI module.
+ */
+
+/**
+ * @ingroup field_ui_field_type
+ * @{
+ */
+
+/**
+ * Field settings form.
+ *
+ * @param $field
+ * The field structure being configured.
+ * @param $instance
+ * The instance structure being configured.
+ * @return
+ * The form definition for the field settings.
+ */
+function hook_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,
+ '#element_validate' => array('_element_validate_integer_positive'),
+ '#description' => t('The maximum length of the field in characters. Leave blank for an unlimited size.'),
+ );
+ return $form;
+}
+
+/**
+ * Instance settings form.
+ *
+ * @param $field
+ * The field structure being configured.
+ * @param $instance
+ * The instance structure being configured.
+ * @return
+ * The form definition for the field instance settings.
+ */
+function hook_field_instance_settings_form($field, $instance) {
+ $settings = $instance['settings'];
+
+ $form['text_processing'] = array(
+ '#type' => 'radios',
+ '#title' => t('Text processing'),
+ '#default_value' => $settings['text_processing'],
+ '#options' => array(
+ t('Plain text'),
+ t('Filtered text (user selects input format)'),
+ ),
+ );
+ if ($field['type'] == 'text_with_summary') {
+ $form['display_summary'] = array(
+ '#type' => 'select',
+ '#title' => t('Display summary'),
+ '#options' => array(
+ t('No'),
+ t('Yes'),
+ ),
+ '#description' => t('Display the summary to allow the user to input a summary value. Hide the summary to automatically fill it with a trimmed portion from the main post. '),
+ '#default_value' => !empty($settings['display_summary']) ? $settings['display_summary'] : 0,
+ );
+ }
+
+ return $form;
+}
+
+/**
+ * Widget settings form.
+ *
+ * @param $field
+ * The field structure being configured.
+ * @param $instance
+ * The instance structure being configured.
+ * @return
+ * The form definition for the widget settings.
+ */
+function hook_field_widget_settings_form($field, $instance) {
+ $widget = $instance['widget'];
+ $settings = $widget['settings'];
+ $form = array();
+
+ if ($widget['type'] == 'text_textfield') {
+ $form['size'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Size of textfield'),
+ '#default_value' => $settings['size'],
+ '#element_validate' => array('_element_validate_integer_positive'),
+ '#required' => TRUE,
+ );
+ }
+ else {
+ $form['rows'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Rows'),
+ '#default_value' => $settings['rows'],
+ '#element_validate' => array('_element_validate_integer_positive'),
+ '#required' => TRUE,
+ );
+ }
+
+ return $form;
+}
+
+/**
+ * Formatter settings form.
+ *
+ * @todo Not implemented yet. The signature below is only prospective, but
+ * providing $instance is not enough, since one $instance holds several display
+ * settings.
+ *
+ * @param $formatter
+ * The type of the formatter being configured.
+ * @param $settings
+ * The current values of the formatter settings.
+ * @param $field
+ * The field structure being configured.
+ * @param $instance
+ * The instance structure being configured.
+ * @return
+ * The form definition for the formatter settings.
+ */
+function hook_field_formatter_settings_form($formatter, $settings, $field, $instance) {
+}
+
+/**
+ * @} End of "ingroup field_ui_field_type"
+ */
diff --git a/modules/field_ui/field_ui.css b/modules/field_ui/field_ui.css
new file mode 100644
index 000000000..2cc52736b
--- /dev/null
+++ b/modules/field_ui/field_ui.css
@@ -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;
+}
+
diff --git a/modules/field_ui/field_ui.info b/modules/field_ui/field_ui.info
new file mode 100644
index 000000000..e2ca87c16
--- /dev/null
+++ b/modules/field_ui/field_ui.info
@@ -0,0 +1,8 @@
+; $Id$
+name = Field UI
+description = User Interface for the Field API.
+package = Core
+version = VERSION
+core = 7.x
+files[] = field_ui.module
+files[] = field_ui.admin.inc
diff --git a/modules/field_ui/field_ui.js b/modules/field_ui/field_ui.js
new file mode 100644
index 000000000..aea5cfb5f
--- /dev/null
+++ b/modules/field_ui/field_ui.js
@@ -0,0 +1,84 @@
+// $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).bind('mouseup keyup', 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('mouseup');
+ });
+
+ // '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 += '<option value="' + value + '"' + (is_selected ? ' selected="selected"' : '') + '>' + text + '</option>';
+ });
+
+ $(this).html(html).attr('disabled', disabled ? 'disabled' : '');
+ });
+};
+
+})(jQuery);
diff --git a/modules/field_ui/field_ui.module b/modules/field_ui/field_ui.module
new file mode 100644
index 000000000..ce856dd97
--- /dev/null
+++ b/modules/field_ui/field_ui.module
@@ -0,0 +1,327 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Allows administrators to associate custom fields to fieldable types.
+ */
+
+/**
+ * Implement hook_help().
+ */
+function field_ui_help($path, $arg) {
+ switch ($path) {
+ case 'admin/help#field_ui':
+ $output = '';
+ $output .= '<p>' . t('The Field UI module provides an administrative interface for adding custom fields to content types, users, comments, and other types of data. In the case of content types, a few fields are provided by default, such as the "Summary and Body" field. The Field UI module lets administrators edit or delete the default fields attached to content, as well as create new fields for storing any additional information. Field configuration is accessible through tabs on the <a href="@content-types">content types administration page</a>. (See the <a href="@node-help">node module help page</a> for more information about content types.)', array('@content-types' => url('admin/content/types'), '@node-help' => url('admin/help/node'))) . '</p>';
+ $output .= '<p>' . t('When adding a custom field to a content type, you determine its type (whether it will contain text, numbers, lists, etc.) and how it will be displayed (either as a text field or text area, a select box, checkboxes, radio buttons, or an auto-complete text field). A field may have multiple values (i.e., a "person" may have multiple e-mail addresses) or a single value (i.e., an "employee" has a single employee identification number).') . '</p>';
+ $output .= '<p>' . t('Custom field types may be provided by additional modules. Drupal core includes the following field types:') . '</p>';
+ $output .= '<ul>';
+ $output .= '<li>' . t('<em>Number</em>: Adds numeric field types, in integer, decimal or floating point form. You may define a set of allowed inputs, or specify an allowable range of values. A variety of common formats for displaying numeric data are available.') . '</li>';
+ $output .= '<li>' . t("<em>Text</em>: Adds text field types. A text field may contain plain text only, or optionally, may use Drupal's input format filters to securely manage HTML output. Text input fields may be either a single line (text field), multiple lines (text area), or for greater input control, a select box, checkbox, or radio buttons. If desired, CCK can validate the input to a set of allowed values.") . '</li>';
+ $output .= '<li>' . t('<em>List</em>: Provides storage mechanisms to store a list of items. Usually these items are input through a select list, checkboxes, or radio buttons.') . '</li>';
+ $output .= '</ul>';
+ return $output;
+
+ case 'admin/reports/fields':
+ return '<p>' . t('This list shows all fields currently in use for easy reference.') . '</p>';
+ }
+}
+
+/**
+ * Implement hook_menu().
+ */
+function field_ui_menu() {
+ $items['admin/reports/fields'] = array(
+ 'title' => 'Field list',
+ 'description' => 'Overview of fields on all object types.',
+ 'page callback' => 'field_ui_fields_list',
+ 'access arguments' => array('administer content types'),
+ 'type' => MENU_NORMAL_ITEM,
+ );
+
+ // Ensure the following is not executed until field_bundles is working and
+ // tables are updated. Needed to avoid errors on initial installation.
+ if (defined('MAINTENANCE_MODE')) {
+ return $items;
+ }
+ // 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' => 'field_ui_field_menu_refresh',
+ 'page arguments' => array($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;
+ }
+
+ $instance_position = count(explode('/', $path)) + 1;
+ $items["$path/fields/%field_ui_menu"] = array(
+ 'title callback' => 'field_ui_menu_label',
+ 'title arguments' => array($instance_position),
+ 'load arguments' => array($bundle_name),
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('field_ui_field_edit_form', $obj_type, $bundle_arg, $instance_position),
+ 'type' => MENU_LOCAL_TASK,
+ ) + $access;
+ $items["$path/fields/%field_ui_menu/edit"] = array(
+ 'title' => 'Edit instance settings',
+ 'load arguments' => array($bundle_name),
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('field_ui_field_edit_form', $obj_type, $bundle_arg, $instance_position),
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ ) + $access;
+ $items["$path/fields/%field_ui_menu/field-settings"] = array(
+ 'title' => 'Edit field settings',
+ 'load arguments' => array($bundle_name),
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('field_ui_field_settings_form', $obj_type, $bundle_arg, $instance_position),
+ 'type' => MENU_LOCAL_TASK,
+ ) + $access;
+ $items["$path/fields/%field_ui_menu/widget-type"] = array(
+ 'title' => 'Change widget type',
+ 'load arguments' => array($bundle_name),
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('field_ui_widget_type_form', $obj_type, $bundle_arg, $instance_position),
+ 'type' => MENU_LOCAL_TASK,
+ ) + $access;
+ $items["$path/fields/%field_ui_menu/delete"] = array(
+ 'title' => 'Delete instance',
+ 'load arguments' => array($bundle_name),
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('field_ui_field_delete_form', $obj_type, $bundle_arg, $instance_position),
+ 'type' => MENU_LOCAL_TASK,
+ ) + $access;
+ }
+ }
+ }
+ return $items;
+}
+
+/**
+ * Menu loader; Load a field instance based on its name.
+ */
+function field_ui_menu_load($field_name, $bundle_name) {
+ if ($instance = field_info_instance($field_name, $bundle_name)) {
+ return $instance;
+ }
+ return FALSE;
+}
+
+/**
+ * Menu title callback; Return a field label based on its instance.
+ */
+function field_ui_menu_label($instance) {
+ return t($instance['label']);
+}
+
+/**
+ * Implement hook_theme().
+ */
+function field_ui_theme() {
+ return array(
+ 'field_ui_field_overview_form' => array(
+ 'arguments' => array('form' => NULL),
+ 'file' => 'field_ui.admin.inc',
+ 'template' => 'field_ui-field-overview-form',
+ ),
+ 'field_ui_display_overview_form' => array(
+ 'arguments' => array('form' => NULL),
+ 'file' => 'field_ui.admin.inc',
+ 'template' => 'field_ui-display-overview-form',
+ ),
+ );
+}
+
+/**
+ * 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) {
+ $info = &drupal_static(__FUNCTION__);
+
+ 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();
+ }
+ 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.
+ *
+ * A module can add its render modes to a tab defined by another module.
+ * Expected format:
+ * @code
+ * array(
+ * 'tab1' => array(
+ * 'title' => t('The human-readable title of the tab'),
+ * 'build modes' => array('mymodule_mode1', 'mymodule_mode2'),
+ * ),
+ * 'tab2' => array(
+ * // ...
+ * ),
+ * );
+ * @endcode
+ */
+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;
+}
+
+/**
+ * Updates a field.
+ *
+ * Field API does not allow field updates, so we create a method here to
+ * update a 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) {
+ // When a new bundle is created, the menu needs to be rebuilt to add our
+ // menu item tabs.
+ menu_rebuild();
+}
+
+/**
+ * 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'];
+}
+
+/**
+ * Helper function to identify inactive fields within a bundle.
+ */
+function field_ui_inactive_instances($bundle_name = NULL) {
+ if (!empty($bundle_name)) {
+ $inactive = array($bundle_name => array());
+ $params = array('bundle' => $bundle_name);
+ }
+ else {
+ $inactive = array();
+ $params = array();
+ }
+ $active_instances = field_info_instances();
+ $all_instances = field_read_instances($params, array('include_inactive' => TRUE));
+ foreach ($all_instances as $instance) {
+ if (!isset($active_instances[$instance['bundle']][$instance['field_name']])) {
+ $inactive[$instance['bundle']][$instance['field_name']] = $instance;
+ }
+ }
+ if (!empty($bundle_name)) {
+ return $inactive[$bundle_name];
+ }
+ return $inactive;
+}