diff --git a/core/modules/field_ui/css/field_ui.admin.css b/core/modules/field_ui/css/field_ui.admin.css
index 29bef62..95c0033 100644
--- a/core/modules/field_ui/css/field_ui.admin.css
+++ b/core/modules/field_ui/css/field_ui.admin.css
@@ -3,6 +3,16 @@
* Stylesheet for the Field UI module.
*/
+/* Add new field page. */
+.field-ui-add-field .field-type-wrapper .form-item {
+ float: left;
+ margin-right: 1em;
+ vertical-align: text-bottom;
+}
+.field-ui-add-field .field-type-wrapper .form-type-item {
+ margin-top: 2.3em;
+}
+
/* 'Manage fields' and 'Manage display' overviews */
.field-ui-overview .add-new .label-input {
float: left; /* LTR */
diff --git a/core/modules/field_ui/field_ui.links.action.yml b/core/modules/field_ui/field_ui.links.action.yml
index dae1583..b03bac5 100644
--- a/core/modules/field_ui/field_ui.links.action.yml
+++ b/core/modules/field_ui/field_ui.links.action.yml
@@ -11,3 +11,7 @@ field_ui.entity_form_mode_add:
weight: 1
appears_on:
- field_ui.entity_form_mode_list
+
+field_ui.field_storage_config_add:
+ class: \Drupal\Core\Menu\LocalActionDefault
+ deriver: \Drupal\field_ui\Plugin\Derivative\FieldUiLocalAction
diff --git a/core/modules/field_ui/src/Controller/FieldConfigListController.php b/core/modules/field_ui/src/Controller/FieldConfigListController.php
new file mode 100644
index 0000000..8ed9be0
--- /dev/null
+++ b/core/modules/field_ui/src/Controller/FieldConfigListController.php
@@ -0,0 +1,39 @@
+entityManager()->getDefinition($entity_type_id);
+ $bundle = $request->attributes->get('_raw_variables')->get($entity_info->getBundleEntityType());
+ }
+ return $this->entityManager()->getListBuilder('field_config')->render($entity_type_id, $bundle, $request);
+ }
+
+}
diff --git a/core/modules/field_ui/src/DisplayOverviewBase.php b/core/modules/field_ui/src/DisplayOverviewBase.php
index adab34e..2cb03ec 100644
--- a/core/modules/field_ui/src/DisplayOverviewBase.php
+++ b/core/modules/field_ui/src/DisplayOverviewBase.php
@@ -16,13 +16,49 @@
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldTypePluginManagerInterface;
use Drupal\Core\Field\PluginSettingsInterface;
+use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Field UI display overview base class.
*/
-abstract class DisplayOverviewBase extends OverviewBase {
+abstract class DisplayOverviewBase extends FormBase {
+
+ /**
+ * The name of the entity type.
+ *
+ * @var string
+ */
+ protected $entity_type = '';
+
+ /**
+ * The entity bundle.
+ *
+ * @var string
+ */
+ protected $bundle = '';
+
+ /**
+ * The entity type of the entity bundle.
+ *
+ * @var string
+ */
+ protected $bundleEntityType;
+
+ /**
+ * The entity view or form mode.
+ *
+ * @var string
+ */
+ protected $mode = '';
+
+ /**
+ * The entity manager.
+ *
+ * @var \Drupal\Core\Entity\EntityManagerInterface
+ */
+ protected $entityManager;
/**
* The display context. Either 'view' or 'form'.
@@ -65,8 +101,7 @@
* The configuration factory.
*/
public function __construct(EntityManagerInterface $entity_manager, FieldTypePluginManagerInterface $field_type_manager, PluginManagerBase $plugin_manager, ConfigFactoryInterface $config_factory) {
- parent::__construct($entity_manager);
-
+ $this->entityManager = $entity_manager;
$this->fieldTypes = $field_type_manager->getDefinitions();
$this->pluginManager = $plugin_manager;
$this->configFactory = $config_factory;
@@ -85,7 +120,23 @@ public static function create(ContainerInterface $container) {
}
/**
- * {@inheritdoc}
+ * Get the regions needed to create the overview form.
+ *
+ * @return array
+ * Example usage:
+ * @code
+ * return array(
+ * 'content' => array(
+ * // label for the region.
+ * 'title' => $this->t('Content'),
+ * // Indicates if the region is visible in the UI.
+ * 'invisible' => TRUE,
+ * // A message to indicate that there is nothing to be displayed in
+ * // the region.
+ * 'message' => $this->t('No field is displayed.'),
+ * ),
+ * );
+ * @endcode
*/
public function getRegions() {
return array(
@@ -102,6 +153,20 @@ public function getRegions() {
}
/**
+ * Returns an associative array of all regions.
+ *
+ * @return array
+ * An array containing the region options.
+ */
+ public function getRegionOptions() {
+ $options = array();
+ foreach ($this->getRegions() as $region => $data) {
+ $options[$region] = $data['title'];
+ }
+ return $options;
+ }
+
+ /**
* Collects the definitions of fields whose display is configurable.
*
* @return \Drupal\Core\Field\FieldDefinitionInterface[]
@@ -118,8 +183,19 @@ protected function getFieldDefinitions() {
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, $entity_type_id = NULL, $bundle = NULL, $mode = 'default') {
- parent::buildForm($form, $form_state, $entity_type_id, $bundle);
+ $entity_type = $this->entityManager->getDefinition($entity_type_id);
+ $this->bundleEntityType = $entity_type->getBundleEntityType();
+ $stored_bundle = $form_state->get('bundle');
+ if (!$stored_bundle) {
+ if (!$bundle) {
+ $bundle = $this->getRequest()->attributes->get('_raw_variables')->get($this->bundleEntityType);
+ }
+ $stored_bundle = $bundle;
+ $form_state->set('bundle', $bundle);
+ }
+ $this->entity_type = $entity_type_id;
+ $this->bundle = $stored_bundle;
$this->mode = $mode;
$field_definitions = $this->getFieldDefinitions();
@@ -690,6 +766,123 @@ public function multistepAjax($form, FormStateInterface $form_state) {
}
/**
+ * Performs pre-render tasks on field_ui_table elements.
+ *
+ * This function is assigned as a #pre_render callback in
+ * field_ui_element_info().
+ *
+ * @param array $element
+ * A structured array containing two sub-levels of elements. Properties
+ * used:
+ * - #tabledrag: The value is a list of $options arrays that are passed to
+ * drupal_attach_tabledrag(). The HTML ID of the table is added to each
+ * $options array.
+ *
+ * @see drupal_render()
+ * @see \Drupal\Core\Render\Element\Table::preRenderTable()
+ */
+ public function tablePreRender($elements) {
+ $js_settings = array();
+
+ // For each region, build the tree structure from the weight and parenting
+ // data contained in the flat form structure, to determine row order and
+ // indentation.
+ $regions = $elements['#regions'];
+ $tree = array('' => array('name' => '', 'children' => array()));
+ $trees = array_fill_keys(array_keys($regions), $tree);
+
+ $parents = array();
+ $children = Element::children($elements);
+ $list = array_combine($children, $children);
+
+ // Iterate on rows until we can build a known tree path for all of them.
+ while ($list) {
+ foreach ($list as $name) {
+ $row = &$elements[$name];
+ $parent = $row['parent_wrapper']['parent']['#value'];
+ // Proceed if parent is known.
+ if (empty($parent) || isset($parents[$parent])) {
+ // Grab parent, and remove the row from the next iteration.
+ $parents[$name] = $parent ? array_merge($parents[$parent], array($parent)) : array();
+ unset($list[$name]);
+
+ // Determine the region for the row.
+ $region_name = call_user_func($row['#region_callback'], $row);
+
+ // Add the element in the tree.
+ $target = &$trees[$region_name][''];
+ foreach ($parents[$name] as $key) {
+ $target = &$target['children'][$key];
+ }
+ $target['children'][$name] = array('name' => $name, 'weight' => $row['weight']['#value']);
+
+ // Add tabledrag indentation to the first row cell.
+ if ($depth = count($parents[$name])) {
+ $children = Element::children($row);
+ $cell = current($children);
+ $indentation = array(
+ '#theme' => 'indentation',
+ '#size' => $depth,
+ );
+ $row[$cell]['#prefix'] = drupal_render($indentation) . (isset($row[$cell]['#prefix']) ? $row[$cell]['#prefix'] : '');
+ }
+
+ // Add row id and associate JS settings.
+ $id = drupal_html_class($name);
+ $row['#attributes']['id'] = $id;
+ if (isset($row['#js_settings'])) {
+ $row['#js_settings'] += array(
+ 'rowHandler' => $row['#row_type'],
+ 'name' => $name,
+ 'region' => $region_name,
+ );
+ $js_settings[$id] = $row['#js_settings'];
+ }
+ }
+ }
+ }
+ // Determine rendering order from the tree structure.
+ foreach ($regions as $region_name => $region) {
+ $elements['#regions'][$region_name]['rows_order'] = array_reduce($trees[$region_name], array($this, 'reduceOrder'));
+ }
+
+ $elements['#attached']['js'][] = array(
+ 'type' => 'setting',
+ 'data' => array('fieldUIRowsData' => $js_settings),
+ );
+
+ // If the custom #tabledrag is set and there is a HTML ID, add the table's
+ // HTML ID to the options and attach the behavior.
+ // @see \Drupal\Core\Render\Element\Table::preRenderTable()
+ if (!empty($elements['#tabledrag']) && isset($elements['#attributes']['id'])) {
+ foreach ($elements['#tabledrag'] as $options) {
+ $options['table_id'] = $elements['#attributes']['id'];
+ drupal_attach_tabledrag($elements, $options);
+ }
+ }
+
+ return $elements;
+ }
+
+ /**
+ * Determines the rendering order of an array representing a tree.
+ *
+ * Callback for array_reduce() within
+ * \Drupal\field_ui\DisplayOverviewBase::tablePreRender().
+ */
+ public function reduceOrder($array, $a) {
+ $array = !isset($array) ? array() : $array;
+ if ($a['name']) {
+ $array[] = $a['name'];
+ }
+ if (!empty($a['children'])) {
+ uasort($a['children'], array('Drupal\Component\Utility\SortArray', 'sortByWeightElement'));
+ $array = array_merge($array, array_reduce($a['children'], array($this, 'reduceOrder')));
+ }
+ return $array;
+ }
+
+ /**
* Returns the entity display object used by this form.
*
* @param string $mode
diff --git a/core/modules/field_ui/src/FieldConfigListBuilder.php b/core/modules/field_ui/src/FieldConfigListBuilder.php
index cae4250..c579baf 100644
--- a/core/modules/field_ui/src/FieldConfigListBuilder.php
+++ b/core/modules/field_ui/src/FieldConfigListBuilder.php
@@ -7,10 +7,14 @@
namespace Drupal\field_ui;
+use Drupal\Component\Utility\String;
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Field\FieldTypePluginManagerInterface;
+use Drupal\Core\Url;
+use Drupal\field\FieldConfigInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@@ -19,6 +23,34 @@
class FieldConfigListBuilder extends ConfigEntityListBuilder {
/**
+ * The name of the entity type the listed fields are attached to.
+ *
+ * @var string
+ */
+ protected $targetEntityTypeId;
+
+ /**
+ * The name of the bundle the listed fields are attached to.
+ *
+ * @var string
+ */
+ protected $targetBundle;
+
+ /**
+ * The entity type of the target entity bundle.
+ *
+ * @var string
+ */
+ protected $targetBundleEntityType;
+
+ /**
+ * An array of field type definitions.
+ *
+ * @var array
+ */
+ protected $fieldTypes;
+
+ /**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
@@ -32,27 +64,100 @@ class FieldConfigListBuilder extends ConfigEntityListBuilder {
* The entity type definition.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
+ * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager
+ * The field type manager
*/
- public function __construct(EntityTypeInterface $entity_type, EntityManagerInterface $entity_manager) {
+ public function __construct(EntityTypeInterface $entity_type, EntityManagerInterface $entity_manager, FieldTypePluginManagerInterface $field_type_manager) {
parent::__construct($entity_type, $entity_manager->getStorage($entity_type->id()));
+
$this->entityManager = $entity_manager;
+ $this->fieldTypeManager = $field_type_manager;
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
- return new static($entity_type, $container->get('entity.manager'));
+ return new static($entity_type, $container->get('entity.manager'), $container->get('plugin.manager.field.field_type'));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function render($target_entity_type_id = NULL, $target_bundle = NULL) {
+ $this->targetEntityTypeId = $target_entity_type_id;
+ $this->targetBundle = $target_bundle;
+ $this->targetBundleEntityType = $this->entityManager->getDefinition($target_entity_type_id)->getBundleEntityType();
+ $this->fieldTypes = $this->fieldTypeManager->getDefinitions();
+
+ $build = parent::render();
+ $build['#empty'] = $this->t('No fields are present yet.');
+
+ return $build;
}
/**
* {@inheritdoc}
*/
- public function render() {
- // The actual field config overview is rendered by
- // \Drupal\field_ui\FieldOverview, so we should not use this class to build
- // lists.
- throw new \Exception('This class is only used for operations and not for building lists.');
+ public function load() {
+ $entities = array_filter($this->entityManager->getFieldDefinitions($this->targetEntityTypeId, $this->targetBundle), function ($field_definition) {
+ return $field_definition instanceof FieldConfigInterface;
+ });
+
+ // Sort the entities using the entity class's sort() method.
+ // See \Drupal\Core\Config\Entity\ConfigEntityBase::sort().
+ uasort($entities, array($this->entityType->getClass(), 'sort'));
+ return $entities;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildHeader() {
+ $header = array(
+ 'label' => $this->t('Label'),
+ 'field_name' => array(
+ 'data' => $this->t('Machine name'),
+ 'class' => array(RESPONSIVE_PRIORITY_MEDIUM),
+ ),
+ 'field_type' => $this->t('Field type'),
+ );
+ return $header + parent::buildHeader();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildRow(EntityInterface $field_config) {
+ /** @var \Drupal\field\FieldConfigInterface $field_config */
+ $field_storage = $field_config->getFieldStorageDefinition();
+ $route_parameters = array(
+ $this->targetBundleEntityType => $this->targetBundle,
+ 'field_config' => $field_config->id(),
+ );
+
+ $row = array(
+ 'label' => String::checkPlain($field_config->getLabel()),
+ 'field_name' => $field_config->getName(),
+ 'field_type' => array(
+ 'data' => array(
+ '#type' => 'link',
+ '#title' => $this->fieldTypes[$field_storage->getType()]['label'],
+ '#url' => Url::fromRoute('field_ui.storage_edit_' . $this->targetEntityTypeId, $route_parameters),
+ '#options' => array('attributes' => array('title' => $this->t('Edit field settings.'))),
+ ),
+ ),
+ );
+
+ // Add the operations.
+ $row = $row + parent::buildRow($field_config);
+
+ if (!empty($field_storage->locked)) {
+ $row['operations'] = array('#markup' => $this->t('Locked'));
+ $row['#attributes']['class'][] = 'menu-disabled';
+ }
+
+ return $row;
}
/**
diff --git a/core/modules/field_ui/src/FieldOverview.php b/core/modules/field_ui/src/FieldOverview.php
deleted file mode 100644
index 290d655..0000000
--- a/core/modules/field_ui/src/FieldOverview.php
+++ /dev/null
@@ -1,550 +0,0 @@
-fieldTypeManager = $field_type_manager;
- }
-
- /**
- * {@inheritdoc}
- */
- public static function create(ContainerInterface $container) {
- return new static(
- $container->get('entity.manager'),
- $container->get('plugin.manager.field.field_type')
- );
- }
-
- /**
- * {@inheritdoc}
- */
- public function getRegions() {
- return array(
- 'content' => array(
- 'title' => $this->t('Content'),
- 'invisible' => TRUE,
- // @todo Bring back this message in https://drupal.org/node/1963340.
- //'message' => $this->t('No fields are present yet.'),
- ),
- );
- }
-
- /**
- * {@inheritdoc}
- */
- public function getFormId() {
- return 'field_ui_field_overview_form';
- }
-
- /**
- * {@inheritdoc}
- */
- public function buildForm(array $form, FormStateInterface $form_state, $entity_type_id = NULL, $bundle = NULL) {
- parent::buildForm($form, $form_state, $entity_type_id, $bundle);
-
- // Gather bundle information.
- $fields = array_filter(\Drupal::entityManager()->getFieldDefinitions($this->entity_type, $this->bundle), function ($field_definition) {
- return $field_definition instanceof FieldConfigInterface;
- });
- $field_types = $this->fieldTypeManager->getDefinitions();
-
- // Field prefix.
- $field_prefix = \Drupal::config('field_ui.settings')->get('field_prefix');
-
- $form += array(
- '#entity_type' => $this->entity_type,
- '#bundle' => $this->bundle,
- '#fields' => array_keys($fields),
- );
-
- $table = array(
- '#type' => 'field_ui_table',
- '#tree' => TRUE,
- '#header' => array(
- $this->t('Label'),
- array(
- 'data' => $this->t('Machine name'),
- 'class' => array(RESPONSIVE_PRIORITY_MEDIUM),
- ),
- $this->t('Field type'),
- $this->t('Operations'),
- ),
- '#regions' => $this->getRegions(),
- '#attributes' => array(
- 'class' => array('field-ui-overview'),
- 'id' => 'field-overview',
- ),
- );
-
- // Fields.
- foreach ($fields as $name => $field) {
- $field_storage = $field->getFieldStorageDefinition();
- $route_parameters = array(
- $this->bundleEntityType => $this->bundle,
- 'field_config' => $field->id(),
- );
- $table[$name] = array(
- '#attributes' => array(
- 'id' => drupal_html_class($name),
- ),
- 'label' => array(
- '#markup' => String::checkPlain($field->getLabel()),
- ),
- 'field_name' => array(
- '#markup' => $field->getName(),
- ),
- 'type' => array(
- '#type' => 'link',
- '#title' => $field_types[$field_storage->getType()]['label'],
- '#url' => Url::fromRoute('field_ui.storage_edit_' . $this->entity_type, $route_parameters),
- '#options' => array('attributes' => array('title' => $this->t('Edit field settings.'))),
- ),
- );
-
- $table[$name]['operations']['data'] = array(
- '#type' => 'operations',
- '#links' => $this->entityManager->getListBuilder('field_config')->getOperations($field),
- );
-
- if (!empty($field_storage->locked)) {
- $table[$name]['operations'] = array('#markup' => $this->t('Locked'));
- $table[$name]['#attributes']['class'][] = 'menu-disabled';
- }
- }
-
- // Gather valid field types.
- $field_type_options = array();
- foreach ($field_types as $name => $field_type) {
- // Skip field types which should not be added via user interface.
- if (empty($field_type['no_ui'])) {
- $field_type_options[$name] = $field_type['label'];
- }
- }
- asort($field_type_options);
-
- // Additional row: add new field.
- if ($field_type_options) {
- $name = '_add_new_field';
- $table[$name] = array(
- '#attributes' => array('class' => array('add-new')),
- 'label' => array(
- '#type' => 'textfield',
- '#title' => $this->t('New field label'),
- '#title_display' => 'invisible',
- '#size' => 15,
- '#description' => $this->t('Label'),
- '#prefix' => '
',
- ),
- 'field_name' => array(
- '#type' => 'machine_name',
- '#title' => $this->t('New field name'),
- '#title_display' => 'invisible',
- // This field should stay LTR even for RTL languages.
- '#field_prefix' => '' . $field_prefix,
- '#field_suffix' => '',
- '#size' => 15,
- '#description' => $this->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' => FieldStorageConfig::NAME_MAX_LENGTH - strlen($field_prefix),
- '#prefix' => '
',
- '#machine_name' => array(
- 'source' => array('fields', $name, 'label'),
- 'exists' => array($this, 'fieldNameExists'),
- 'standalone' => TRUE,
- 'label' => '',
- ),
- '#required' => FALSE,
- ),
- 'type' => array(
- '#type' => 'select',
- '#title' => $this->t('Type of new field'),
- '#title_display' => 'invisible',
- '#options' => $field_type_options,
- '#empty_option' => $this->t('- Select a field type -'),
- '#description' => $this->t('Type of data to store.'),
- '#attributes' => array('class' => array('field-type-select')),
- '#cell_attributes' => array('colspan' => 2),
- '#prefix' => '
',
- ),
- // 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' => TRUE,
- ),
- );
- }
-
- // Additional row: re-use existing field.
- $existing_fields = $this->getExistingFieldOptions();
- if ($existing_fields) {
- // Build list of options.
- $existing_field_options = array();
- foreach ($existing_fields as $field_name => $info) {
- $text = $this->t('@type: @field (@label)', array(
- '@type' => $info['type_label'],
- '@label' => $info['label'],
- '@field' => $info['field'],
- ));
- $existing_field_options[$field_name] = Unicode::truncate($text, 80, FALSE, TRUE);
- }
- asort($existing_field_options);
- $name = '_add_existing_field';
- $table[$name] = array(
- '#attributes' => array('class' => array('add-new')),
- '#row_type' => 'add_new_field',
- '#region_callback' => array($this, 'getRowRegion'),
- 'label' => array(
- '#type' => 'textfield',
- '#title' => $this->t('Existing field label'),
- '#title_display' => 'invisible',
- '#size' => 15,
- '#description' => $this->t('Label'),
- '#attributes' => array('class' => array('label-textfield')),
- '#prefix' => '',
- ),
- 'field_name' => array(
- '#type' => 'select',
- '#title' => $this->t('Existing field to share'),
- '#title_display' => 'invisible',
- '#options' => $existing_field_options,
- '#empty_option' => $this->t('- Select an existing field -'),
- '#description' => $this->t('Field to share'),
- '#attributes' => array('class' => array('field-select')),
- '#cell_attributes' => array('colspan' => 3),
- '#prefix' => '
',
- ),
- );
- }
-
- // We can set the 'rows_order' element, needed by theme_field_ui_table(),
- // here instead of a #pre_render callback because this form doesn't have the
- // tabledrag behavior anymore.
- $table['#regions']['content']['rows_order'] = array();
- foreach (Element::children($table) as $name) {
- $table['#regions']['content']['rows_order'][] = $name;
- }
-
- $form['fields'] = $table;
-
- $form['actions'] = array('#type' => 'actions');
- $form['actions']['submit'] = array(
- '#type' => 'submit',
- '#button_type' => 'primary',
- '#value' => $this->t('Save'));
-
- return $form;
- }
-
- /**
- * {@inheritdoc}
- */
- public function validateForm(array &$form, FormStateInterface $form_state) {
- $this->validateAddNew($form, $form_state);
- $this->validateAddExisting($form, $form_state);
- }
-
- /**
- * Validates the 'add new field' row.
- *
- * @param array $form
- * An associative array containing the structure of the form.
- * @param \Drupal\Core\Form\FormStateInterface $form_state
- * The current state of the form.
- *
- * @see \Drupal\field_ui\FieldOverview::validateForm()
- */
- protected function validateAddNew(array $form, FormStateInterface $form_state) {
- $field = $form_state->getValue(array('fields', '_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']))) {
- // Missing label.
- if (!$field['label']) {
- $form_state->setErrorByName('fields][_add_new_field][label', $this->t('Add new field: you need to provide a label.'));
- }
-
- // Missing field name.
- if (!$field['field_name']) {
- $form_state->setErrorByName('fields][_add_new_field][field_name', $this->t('Add new field: you need to provide a machine name for the field.'));
- }
- // Field name validation.
- else {
- $field_name = $field['field_name'];
-
- // Add the field prefix.
- $field_name = \Drupal::config('field_ui.settings')->get('field_prefix') . $field_name;
- $form_state->setValueForElement($form['fields']['_add_new_field']['field_name'], $field_name);
- }
-
- // Missing field type.
- if (!$field['type']) {
- $form_state->setErrorByName('fields][_add_new_field][type', $this->t('Add new field: you need to select a field type.'));
- }
- }
- }
-
- /**
- * Validates the 're-use existing field' row.
- *
- * @param array $form
- * An associative array containing the structure of the form.
- * @param \Drupal\Core\Form\FormStateInterface $form_state
- * The current state of the form.
- *
- * @see \Drupal\field_ui\FieldOverview::validate()
- */
- protected function validateAddExisting(array $form, FormStateInterface $form_state) {
- // The form element might be absent if no existing fields can be added to
- // this bundle.
- if ($field = $form_state->getValue(array('fields', '_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']))) {
- // Missing label.
- if (!$field['label']) {
- $form_state->setErrorByName('fields][_add_existing_field][label', $this->t('Re-use existing field: you need to provide a label.'));
- }
-
- // Missing existing field name.
- if (!$field['field_name']) {
- $form_state->setErrorByName('fields][_add_existing_field][field_name', $this->t('Re-use existing field: you need to select a field.'));
- }
- }
- }
- }
-
- /**
- * Overrides \Drupal\field_ui\OverviewBase::submitForm().
- */
- public function submitForm(array &$form, FormStateInterface $form_state) {
- $error = FALSE;
- $form_values = $form_state->getValue('fields');
- $destinations = array();
-
- // Create new field.
- if (!empty($form_values['_add_new_field']['field_name'])) {
- $values = $form_values['_add_new_field'];
-
- $field_storage = array(
- 'field_name' => $values['field_name'],
- 'entity_type' => $this->entity_type,
- 'type' => $values['type'],
- 'translatable' => $values['translatable'],
- );
- $field = array(
- 'field_name' => $values['field_name'],
- 'entity_type' => $this->entity_type,
- 'bundle' => $this->bundle,
- 'label' => $values['label'],
- // Field translatability should be explicitly enabled by the users.
- 'translatable' => FALSE,
- );
-
- // Create the field storage and field.
- try {
- $this->entityManager->getStorage('field_storage_config')->create($field_storage)->save();
- $new_field = $this->entityManager->getStorage('field_config')->create($field);
- $new_field->save();
-
- // Make sure the field is displayed in the 'default' form mode (using
- // default widget and settings). It stays hidden for other form modes
- // until it is explicitly configured.
- entity_get_form_display($this->entity_type, $this->bundle, 'default')
- ->setComponent($values['field_name'])
- ->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($values['field_name'])
- ->save();
-
- // Always show the field settings step, as the cardinality needs to be
- // configured for new fields.
- $route_parameters = array(
- $this->bundleEntityType => $this->bundle,
- 'field_config' => $new_field->id(),
- );
- $destinations[] = array('route_name' => 'field_ui.storage_edit_' . $this->entity_type, 'route_parameters' => $route_parameters);
- $destinations[] = array('route_name' => 'field_ui.field_edit_' . $this->entity_type, 'route_parameters' => $route_parameters);
-
- // Store new field information for any additional submit handlers.
- $form_state->set(['fields_added', '_add_new_field'], $values['field_name']);
- }
- catch (\Exception $e) {
- $error = TRUE;
- drupal_set_message($this->t('There was a problem creating field %label: !message', array('%label' => $field['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_name = $values['field_name'];
- $field_storage = FieldStorageConfig::loadByName($this->entity_type, $field_name);
- if (!empty($field_storage->locked)) {
- drupal_set_message($this->t('The field %label cannot be added because it is locked.', array('%label' => $values['label'])), 'error');
- }
- else {
- $field = array(
- 'field_name' => $field_name,
- 'entity_type' => $this->entity_type,
- 'bundle' => $this->bundle,
- 'label' => $values['label'],
- );
-
- try {
- $new_field = $this->entityManager->getStorage('field_config')->create($field);
- $new_field->save();
-
- // Make sure the field is displayed in the 'default' form mode (using
- // default widget and settings). It stays hidden for other form modes
- // until it is explicitly configured.
- entity_get_form_display($this->entity_type, $this->bundle, 'default')
- ->setComponent($field_name)
- ->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_name)
- ->save();
-
- $destinations[] = array(
- 'route_name' => 'field_ui.field_edit_' . $this->entity_type,
- 'route_parameters' => array(
- $this->bundleEntityType => $this->bundle,
- 'field_config' => $new_field->id(),
- ),
- );
- // Store new field information for any additional submit handlers.
- $form_state->set(['fields_added', '_add_existing_field'], $field['field_name']);
- }
- catch (\Exception $e) {
- $error = TRUE;
- drupal_set_message($this->t('There was a problem creating field %label: @message.', array('%label' => $field['label'], '@message' => $e->getMessage())), 'error');
- }
- }
- }
-
- if ($destinations) {
- $destination = drupal_get_destination();
- $destinations[] = $destination['destination'];
- $form_state->setRedirectUrl(FieldUI::getNextDestination($destinations, $form_state));
- }
- elseif (!$error) {
- drupal_set_message($this->t('Your settings have been saved.'));
- }
- }
-
- /**
- * Returns an array of existing fields to be added to a bundle.
- *
- * @return array
- * An array of existing fields keyed by field name.
- */
- protected function getExistingFieldOptions() {
- $options = array();
-
- // Collect candidate fields: all fields of field storages for this
- // entity type that are not already present in the current bundle.
- $field_map = \Drupal::entityManager()->getFieldMap();
- $field_ids = array();
- if (!empty($field_map[$this->entity_type])) {
- foreach ($field_map[$this->entity_type] as $field_name => $data) {
- if (!in_array($this->bundle, $data['bundles'])) {
- $bundle = reset($data['bundles']);
- $field_ids[] = $this->entity_type . '.' . $bundle . '.' . $field_name;
- }
- }
- }
-
- // Load the fields and build the list of options.
- if ($field_ids) {
- $field_types = $this->fieldTypeManager->getDefinitions();
- $fields = $this->entityManager->getStorage('field_config')->loadMultiple($field_ids);
- foreach ($fields as $field) {
- // Do not show:
- // - locked fields,
- // - fields that should not be added via user interface.
- $field_type = $field->getType();
- $field_storage = $field->getFieldStorageDefinition();
- if (empty($field_storage->locked) && empty($field_types[$field_type]['no_ui'])) {
- $options[$field->getName()] = array(
- 'type' => $field_type,
- 'type_label' => $field_types[$field_type]['label'],
- 'field' => $field->getName(),
- 'label' => $field->getLabel(),
- );
- }
- }
- }
-
- return $options;
- }
-
- /**
- * Checks if a field machine name is taken.
- *
- * @param string $value
- * The machine name, not prefixed.
- *
- * @return bool
- * Whether or not the field machine name is taken.
- */
- public function fieldNameExists($value) {
- // Add the field prefix.
- $field_name = \Drupal::config('field_ui.settings')->get('field_prefix') . $value;
-
- $field_storage_definitions = \Drupal::entityManager()->getFieldStorageDefinitions($this->entity_type);
- return isset($field_storage_definitions[$field_name]);
- }
-
-}
diff --git a/core/modules/field_ui/src/Form/FieldStorageAddForm.php b/core/modules/field_ui/src/Form/FieldStorageAddForm.php
new file mode 100644
index 0000000..6e35ea2
--- /dev/null
+++ b/core/modules/field_ui/src/Form/FieldStorageAddForm.php
@@ -0,0 +1,476 @@
+entityManager = $entity_manager;
+ $this->fieldTypePluginManager = $field_type_plugin_manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFormID() {
+ return 'field_ui_field_storage_add_form';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('entity.manager'),
+ $container->get('plugin.manager.field.field_type')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildForm(array $form, FormStateInterface $form_state, $entity_type_id = NULL, $bundle = NULL) {
+ $entity_type = $this->entityManager->getDefinition($entity_type_id);
+ $this->bundleEntityTypeId = $entity_type->getBundleEntityType();
+ if (!$form_state->hasValue('bundle')) {
+ if (!$bundle) {
+ $bundle = $this->getRequest()->attributes->get('_raw_variables')->get($this->bundleEntityTypeId);
+ }
+ $form_state->setValue('bundle', $bundle);
+ }
+
+ $this->entityTypeId = $entity_type_id;
+ $this->bundle = $form_state->getValue('bundle');
+
+ $form = array(
+ '#attributes' => array(
+ 'id' => 'field-add-wrapper',
+ 'class' => array('field-ui-add-field'),
+ ),
+ );
+
+ // Gather valid field types.
+ $field_type_options = array();
+ foreach ($this->fieldTypePluginManager->getDefinitions() as $name => $field_type) {
+ // Skip field types which should not be added via user interface.
+ if (empty($field_type['no_ui'])) {
+ $field_type_options[$name] = $field_type['label'];
+ }
+ }
+ asort($field_type_options);
+
+ $form['field'] = array(
+ '#type' => 'container',
+ '#attributes' => array('class' => array('field-type-wrapper', 'clearfix')),
+ );
+
+ $form['field']['type'] = array(
+ '#type' => 'select',
+ '#title' => $this->t('Add a new field'),
+ '#options' => $field_type_options,
+ '#empty_option' => $this->t('- Select a field type -'),
+ );
+
+ // Re-use existing field.
+ if ($existing_fields = $this->getExistingFieldOptions()) {
+ // Build list of options.
+ $existing_field_options = array();
+ foreach ($existing_fields as $field_name => $info) {
+ $text = $this->t('@type: @field (@label)', array(
+ '@type' => $info['type_label'],
+ '@label' => $info['label'],
+ '@field' => $info['field'],
+ ));
+ $existing_field_options[$field_name] = Unicode::truncate($text, 80, FALSE, TRUE);
+ }
+ asort($existing_field_options);
+
+ $form['field']['separator'] = array(
+ '#type' => 'item',
+ '#markup' => $this->t('or'),
+ );
+ $form['field']['existing_field'] = array(
+ '#type' => 'select',
+ '#title' => $this->t('Re-use an existing field'),
+ '#options' => $existing_field_options,
+ '#empty_option' => $this->t('- Select an existing field -'),
+ );
+ }
+
+ $form['label'] = array(
+ '#type' => 'textfield',
+ '#title' => $this->t('Label'),
+ '#size' => 15,
+ '#weight' => 20,
+ );
+
+ $field_prefix = $this->config('field_ui.settings')->get('field_prefix');
+ $form['field_name'] = array(
+ '#type' => 'machine_name',
+ // This field should stay LTR even for RTL languages.
+ '#field_prefix' => '' . $field_prefix,
+ '#field_suffix' => '',
+ '#size' => 15,
+ '#description' => $this->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' => FieldStorageConfig::NAME_MAX_LENGTH - strlen($field_prefix),
+ '#machine_name' => array(
+ 'source' => array('label'),
+ 'exists' => array($this, 'fieldNameExists'),
+ ),
+ '#required' => FALSE,
+ '#weight' => 25,
+ );
+
+ // Place the 'translatable' property as an explicit value so that
+ // contrib modules can form_alter() the value for newly created fields.
+ $form['translatable'] = array(
+ '#type' => 'value',
+ '#value' => FALSE,
+ );
+
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => $this->t('Save and continue'),
+ '#button_type' => 'primary',
+ );
+
+ $form['#attached']['library'][] = 'field_ui/drupal.field_ui';
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateForm(array &$form, FormStateInterface $form_state) {
+ // Missing field type.
+ if (!$form_state->getValue('type') && !$form_state->getValue('existing_field')) {
+ $form_state->setErrorByName('type', $this->t('You need to select a field type.'));
+ }
+ elseif ($form_state->getValue('type') && $form_state->getValue('existing_field')) {
+ $form_state->setErrorByName('type', $this->t('So.. you want to add a new field *and* re-use an existing one? Not cool!'));
+ return;
+ }
+
+ $this->validateAddNew($form, $form_state);
+ $this->validateAddExisting($form, $form_state);
+ }
+
+ /**
+ * Validates the 'add new field' case.
+ *
+ * @param array $form
+ * An associative array containing the structure of the form.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The current state of the form.
+ *
+ * @see \Drupal\field_ui\Form\FieldStorageAddForm::validateForm()
+ */
+ protected function validateAddNew(array $form, FormStateInterface $form_state) {
+ $field = $form_state->getValues();
+
+ // Validate if any information was provided in the 'add new field' case.
+ if (!$field['existing_field'] && array_filter(array($field['label'], $field['field_name'], $field['type']))) {
+ // Missing label.
+ if (!$field['label']) {
+ $form_state->setErrorByName('label', $this->t('Add new field: you need to provide a label.'));
+ }
+
+ // Missing field name.
+ if (!$field['field_name']) {
+ $form_state->setErrorByName('field_name', $this->t('Add new field: you need to provide a machine name for the field.'));
+ }
+ // Field name validation.
+ else {
+ $field_name = $field['field_name'];
+
+ // Add the field prefix.
+ $field_name = \Drupal::config('field_ui.settings')->get('field_prefix') . $field_name;
+ $form_state->setValueForElement($form['field_name'], $field_name);
+ }
+ }
+ }
+
+ /**
+ * Validates the 're-use existing field' case.
+ *
+ * @param array $form
+ * An associative array containing the structure of the form.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The current state of the form.
+ *
+ * @see \Drupal\field_ui\Form\FieldStorageAddForm::validateForm()
+ */
+ protected function validateAddExisting(array $form, FormStateInterface $form_state) {
+ // The form element might be absent if no existing fields can be added to
+ // this bundle.
+ $field = $form_state->getValues();
+ if ($field['existing_field']) {
+ // Missing label.
+ if (!$field['label']) {
+ $form_state->setErrorByName('label', $this->t('Re-use existing field: you need to provide a label.'));
+ }
+
+ // Discard the value for the 'field_name' form element.
+ $form_state->setValueForElement($form['field_name'], '');
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ $error = FALSE;
+ $values = $form_state->getValues();
+ $destinations = array();
+
+ // Create new field.
+ if (!$values['existing_field']) {
+ $field_storage = array(
+ 'field_name' => $values['field_name'],
+ 'entity_type' => $this->entityTypeId,
+ 'type' => $values['type'],
+ 'translatable' => $values['translatable'],
+ );
+ $field = array(
+ 'field_name' => $values['field_name'],
+ 'entity_type' => $this->entityTypeId,
+ 'bundle' => $this->bundle,
+ 'label' => $values['label'],
+ // Field translatability should be explicitly enabled by the users.
+ 'translatable' => FALSE,
+ );
+
+ // Create the field storage and field.
+ try {
+ $this->entityManager->getStorage('field_storage_config')->create($field_storage)->save();
+ $new_field = $this->entityManager->getStorage('field_config')->create($field);
+ $new_field->save();
+
+ $this->configureEntityDisplays($values['field_name']);
+
+ // Always show the field settings step, as the cardinality needs to be
+ // configured for new fields.
+ $route_parameters = array(
+ $this->bundleEntityTypeId => $this->bundle,
+ 'field_config' => $new_field->id(),
+ );
+ $destinations[] = array('route_name' => 'field_ui.storage_edit_' . $this->entityTypeId, 'route_parameters' => $route_parameters);
+ $destinations[] = array('route_name' => 'field_ui.field_edit_' . $this->entityTypeId, 'route_parameters' => $route_parameters);
+ $destinations[] = array('route_name' => 'field_ui.overview_' . $this->entityTypeId, 'route_parameters' => $route_parameters);
+
+ // Store new field information for any additional submit handlers.
+ $form_state->set(['fields_added', '_add_new_field'], $values['field_name']);
+ }
+ catch (\Exception $e) {
+ $error = TRUE;
+ drupal_set_message($this->t('There was a problem creating field %label: !message', array('%label' => $field['label'], '!message' => $e->getMessage())), 'error');
+ }
+ }
+
+ // Re-use existing field.
+ if (!empty($values['existing_field'])) {
+ $field_name = $values['existing_field'];
+ $field_storage = FieldStorageConfig::loadByName($this->entityTypeId, $field_name);
+ if ($field_storage->isLocked()) {
+ drupal_set_message($this->t('The field %label cannot be added because it is locked.', array('%label' => $values['label'])), 'error');
+ }
+ else {
+ $field = array(
+ 'field_name' => $field_name,
+ 'entity_type' => $this->entityTypeId,
+ 'bundle' => $this->bundle,
+ 'label' => $values['label'],
+ );
+
+ try {
+ $new_field = $this->entityManager->getStorage('field_config')->create($field);
+ $new_field->save();
+
+ $this->configureEntityDisplays($field_name);
+
+ $route_parameters = array(
+ $this->bundleEntityTypeId => $this->bundle,
+ 'field_config' => $new_field->id(),
+ );
+ $destinations[] = array('route_name' => 'field_ui.field_edit_' . $this->entityTypeId, 'route_parameters' => $route_parameters);
+ $destinations[] = array('route_name' => 'field_ui.overview_' . $this->entityTypeId, 'route_parameters' => $route_parameters);
+
+ // Store new field information for any additional submit handlers.
+ $form_state->set(['fields_added', '_add_existing_field'], $field['field_name']);
+ }
+ catch (\Exception $e) {
+ $error = TRUE;
+ drupal_set_message($this->t('There was a problem creating field %label: @message.', array('%label' => $field['label'], '@message' => $e->getMessage())), 'error');
+ }
+ }
+ }
+
+ if ($destinations) {
+ $destination = drupal_get_destination();
+ $destinations[] = $destination['destination'];
+ $form_state->setRedirectUrl(FieldUI::getNextDestination($destinations, $form_state));
+ }
+ elseif (!$error) {
+ drupal_set_message($this->t('Your settings have been saved.'));
+ }
+ }
+
+ /**
+ * Configures the newly created field for the default view and form modes.
+ *
+ * @param string $field_name
+ * The field name.
+ */
+ protected function configureEntityDisplays($field_name) {
+ // Make sure the field is displayed in the 'default' form mode (using
+ // default widget and settings). It stays hidden for other form modes
+ // until it is explicitly configured.
+ entity_get_form_display($this->entityTypeId, $this->bundle, 'default')
+ ->setComponent($field_name)
+ ->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->entityTypeId, $this->bundle, 'default')
+ ->setComponent($field_name)
+ ->save();
+ }
+
+ /**
+ * Returns an array of existing fields to be added to a bundle.
+ *
+ * @return array
+ * An array of existing fields keyed by field name.
+ */
+ protected function getExistingFieldOptions() {
+ $options = array();
+
+ // Collect candidate fields: all fields of field storages for this
+ // entity type that are not already present in the current bundle.
+ $field_map = \Drupal::entityManager()->getFieldMap();
+ $field_ids = array();
+ if (!empty($field_map[$this->entityTypeId])) {
+ foreach ($field_map[$this->entityTypeId] as $field_name => $data) {
+ if (!in_array($this->bundle, $data['bundles'])) {
+ $bundle = reset($data['bundles']);
+ $field_ids[] = $this->entityTypeId . '.' . $bundle . '.' . $field_name;
+ }
+ }
+ }
+
+ // Load the fields and build the list of options.
+ if ($field_ids) {
+ $field_types = $this->fieldTypePluginManager->getDefinitions();
+ $fields = $this->entityManager->getStorage('field_config')->loadMultiple($field_ids);
+ foreach ($fields as $field) {
+ // Do not show:
+ // - locked fields,
+ // - fields that should not be added via user interface.
+ $field_type = $field->getType();
+ $field_storage = $field->getFieldStorageDefinition();
+ if (empty($field_storage->locked) && empty($field_types[$field_type]['no_ui'])) {
+ $options[$field->getName()] = array(
+ 'type' => $field_type,
+ 'type_label' => $field_types[$field_type]['label'],
+ 'field' => $field->getName(),
+ 'label' => $field->getLabel(),
+ );
+ }
+ }
+ }
+
+ return $options;
+ }
+
+ /**
+ * Checks if a field machine name is taken.
+ *
+ * @param string $value
+ * The machine name, not prefixed.
+ * @param array $element
+ * An array containing the structure of the 'field_name' element.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The current state of the form.
+ *
+ * @return bool
+ * Whether or not the field machine name is taken.
+ */
+ public function fieldNameExists($value, $element, FormStateInterface $form_state) {
+ // Don't validate the case when an existing field has been selected.
+ if ($form_state->getValue('existing_field')) {
+ return FALSE;
+ }
+
+ // Add the field prefix.
+ $field_name = \Drupal::config('field_ui.settings')->get('field_prefix') . $value;
+
+ $field_storage_definitions = \Drupal::entityManager()->getFieldStorageDefinitions($this->entityTypeId);
+ return isset($field_storage_definitions[$field_name]);
+ }
+
+}
diff --git a/core/modules/field_ui/src/OverviewBase.php b/core/modules/field_ui/src/OverviewBase.php
deleted file mode 100644
index 2de43ee..0000000
--- a/core/modules/field_ui/src/OverviewBase.php
+++ /dev/null
@@ -1,249 +0,0 @@
-entityManager = $entity_manager;
- }
-
- /**
- * {@inheritdoc}
- */
- public static function create(ContainerInterface $container) {
- return new static(
- $container->get('entity.manager')
- );
- }
-
- /**
- * {@inheritdoc}
- */
- public function buildForm(array $form, FormStateInterface $form_state, $entity_type_id = NULL, $bundle = NULL) {
- $entity_type = $this->entityManager->getDefinition($entity_type_id);
- $this->bundleEntityType = $entity_type->getBundleEntityType();
- $stored_bundle = $form_state->get('bundle');
- if (!$stored_bundle) {
- if (!$bundle) {
- $bundle = $this->getRequest()->attributes->get('_raw_variables')->get($this->bundleEntityType);
- }
- $stored_bundle = $bundle;
- $form_state->set('bundle', $bundle);
- }
-
- $this->entity_type = $entity_type_id;
- $this->bundle = $stored_bundle;
- }
-
- /**
- * {@inheritdoc}
- */
- public function submitForm(array &$form, FormStateInterface $form_state) {
- }
-
- /**
- * Get the regions needed to create the overview form.
- *
- * @return array
- * Example usage:
- * @code
- * return array(
- * 'content' => array(
- * // label for the region.
- * 'title' => $this->t('Content'),
- * // Indicates if the region is visible in the UI.
- * 'invisible' => TRUE,
- * // A message to indicate that there is nothing to be displayed in
- * // the region.
- * 'message' => $this->t('No field is displayed.'),
- * ),
- * );
- * @endcode
- */
- abstract public function getRegions();
-
- /**
- * Returns an associative array of all regions.
- */
- public function getRegionOptions() {
- $options = array();
- foreach ($this->getRegions() as $region => $data) {
- $options[$region] = $data['title'];
- }
- return $options;
- }
-
- /**
- * Performs pre-render tasks on field_ui_table elements.
- *
- * This function is assigned as a #pre_render callback in
- * field_ui_element_info().
- *
- * @param array $element
- * A structured array containing two sub-levels of elements. Properties
- * used:
- * - #tabledrag: The value is a list of $options arrays that are passed to
- * drupal_attach_tabledrag(). The HTML ID of the table is added to each
- * $options array.
- *
- * @see drupal_render()
- * @see \Drupal\Core\Render\Element\Table::preRenderTable()
- */
- public function tablePreRender($elements) {
- $js_settings = array();
-
- // For each region, build the tree structure from the weight and parenting
- // data contained in the flat form structure, to determine row order and
- // indentation.
- $regions = $elements['#regions'];
- $tree = array('' => array('name' => '', 'children' => array()));
- $trees = array_fill_keys(array_keys($regions), $tree);
-
- $parents = array();
- $children = Element::children($elements);
- $list = array_combine($children, $children);
-
- // Iterate on rows until we can build a known tree path for all of them.
- while ($list) {
- foreach ($list as $name) {
- $row = &$elements[$name];
- $parent = $row['parent_wrapper']['parent']['#value'];
- // Proceed if parent is known.
- if (empty($parent) || isset($parents[$parent])) {
- // Grab parent, and remove the row from the next iteration.
- $parents[$name] = $parent ? array_merge($parents[$parent], array($parent)) : array();
- unset($list[$name]);
-
- // Determine the region for the row.
- $region_name = call_user_func($row['#region_callback'], $row);
-
- // Add the element in the tree.
- $target = &$trees[$region_name][''];
- foreach ($parents[$name] as $key) {
- $target = &$target['children'][$key];
- }
- $target['children'][$name] = array('name' => $name, 'weight' => $row['weight']['#value']);
-
- // Add tabledrag indentation to the first row cell.
- if ($depth = count($parents[$name])) {
- $children = Element::children($row);
- $cell = current($children);
- $indentation = array(
- '#theme' => 'indentation',
- '#size' => $depth,
- );
- $row[$cell]['#prefix'] = drupal_render($indentation) . (isset($row[$cell]['#prefix']) ? $row[$cell]['#prefix'] : '');
- }
-
- // Add row id and associate JS settings.
- $id = drupal_html_class($name);
- $row['#attributes']['id'] = $id;
- if (isset($row['#js_settings'])) {
- $row['#js_settings'] += array(
- 'rowHandler' => $row['#row_type'],
- 'name' => $name,
- 'region' => $region_name,
- );
- $js_settings[$id] = $row['#js_settings'];
- }
- }
- }
- }
- // Determine rendering order from the tree structure.
- foreach ($regions as $region_name => $region) {
- $elements['#regions'][$region_name]['rows_order'] = array_reduce($trees[$region_name], array($this, 'reduceOrder'));
- }
-
- $elements['#attached']['js'][] = array(
- 'type' => 'setting',
- 'data' => array('fieldUIRowsData' => $js_settings),
- );
-
- // If the custom #tabledrag is set and there is a HTML ID, add the table's
- // HTML ID to the options and attach the behavior.
- // @see \Drupal\Core\Render\Element\Table::preRenderTable()
- if (!empty($elements['#tabledrag']) && isset($elements['#attributes']['id'])) {
- foreach ($elements['#tabledrag'] as $options) {
- $options['table_id'] = $elements['#attributes']['id'];
- drupal_attach_tabledrag($elements, $options);
- }
- }
-
- return $elements;
- }
-
- /**
- * Determines the rendering order of an array representing a tree.
- *
- * Callback for array_reduce() within
- * \Drupal\field_ui\OverviewBase::tablePreRender().
- */
- public function reduceOrder($array, $a) {
- $array = !isset($array) ? array() : $array;
- if ($a['name']) {
- $array[] = $a['name'];
- }
- if (!empty($a['children'])) {
- uasort($a['children'], array('Drupal\Component\Utility\SortArray', 'sortByWeightElement'));
- $array = array_merge($array, array_reduce($a['children'], array($this, 'reduceOrder')));
- }
- return $array;
- }
-
-}
diff --git a/core/modules/field_ui/src/Plugin/Derivative/FieldUiLocalAction.php b/core/modules/field_ui/src/Plugin/Derivative/FieldUiLocalAction.php
new file mode 100644
index 0000000..88b47e8
--- /dev/null
+++ b/core/modules/field_ui/src/Plugin/Derivative/FieldUiLocalAction.php
@@ -0,0 +1,77 @@
+routeProvider = $route_provider;
+ $this->entityManager = $entity_manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, $base_plugin_id) {
+ return new static(
+ $container->get('router.route_provider'),
+ $container->get('entity.manager')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDerivativeDefinitions($base_plugin_definition) {
+ $this->derivatives = array();
+
+ foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) {
+ if ($entity_type->get('field_ui_base_route')) {
+ $this->derivatives["field_storage_config_add_$entity_type_id"] = array(
+ 'route_name' => "field_ui.field_storage_config_add_$entity_type_id",
+ 'title' => $this->t('Add field'),
+ 'appears_on' => array("field_ui.overview_$entity_type_id"),
+ );
+ }
+ }
+
+ foreach ($this->derivatives as &$entry) {
+ $entry += $base_plugin_definition;
+ }
+
+ return $this->derivatives;
+ }
+
+}
diff --git a/core/modules/field_ui/src/Routing/RouteSubscriber.php b/core/modules/field_ui/src/Routing/RouteSubscriber.php
index 3abb6cf..7564fa1 100644
--- a/core/modules/field_ui/src/Routing/RouteSubscriber.php
+++ b/core/modules/field_ui/src/Routing/RouteSubscriber.php
@@ -90,7 +90,7 @@ protected function alterRoutes(RouteCollection $collection) {
$route = new Route(
"$path/fields",
array(
- '_form' => '\Drupal\field_ui\FieldOverview',
+ '_content' => '\Drupal\field_ui\Controller\FieldConfigListController::listing',
'_title' => 'Manage fields',
) + $defaults,
array('_permission' => 'administer ' . $entity_type_id . ' fields'),
@@ -99,6 +99,16 @@ protected function alterRoutes(RouteCollection $collection) {
$collection->add("field_ui.overview_$entity_type_id", $route);
$route = new Route(
+ "$path/add-field",
+ array(
+ '_form' => '\Drupal\field_ui\Form\FieldStorageAddForm',
+ '_title' => 'Add field',
+ ) + $defaults,
+ array('_permission' => 'administer ' . $entity_type_id . ' fields')
+ );
+ $collection->add("field_ui.field_storage_config_add_$entity_type_id", $route);
+
+ $route = new Route(
"$path/form-display",
array(
'_form' => '\Drupal\field_ui\FormDisplayOverview',
diff --git a/core/modules/field_ui/src/Tests/FieldUIRouteTest.php b/core/modules/field_ui/src/Tests/FieldUIRouteTest.php
index 1f4d3ea..65e0867 100644
--- a/core/modules/field_ui/src/Tests/FieldUIRouteTest.php
+++ b/core/modules/field_ui/src/Tests/FieldUIRouteTest.php
@@ -35,9 +35,7 @@ protected function setUp() {
*/
public function testFieldUIRoutes() {
$this->drupalGet('field-ui-test-no-bundle/manage/fields');
- // @todo Bring back this assertion in https://drupal.org/node/1963340.
- // @see \Drupal\field_ui\FieldOverview::getRegions()
- //$this->assertText('No fields are present yet.');
+ $this->assertText('No fields are present yet.');
$this->drupalGet('admin/config/people/accounts/fields');
$this->assertTitle('Manage fields | Drupal');