diff --git a/core/modules/field_ui/field_ui.js b/core/modules/field_ui/field_ui.js
index 3000cd1..9153b19 100644
--- a/core/modules/field_ui/field_ui.js
+++ b/core/modules/field_ui/field_ui.js
@@ -9,7 +9,7 @@
Drupal.behaviors.fieldUIFieldOverview = {
attach: function (context, settings) {
- $(context).find('table#field-overview').once('field-overview', function () {
+ $(context).find('#field-overview').once('field-overview', function () {
Drupal.fieldUIFieldOverview.attachUpdateSelects(this, settings);
});
}
@@ -32,7 +32,7 @@ Drupal.fieldUIFieldOverview = {
// 'Field type' select updates its 'Widget' select.
$table.find('.field-type-select').each(function () {
var $this = $(this);
- this.targetSelect = $this.closest('tr').find('.widget-type-select');
+ this.targetSelect = $('.new-field-settings .widget-type-select');
$this.bind('change keyup', function () {
var selectedFieldType = this.options[this.selectedIndex].value;
@@ -48,7 +48,7 @@ Drupal.fieldUIFieldOverview = {
// 'Existing field' select updates its 'Widget' select and 'Label' textfield.
$table.find('.field-select').each(function () {
var $this = $(this);
- var $tr = $this.closest('tr');
+ var $tr = $('.existing-field-settings');
this.targetSelect = $tr.find('.widget-type-select');
this.targetTextfield = $tr.find('.label-textfield');
this.targetTextfield
diff --git a/core/modules/field_ui/lib/Drupal/field_ui/FieldOverview.php b/core/modules/field_ui/lib/Drupal/field_ui/FieldOverview.php
index ec602ff..94be7fd 100644
--- a/core/modules/field_ui/lib/Drupal/field_ui/FieldOverview.php
+++ b/core/modules/field_ui/lib/Drupal/field_ui/FieldOverview.php
@@ -124,6 +124,18 @@ public function buildForm(array $form, array &$form_state, $entity_type = NULL,
),
);
+ $form['inline_actions'] = array(
+ '#prefix' => '
',
+ );
+ $form['inline_actions']['add'] = array(
+ '#theme' => 'menu_local_action',
+ '#link' => array(
+ 'href' => 'admin/structure/types/manage/' . $this->bundle . '/add-field',
+ 'title' => t('Add field'),
+ ),
+ );
+
// Fields.
foreach ($instances as $name => $instance) {
$field = field_info_field($instance['field_name']);
diff --git a/core/modules/field_ui/lib/Drupal/field_ui/FieldUI.php b/core/modules/field_ui/lib/Drupal/field_ui/FieldUI.php
index 6e4a6a2..002db1a 100644
--- a/core/modules/field_ui/lib/Drupal/field_ui/FieldUI.php
+++ b/core/modules/field_ui/lib/Drupal/field_ui/FieldUI.php
@@ -33,4 +33,64 @@ public static function getNextDestination() {
return $next_destination;
}
+ /**
+ * returns an array of existing fields to be added to a bundle.
+ *
+ * @return array
+ * an array of existing fields keyed by field name.
+ */
+ public static function getExistingFieldOptions($entity_type, $bundle) {
+ $info = array();
+ $field_types = field_info_field_types();
+
+ foreach (field_info_instances() as $existing_entity_type => $bundles) {
+ foreach ($bundles as $existing_bundle => $instances) {
+ // No need to look in the current bundle.
+ if (!($existing_bundle == $bundle && $existing_entity_type == $entity_type)) {
+ foreach ($instances as $instance) {
+ $field = field_info_field($instance['field_name']);
+ // Don't show
+ // - locked fields,
+ // - fields already in the current bundle,
+ // - fields that cannot be added to the entity type,
+ // - fields that should not be added via user interface.
+
+ if (empty($field['locked'])
+ && !field_info_instance($entity_type, $field['field_name'], $bundle)
+ && (empty($field['entity_types']) || in_array($entity_type, $field['entity_types']))
+ && empty($field_types[$field['type']]['no_ui'])) {
+ $widget = entity_get_form_display($instance['entity_type'], $instance['bundle'], 'default')->getComponent($instance['field_name']);
+ $info[$instance['field_name']] = array(
+ 'type' => $field['type'],
+ 'type_label' => $field_types[$field['type']]['label'],
+ 'field' => $field['field_name'],
+ 'label' => $instance['label'],
+ 'widget_type' => $widget['type'],
+ );
+ }
+ }
+ }
+ }
+ }
+ return $info;
+ }
+
+ /**
+ * Checks if a field machine name is taken.
+ *
+ * @param string $value
+ * The machine name, not prefixed with 'field_'.
+ *
+ * @return bool
+ * Whether or not the field machine name is taken.
+ */
+ public static function fieldNameExists($value) {
+ // Prefix with 'field_'.
+ $field_name = 'field_' . $value;
+
+ // We need to check inactive fields as well, so we can't use
+ // field_info_fields().
+ return (bool) field_read_fields(array('field_name' => $field_name), array('include_inactive' => TRUE));
+ }
+
}
diff --git a/core/modules/field_ui/lib/Drupal/field_ui/Form/FieldAddForm.php b/core/modules/field_ui/lib/Drupal/field_ui/Form/FieldAddForm.php
new file mode 100644
index 0000000..65d4196
--- /dev/null
+++ b/core/modules/field_ui/lib/Drupal/field_ui/Form/FieldAddForm.php
@@ -0,0 +1,560 @@
+entityManager = $entity_manager;
+ $this->widgetManager = $widget_manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFormID() {
+ return 'field_ui_field_add_form';
+ }
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('plugin.manager.entity'),
+ $container->get('plugin.manager.field.widget')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildForm(array $form, array &$form_state, $entity_type = NULL, $bundle = NULL, $form_mode = NULL) {
+ $entity_info = $this->entityManager->getDefinition($entity_type);
+ if (!empty($entity_info['bundle_prefix'])) {
+ $bundle = $entity_info['bundle_prefix'] . $bundle;
+ }
+
+ $this->entity_type = $entity_type;
+ $this->bundle = $bundle;
+ $this->mode = $form_mode;
+ $this->adminPath = $this->entityManager->getAdminPath($this->entity_type, $this->bundle);
+
+ $form['#attributes'] = array(
+ 'class' => array('field-ui-overview'),
+ 'id' => 'field-overview',
+ );
+
+ // Gather bundle information.
+ $instances = field_info_instances($entity_type, $bundle);
+ $field_types = field_info_field_types();
+ $extra_fields = field_info_extra_fields($entity_type, $bundle, 'form');
+ $entity_form_display = entity_get_form_display($entity_type, $bundle, $form_mode);
+
+ // Field prefix.
+ $field_prefix = config('field_ui.settings')->get('field_prefix');
+
+ drupal_set_title(t('Add a new field.'));
+ $form = array(
+ '#entity_type' => $this->entity_type,
+ '#bundle' => $this->bundle,
+ '#fields' => array_keys($instances),
+ '#extra' => array_keys($extra_fields),
+ '#tree' => TRUE,
+ '#attributes' => array(
+ 'class' => array('field-ui-overview'),
+ 'id' => 'field-overview',
+ ),
+ );
+
+ $max_weight = $entity_form_display->getHighestWeight();
+
+ // Prepare the widget types to be display as options.
+ $widget_options = $this->widgetManager->getOptions();
+ $widget_type_options = array();
+ foreach ($widget_options as $field_type => $widgets) {
+ $widget_type_options[$field_types[$field_type]['label']] = $widgets;
+ }
+
+ // Gather valid field types.
+ $field_type_options = array();
+ foreach ($field_types as $name => $field_type) {
+ // Skip field types which have no widget types, or should not be added via
+ // user interface.
+ if (isset($widget_options[$name]) && empty($field_type['no_ui'])) {
+ $field_type_options[$name] = $field_type['label'];
+ }
+ }
+ asort($field_type_options);
+
+ $form['type'] = array(
+ '#type' => 'select',
+ '#title' => t('Type of new field'),
+ '#title_display' => 'invisible',
+ '#options' => $field_type_options,
+ '#empty_option' => t('- Select a field type -'),
+ '#description' => t('Type of data to store.'),
+ '#attributes' => array('class' => array('field-type-select')),
+ );
+
+ // Determine which options from the 'type' field already have existing
+ // fields and add them to the $states array so we can determine if the
+ // new-or-existing select list should be displayed.
+ $existing_fields = FieldUI::getExistingFieldOptions($this->entity_type, $this->bundle);
+ $states[] = array('value' => FALSE);
+ foreach ($existing_fields as $field) {
+ $states[] = array('value' => $field['type']);
+ }
+
+ $form['new-or-existing'] = array(
+ '#type' => 'select',
+ '#title' => t('New or Existing'),
+ '#options' => array(
+ t('New'),
+ t('Existing'),
+ ),
+ '#empty_option' => t('- New or Existing -'),
+ '#states' => array(
+ 'visible' => array(
+ 'select[name="type"]' => $states,
+ ),
+ ),
+ );
+
+ if ($field_type_options && $widget_type_options) {
+ $states[] = array('value' => '');
+ $name = '_add_new_field';
+ $form[$name] = array(
+ '#type' => 'container',
+ '#attributes' => array('class' => array('new-field-settings')),
+ // Only show this field if a type has been selected and 'Existing'
+ // has not explicitly been selected.
+ '#states' => array(
+ 'invisible' => array(
+ 'select[name="type"]' => $states,
+ 'select[name="new-or-existing"]' => array('!value' => 0),
+ ),
+ ),
+ 'label' => array(
+ '#type' => 'textfield',
+ '#title' => t('New field label'),
+ '#size' => 15,
+ '#description' => t('Label'),
+ ),
+ 'weight' => array(
+ '#type' => 'textfield',
+ '#default_value' => $max_weight + 1,
+ '#size' => 3,
+ '#title' => t('Weight for new field'),
+ '#attributes' => array('class' => array('field-weight', 'element-invisible')),
+ ),
+ 'field_name' => array(
+ '#type' => 'machine_name',
+ '#title' => t('New field name'),
+ // This field should stay LTR even for RTL languages.
+ '#field_prefix' => '' . $field_prefix,
+ '#field_suffix' => '',
+ '#size' => 15,
+ '#description' => t('A unique machine-readable name containing letters, numbers, and underscores.'),
+ // Calculate characters depending on the length of the field prefix
+ // setting. Maximum length is 32.
+ '#maxlength' => Field::ID_MAX_LENGTH - strlen($field_prefix),
+ '#machine_name' => array(
+ 'source' => array($name, 'label'),
+ 'exists' => array(new FieldUI, 'fieldNameExists'),
+ 'standalone' => TRUE,
+ 'label' => '',
+ ),
+ '#required' => FALSE,
+ ),
+ 'widget_type' => array(
+ '#type' => 'select',
+ '#title' => t('Widget for new field'),
+ '#options' => $widget_type_options,
+ '#empty_option' => t('- Select a widget -'),
+ '#description' => t('Form element to edit the data.'),
+ '#attributes' => array('class' => array('widget-type-select')),
+ '#prefix' => '',
+ '#suffix' => '
',
+ ),
+ // Place the 'translatable' property as an explicit value so that
+ // contrib modules can form_alter() the value for newly created fields.
+ 'translatable' => array(
+ '#type' => 'value',
+ '#value' => FALSE,
+ ),
+ );
+ }
+
+ // Additional row: re-use existing field.
+ if ($existing_fields && $widget_type_options) {
+ // Build list of options.
+ $existing_field_options = array();
+ foreach ($existing_fields as $field_name => $info) {
+ $text = t('@type: @field (@label)', array(
+ '@type' => $info['type_label'],
+ '@label' => $info['label'],
+ '@field' => $info['field'],
+ ));
+ $existing_field_options[$field_name] = truncate_utf8($text, 80, FALSE, TRUE);
+ }
+ asort($existing_field_options);
+ $name = '_add_existing_field';
+ $form[$name] = array(
+ '#type' => 'container',
+ '#attributes' => array('class' => array('existing-field-settings')),
+ // Only show this field if 'Existing' has explicitly been selected.
+ '#states' => array(
+ 'invisible' => array(
+ 'select[name="new-or-existing"]' => array('!value' => 1),
+ ),
+ ),
+ 'field_name' => array(
+ '#type' => 'select',
+ '#title' => t('Existing field to share'),
+ '#options' => $existing_field_options,
+ '#empty_option' => t('- Select an existing field -'),
+ '#description' => t('Field to share'),
+ '#attributes' => array('class' => array('field-select')),
+ ),
+ 'label' => array(
+ '#type' => 'textfield',
+ '#title' => t('Existing field label'),
+ '#size' => 15,
+ '#description' => t('Label'),
+ '#attributes' => array('class' => array('label-textfield')),
+ ),
+ 'widget_type' => array(
+ '#type' => 'select',
+ '#title' => t('Widget for existing field'),
+ '#options' => $widget_type_options,
+ '#empty_option' => t('- Select a widget -'),
+ '#description' => t('Form element to edit the data.'),
+ '#attributes' => array('class' => array('widget-type-select')),
+ ),
+ 'weight' => array(
+ '#type' => 'textfield',
+ '#default_value' => $max_weight + 2,
+ '#size' => 3,
+ '#title' => t('Weight for added field'),
+ '#attributes' => array('class' => array('field-weight', 'element-invisible')),
+ ),
+ );
+ }
+
+ // This key is used to store the current updated field.
+ $form_state += array(
+ 'formatter_settings_edit' => NULL,
+ );
+
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save field settings'));
+
+ $form['#attached']['library'][] = array('field_ui', 'drupal.field_ui');
+
+ // Add settings for the update selects behavior.
+ $js_fields = array();
+ foreach ($existing_fields as $field_name => $info) {
+ $js_fields[$field_name] = array('label' => $info['label'], 'type' => $info['type'], 'widget' => $info['widget_type']);
+ }
+
+ $form['#attached']['js'][] = array(
+ 'type' => 'setting',
+ 'data' => array('fields' => $js_fields, 'fieldWidgetTypes' => $widget_options),
+ );
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateForm(array &$form, array &$form_state) {
+ $this->validateAddNew($form, $form_state);
+ $this->validateAddExisting($form, $form_state);
+ }
+
+ /**
+ * Validates the 'add new field' field settings.
+ *
+ * @param array $form
+ * An associative array containing the structure of the form.
+ * @param array $form_state
+ * A reference to a keyed array containing the current state of the form.
+ *
+ * @see Drupal\field_ui\FieldOverview::validateForm()
+ */
+ protected function validateAddNew(array $form, array &$form_state) {
+ if (!($form_state['values']['new-or-existing'])) {
+ $field = $form_state['values']['_add_new_field'];
+ $value = $form_state['values'];
+
+ // Missing label.
+ if (!$field['label']) {
+ form_set_error('fields][_add_new_field:][label', t('Add new field: you need to provide a label.'));
+ }
+
+ // Missing field name.jk
+ if (!$field['field_name']) {
+ form_set_error('fields][_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.
+ $field_name = 'field_' . $field_name;
+ form_set_value($form['_add_new_field']['field_name'], $field_name, $form_state);
+ }
+
+ // Missing field type.
+ if (!$form_state['values']['type']) {
+ form_set_error('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 ($form_state['values']['type']) {
+ $widget_types = $this->widgetManager->getOptions($form_state['values']['type']);
+ if (!isset($widget_types[$field['widget_type']])) {
+ form_set_error('_add_new_field][widget_type', t('Add new field: invalid widget.'));
+ }
+ }
+ }
+ }
+
+ /**
+ * Validates the 're-use existing field' field settings.
+ *
+ * @param array $form
+ * An associative array containing the structure of the form.
+ * @param array $form_state
+ * A reference to a keyed array containing the current state of the form.
+ *
+ * @see Drupal\field_ui\FieldOverview::validate()
+ */
+ protected function validateAddExisting(array $form, array &$form_state) {
+ // The form element might be absent if no existing fields can be added to
+ // this bundle.
+ if (isset($form_state['values']['_add_existing_field'])) {
+ $field = $form_state['values']['_add_existing_field'];
+
+ // Validate if any information was provided in the
+ // 're-use existing field' row.
+ if (array_filter(array($field['label'], $field['field_name'], $field['widget_type']))) {
+ // Missing label.
+ if (!$field['label']) {
+ form_set_error('fields][_add_existing_field][label', t('Re-use existing field: you need to provide a label.'));
+ }
+
+ // Missing existing field name.
+ if (!$field['field_name']) {
+ form_set_error('fields][_add_existing_field][field_name', t('Re-use existing field: you need to select a field.'));
+ }
+
+ // Missing widget type.
+ if (!$field['widget_type']) {
+ form_set_error('fields][_add_existing_field][widget_type', t('Re-use 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 = $this->widgetManager->getOptions($existing_field['type']);
+ if (!isset($widget_types[$field['widget_type']])) {
+ form_set_error('fields][_add_existing_field][widget_type', t('Re-use existing field: invalid widget.'));
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, array &$form_state) {
+ $form_values = $form_state['values'];
+ $entity_form_display = entity_get_form_display($this->entity_type, $this->bundle, $this->mode);
+
+ // Save the form display.
+ $entity_form_display->save();
+
+ $destinations = array();
+
+ // Create new field.
+ if (!empty($form_state['values']['_add_new_field']['field_name'])) {
+ $values = $form_values['_add_new_field'];
+
+ $field = array(
+ 'field_name' => $values['field_name'],
+ 'type' => $form_state['values']['type'],
+ 'translatable' => $values['translatable'],
+ );
+ $instance = array(
+ 'field_name' => $field['field_name'],
+ 'entity_type' => $this->entity_type,
+ 'bundle' => $this->bundle,
+ 'label' => $values['label'],
+ );
+
+ // Create the field and instance.
+ try {
+ $this->entityManager->getStorageController('field_entity')->create($field)->save();
+ $new_instance = $this->entityManager->getStorageController('field_instance')->create($instance);
+ $new_instance->save();
+
+ // Make sure the field is displayed in the 'default' form mode (using
+ // the configured widget and default settings).
+ entity_get_form_display($this->entity_type, $this->bundle, 'default')
+ ->setComponent($field['field_name'], array(
+ 'type' => $values['widget_type'],
+ 'weight' => $values['weight'],
+ ))
+ ->save();
+
+ // Make sure the field is displayed in the 'default' view mode (using
+ // default formatter and settings). It stays hidden for other view
+ // modes until it is explicitly configured.
+ entity_get_display($this->entity_type, $this->bundle, 'default')
+ ->setComponent($field['field_name'])
+ ->save();
+
+ // Always show the field settings step, as the cardinality needs to be
+ // configured for new fields.
+ $destinations[] = $this->adminPath. '/fields/' . $new_instance->id() . '/field';
+ $destinations[] = $this->adminPath . '/fields/' . $new_instance->id();
+
+ // 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())), 'error');
+ }
+ }
+
+ // Re-use 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'])), 'error');
+ }
+ else {
+ $instance = array(
+ 'field_name' => $field['field_name'],
+ 'entity_type' => $this->entity_type,
+ 'bundle' => $this->bundle,
+ 'label' => $values['label'],
+ );
+
+ try {
+ $new_instance = $this->entityManager->getStorageController('field_instance')->create($instance);
+ $new_instance->save();
+
+ // Make sure the field is displayed in the 'default' form mode (using
+ // the configured widget and default settings).
+ entity_get_form_display($this->entity_type, $this->bundle, 'default')
+ ->setComponent($field['field_name'], array(
+ 'type' => $values['widget_type'],
+ 'weight' => $values['weight'],
+ ))
+ ->save();
+
+ // Make sure the field is displayed in the 'default' view mode (using
+ // default formatter and settings). It stays hidden for other view
+ // modes until it is explicitly configured.
+ entity_get_display($this->entity_type, $this->bundle, 'default')
+ ->setComponent($field['field_name'])
+ ->save();
+
+ $destinations[] = $this->adminPath . '/fields/' . $new_instance->id();
+ // 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())), 'error');
+ }
+ }
+ }
+
+ if ($destinations) {
+ $destination = drupal_get_destination();
+ $destinations[] = $destination['destination'];
+ unset($_GET['destination']);
+ $path = array_shift($destinations);
+ $options = drupal_parse_url($path);
+ $options['query']['destinations'] = $destinations;
+ $form_state['redirect'] = array($options['path'], $options);
+ }
+ else {
+ drupal_set_message(t('Your settings have been saved.'));
+ }
+ }
+
+}
diff --git a/core/modules/field_ui/lib/Drupal/field_ui/Routing/RouteSubscriber.php b/core/modules/field_ui/lib/Drupal/field_ui/Routing/RouteSubscriber.php
index 55477a0..82971d9 100644
--- a/core/modules/field_ui/lib/Drupal/field_ui/Routing/RouteSubscriber.php
+++ b/core/modules/field_ui/lib/Drupal/field_ui/Routing/RouteSubscriber.php
@@ -100,6 +100,13 @@ public function routes(RouteBuildEvent $event) {
);
$collection->add("field_ui.display_overview.$entity_type", $route);
+ $route = new Route(
+ "$path/add-field",
+ array('_form' => '\Drupal\field_ui\Form\FieldAddForm') + $defaults,
+ array('_permission' => 'administer ' . $entity_type . ' fields')
+ );
+ $collection->add("field_ui.add.$entity_type", $route);
+
foreach (entity_get_view_modes($entity_type) as $view_mode => $view_mode_info) {
$route = new Route(
"$path/display/$view_mode",