Commit 9c4cd8d8 authored by webchick's avatar webchick

Issue #2144919 by yched, fago, effulgentsia, amateescu, tstoeckler, chx,...

Issue #2144919 by yched, fago, effulgentsia, amateescu, tstoeckler, chx, larowlan, swentel: Allow widgets and formatters for base fields to be configured in Field UI.
parent d329ec0f
......@@ -195,6 +195,69 @@ public function setPropertyConstraints($name, array $constraints) {
return $this;
}
/**
* Sets the display options for the field in forms or rendered entities.
*
* This enables generic rendering of the field with widgets / formatters,
* including automated support for "In place editing", and with optional
* configurability in the "Manage display" / "Manage form display" UI screens.
*
* Unless this method is called, the field remains invisible (or requires
* ad-hoc rendering logic).
*
* @param string $display_context
* The display context. Either 'view' or 'form'.
* @param array $options
* An array of display options. Refer to
* \Drupal\Core\Field\FieldDefinitionInterface::getDisplayOptions() for
* a list of supported keys. The options should include at least a 'weight',
* or specify 'type' = 'hidden'. The 'default_widget' / 'default_formatter'
* for the field type will be used if no 'type' is specified.
*
* @return static
* The object itself for chaining.
*/
public function setDisplayOptions($display_context, array $options) {
$this->definition['display'][$display_context]['options'] = $options;
return $this;
}
/**
* Sets whether the display for the field can be configured.
*
* @param string $display_context
* The display context. Either 'view' or 'form'.
* @param bool $configurable
* Whether the display options can be configured (e.g., via the "Manage
* display" / "Manage form display" UI screens). If TRUE, the options
* specified via getDisplayOptions() act as defaults.
*
* @return static
* The object itself for chaining.
*/
public function setDisplayConfigurable($display_context, $configurable) {
// If no explicit display options have been specified, default to 'hidden'.
if (empty($this->definition['display'][$display_context])) {
$this->definition['display'][$display_context]['options'] = array('type' => 'hidden');
}
$this->definition['display'][$display_context]['configurable'] = $configurable;
return $this;
}
/**
* {@inheritdoc}
*/
public function getDisplayOptions($display_context) {
return isset($this->definition['display'][$display_context]['options']) ? $this->definition['display'][$display_context]['options'] : NULL;
}
/**
* {@inheritdoc}
*/
public function isDisplayConfigurable($display_context) {
return isset($this->definition['display'][$display_context]['configurable']) ? $this->definition['display'][$display_context]['configurable'] : FALSE;
}
/**
* {@inheritdoc}
*/
......
......@@ -127,13 +127,59 @@ public function getPropertyNames();
public function isTranslatable();
/**
* Determines whether the field is configurable via field.module.
* Returns whether the field is configurable via field.module.
*
* @return bool
* TRUE if the field is configurable.
*/
public function isConfigurable();
/**
* Returns whether the display for the field can be configured.
*
* @param string $display_context
* The display context. Either 'view' or 'form'.
*
* @return bool
* TRUE if the display for this field is configurable in the given context.
* If TRUE, the display options returned by getDisplayOptions() may be
* overridden via the respective EntityDisplay.
*
* @see \Drupal\Core\Entity\Display\EntityDisplayInterface
*/
public function isDisplayConfigurable($display_context);
/**
* Returns the default display options for the field.
*
* If the field's display is configurable, the returned display options act
* as default values and may be overridden via the respective EntityDisplay.
* Otherwise, the display options will be applied to entity displays as is.
*
* @param string $display_context
* The display context. Either 'view' or 'form'.
*
* @return array|null
* The array of display options for the field, or NULL if the field is not
* displayed. The following key/value pairs may be present:
* - label: (string) Position of the field label. The default 'field' theme
* implementation supports the values 'inline', 'above' and 'hidden'.
* Defaults to 'above'. Only applies to 'view' context.
* - type: (string) The plugin (widget or formatter depending on
* $display_context) to use, or 'hidden'. If not specified or if the
* requested plugin is unknown, the 'default_widget' / 'default_formatter'
* for the field type will be used.
* - settings: (array) Settings for the plugin specified above. The default
* settings for the plugin will be used for settings left unspecified.
* - weight: (float) The weight of the element. Not needed if 'type' is
* 'hidden'.
* The defaults of the various display options above get applied by the used
* entity display.
*
* @see \Drupal\Core\Entity\Display\EntityDisplayInterface
*/
public function getDisplayOptions($display_context);
/**
* Determines whether the field is queryable via QueryInterface.
*
......
......@@ -281,7 +281,7 @@ function testTwoPagers() {
->setComponent('comment_2', array(
'label' => 'hidden',
'type' => 'comment_default',
'weight' => 20,
'weight' => 30,
'settings' => array(
'pager_id' => 1,
)
......
......@@ -30,12 +30,16 @@
*/
class EntityDisplay extends EntityDisplayBase implements EntityViewDisplayInterface {
/**
* {@inheritdoc}
*/
protected $displayContext = 'view';
/**
* {@inheritdoc}
*/
public function __construct(array $values, $entity_type) {
$this->pluginManager = \Drupal::service('plugin.manager.field.formatter');
$this->displayContext = 'display';
parent::__construct($values, $entity_type);
}
......
......@@ -30,12 +30,16 @@
*/
class EntityFormDisplay extends EntityDisplayBase implements EntityFormDisplayInterface, \Serializable {
/**
* {@inheritdoc}
*/
protected $displayContext = 'form';
/**
* {@inheritdoc}
*/
public function __construct(array $values, $entity_type) {
$this->pluginManager = \Drupal::service('plugin.manager.field.widget');
$this->displayContext = 'form';
parent::__construct($values, $entity_type);
}
......
......@@ -14,7 +14,7 @@
*/
class EntityDisplayTest extends DrupalUnitTestBase {
public static $modules = array('entity', 'field', 'entity_test', 'user');
public static $modules = array('entity', 'field', 'entity_test', 'user', 'text');
public static function getInfo() {
return array(
......@@ -109,9 +109,10 @@ public function testEntityGetDisplay() {
* Tests the behavior of a field component within an EntityDisplay object.
*/
public function testExtraFieldComponent() {
entity_test_create_bundle('bundle_with_extra_fields');
$display = entity_create('entity_display', array(
'targetEntityType' => 'entity_test',
'bundle' => 'entity_test',
'bundle' => 'bundle_with_extra_fields',
'mode' => 'default',
));
......@@ -133,12 +134,6 @@ public function testExtraFieldComponent() {
public function testFieldComponent() {
$this->enableModules(array('field_test'));
$display = entity_create('entity_display', array(
'targetEntityType' => 'entity_test',
'bundle' => 'entity_test',
'mode' => 'default',
));
$field_name = 'test_field';
// Create a field and an instance.
$field = entity_create('field_entity', array(
......@@ -154,23 +149,29 @@ public function testFieldComponent() {
));
$instance->save();
$display = entity_create('entity_display', array(
'targetEntityType' => 'entity_test',
'bundle' => 'entity_test',
'mode' => 'default',
));
// Check that providing no options results in default values being used.
$display->setComponent($field_name);
$field_type_info = \Drupal::service('plugin.manager.field.field_type')->getDefinition($field->type);
$default_formatter = $field_type_info['default_formatter'];
$formatter_settings = \Drupal::service('plugin.manager.field.formatter')->getDefinition($default_formatter);
$formatter_settings = \Drupal::service('plugin.manager.field.formatter')->getDefaultSettings($default_formatter);
$expected = array(
'weight' => 0,
'label' => 'above',
'type' => $default_formatter,
'settings' => $formatter_settings['settings'],
'settings' => $formatter_settings,
);
$this->assertEqual($display->getComponent($field_name), $expected);
// Check that the getFormatter() method returns the correct formatter plugin.
$formatter = $display->getRenderer($field_name);
$this->assertEqual($formatter->getPluginId(), $default_formatter);
$this->assertEqual($formatter->getSettings(), $formatter_settings['settings']);
$this->assertEqual($formatter->getSettings(), $formatter_settings);
// Check that the formatter is statically persisted, by assigning an
// arbitrary property and reading it back.
......@@ -199,6 +200,65 @@ public function testFieldComponent() {
$this->assertEqual($formatter->getPluginId(), $default_formatter);
}
/**
* Tests the behavior of a field component for a base field.
*/
public function testBaseFieldComponent() {
$display = entity_create('entity_display', array(
'targetEntityType' => 'entity_test_base_field_display',
'bundle' => 'entity_test_base_field_display',
'mode' => 'default',
));
// Check that default options are correctly filled in.
$formatter_settings = \Drupal::service('plugin.manager.field.formatter')->getDefaultSettings('text_default');
$expected = array(
'test_no_display' => NULL,
'test_display_configurable' => array(
'label' => 'above',
'type' => 'text_default',
'settings' => $formatter_settings,
'weight' => 10,
),
'test_display_non_configurable' => array(
'label' => 'above',
'type' => 'text_default',
'settings' => $formatter_settings,
'weight' => 11,
),
);
foreach ($expected as $field_name => $options) {
$this->assertEqual($display->getComponent($field_name), $options);
}
// Check that saving the display only writes data for fields whose display
// is configurable.
$display->save();
$config = \Drupal::config('entity.display.' . $display->id());
$data = $config->get();
$this->assertFalse(isset($data['content']['test_no_display']));
$this->assertFalse(isset($data['hidden']['test_no_display']));
$this->assertEqual($data['content']['test_display_configurable'], $expected['test_display_configurable']);
$this->assertFalse(isset($data['content']['test_display_non_configurable']));
$this->assertFalse(isset($data['hidden']['test_display_non_configurable']));
// Check that defaults are correctly filled when loading the display.
$display = entity_load('entity_display', $display->id());
foreach ($expected as $field_name => $options) {
$this->assertEqual($display->getComponent($field_name), $options);
}
// Check that data manually written for fields whose display is not
// configurable is discarded when loading the display.
$data['content']['test_display_non_configurable'] = $expected['test_display_non_configurable'];
$data['content']['test_display_non_configurable']['weight']++;
$config->setData($data)->save();
$display = entity_load('entity_display', $display->id());
foreach ($expected as $field_name => $options) {
$this->assertEqual($display->getComponent($field_name), $options);
}
}
/**
* Tests renaming and deleting a bundle.
*/
......
......@@ -14,7 +14,7 @@
*/
class EntityFormDisplayTest extends DrupalUnitTestBase {
public static $modules = array('entity', 'field', 'entity_test', 'user');
public static $modules = array('entity', 'field', 'entity_test', 'user', 'text');
public static function getInfo() {
return array(
......@@ -55,12 +55,6 @@ public function testEntityGetFromDisplay() {
public function testFieldComponent() {
$this->enableModules(array('field_test'));
$form_display = entity_create('entity_form_display', array(
'targetEntityType' => 'entity_test',
'bundle' => 'entity_test',
'mode' => 'default',
));
// Create a field and an instance.
$field_name = 'test_field';
$field = entity_create('field_entity', array(
......@@ -76,22 +70,28 @@ public function testFieldComponent() {
));
$instance->save();
$form_display = entity_create('entity_form_display', array(
'targetEntityType' => 'entity_test',
'bundle' => 'entity_test',
'mode' => 'default',
));
// Check that providing no options results in default values being used.
$form_display->setComponent($field_name);
$field_type_info = \Drupal::service('plugin.manager.field.field_type')->getDefinition($field->type);
$default_widget = $field_type_info['default_widget'];
$widget_settings = \Drupal::service('plugin.manager.field.widget')->getDefinition($default_widget);
$widget_settings = \Drupal::service('plugin.manager.field.widget')->getDefaultSettings($default_widget);
$expected = array(
'weight' => 0,
'type' => $default_widget,
'settings' => $widget_settings['settings'],
'settings' => $widget_settings,
);
$this->assertEqual($form_display->getComponent($field_name), $expected);
// Check that the getWidget() method returns the correct widget plugin.
$widget = $form_display->getRenderer($field_name);
$this->assertEqual($widget->getPluginId(), $default_widget);
$this->assertEqual($widget->getSettings(), $widget_settings['settings']);
$this->assertEqual($widget->getSettings(), $widget_settings);
// Check that the widget is statically persisted, by assigning an
// arbitrary property and reading it back.
......@@ -120,6 +120,63 @@ public function testFieldComponent() {
$this->assertEqual($widget->getPluginId(), $default_widget);
}
/**
* Tests the behavior of a field component for a base field.
*/
public function testBaseFieldComponent() {
$display = entity_create('entity_form_display', array(
'targetEntityType' => 'entity_test_base_field_display',
'bundle' => 'entity_test_base_field_display',
'mode' => 'default',
));
// Check that default options are correctly filled in.
$formatter_settings = \Drupal::service('plugin.manager.field.widget')->getDefaultSettings('text_textfield');
$expected = array(
'test_no_display' => NULL,
'test_display_configurable' => array(
'type' => 'text_textfield',
'settings' => $formatter_settings,
'weight' => 10,
),
'test_display_non_configurable' => array(
'type' => 'text_textfield',
'settings' => $formatter_settings,
'weight' => 11,
),
);
foreach ($expected as $field_name => $options) {
$this->assertEqual($display->getComponent($field_name), $options);
}
// Check that saving the display only writes data for fields whose display
// is configurable.
$display->save();
$config = \Drupal::config('entity.form_display.' . $display->id());
$data = $config->get();
$this->assertFalse(isset($data['content']['test_no_display']));
$this->assertFalse(isset($data['hidden']['test_no_display']));
$this->assertEqual($data['content']['test_display_configurable'], $expected['test_display_configurable']);
$this->assertFalse(isset($data['content']['test_display_non_configurable']));
$this->assertFalse(isset($data['hidden']['test_display_non_configurable']));
// Check that defaults are correctly filled when loading the display.
$display = entity_load('entity_form_display', $display->id());
foreach ($expected as $field_name => $options) {
$this->assertEqual($display->getComponent($field_name), $options);
}
// Check that data manually written for fields whose display is not
// configurable is discarded when loading the display.
$data['content']['test_display_non_configurable'] = $expected['test_display_non_configurable'];
$data['content']['test_display_non_configurable']['weight']++;
$config->setData($data)->save();
$display = entity_load('entity_form_display', $display->id());
foreach ($expected as $field_name => $options) {
$this->assertEqual($display->getComponent($field_name), $options);
}
}
/**
* Tests deleting field instance.
*/
......
......@@ -619,6 +619,21 @@ public function isConfigurable() {
return TRUE;
}
/**
* {@inheritdoc}
*/
public function isDisplayConfigurable($context) {
return TRUE;
}
/**
* {@inheritdoc}
*/
public function getDisplayOptions($display_context) {
// Hide configurable fields by default.
return array('type' => 'hidden');
}
/**
* {@inheritdoc}
*/
......
......@@ -579,6 +579,21 @@ public function isConfigurable() {
return TRUE;
}
/**
* {@inheritdoc}
*/
public function isDisplayConfigurable($context) {
return TRUE;
}
/**
* {@inheritdoc}
*/
public function getDisplayOptions($display_context) {
// Hide configurable fields by default.
return array('type' => 'hidden');
}
/**
* {@inheritdoc}
*/
......
......@@ -20,8 +20,7 @@
* @param $context
* An associative array with the following elements:
* - formatter: The formatter object.
* - field: The field structure being configured.
* - instance: The instance structure being configured.
* - field_definition: The field definition.
* - view_mode: The view mode being configured.
* - form: The (entire) configuration form array.
*
......@@ -49,8 +48,7 @@ function hook_field_formatter_settings_form_alter(&$element, &$form_state, $cont
* @param array $context
* An associative array with the following elements:
* - formatter: The formatter object.
* - field: The field structure being configured.
* - instance: The instance structure being configured.
* - field_definition: The field definition.
* - form_mode: The form mode being configured.
* - form: The (entire) configuration form array.
*
......@@ -75,8 +73,7 @@ function hook_field_widget_settings_form_alter(&$element, &$form_state, $context
* @param $context
* An associative array with the following elements:
* - formatter: The formatter object.
* - field: The field structure being configured.
* - instance: The instance structure being configured.
* - field_definition: The field definition.
* - view_mode: The view mode being configured.
*
* @see \Drupal\field_ui\DisplayOverView.
......@@ -99,8 +96,7 @@ function hook_field_formatter_settings_summary_alter(&$summary, $context) {
* @param array $context
* An associative array with the following elements:
* - widget: The widget object.
* - field: The field structure being configured.
* - instance: The instance structure being configured.
* - field_definition: The field definition.
* - form_mode: The form mode being configured.
*
* @see \Drupal\field_ui\FormDisplayOverView.
......
......@@ -7,7 +7,7 @@
namespace Drupal\field_ui;
use Drupal\field\FieldInstanceInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Entity\Display\EntityDisplayInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
......@@ -16,6 +16,11 @@
*/
class DisplayOverview extends DisplayOverviewBase {
/**
* {@inheritdoc}
*/
protected $displayContext = 'view';
/**
* {@inheritdoc}
*/
......@@ -48,15 +53,17 @@ public function buildForm(array $form, array &$form_state, $entity_type = NULL,
/**
* {@inheritdoc}
*/
protected function buildFieldRow($field_id, FieldInstanceInterface $instance, EntityDisplayInterface $entity_display, array $form, array &$form_state) {
$field_row = parent::buildFieldRow($field_id, $instance, $entity_display, $form, $form_state);
$display_options = $entity_display->getComponent($field_id);
protected function buildFieldRow(FieldDefinitionInterface $field_definition, EntityDisplayInterface $entity_display, array $form, array &$form_state) {
$field_row = parent::buildFieldRow($field_definition, $entity_display, $form, $form_state);
$field_name = $field_definition->getName();
$display_options = $entity_display->getComponent($field_name);
// Insert the label column.
$label = array(
'label' => array(
'#type' => 'select',
'#title' => $this->t('Label display for @title', array('@title' => $instance->getLabel())),
'#title' => $this->t('Label display for @title', array('@title' => $field_definition->getLabel())),
'#title_display' => 'invisible',
'#options' => $this->getFieldLabelOptions(),
'#default_value' => $display_options ? $display_options['label'] : 'above',
......@@ -67,9 +74,9 @@ protected function buildFieldRow($field_id, FieldInstanceInterface $instance, En
$field_row = array_slice($field_row, 0, $label_position, TRUE) + $label + array_slice($field_row, $label_position, count($field_row) - 1, TRUE);
// Update the (invisible) title of the 'plugin' column.
$field_row['plugin']['#title'] = $this->t('Formatter for @title', array('@title' => $instance->getLabel()));
if (!empty($field_row['plugin']['settings_edit_form'])) {
$plugin_type_info = $entity_display->getRenderer($field_id)->getPluginDefinition();
$field_row['plugin']['#title'] = $this->t('Formatter for @title', array('@title' => $field_definition->getLabel()));
if (!empty($field_row['plugin']['settings_edit_form']) && ($plugin = $entity_display->getRenderer($field_name))) {
$plugin_type_info = $plugin->getPluginDefinition();
$field_row['plugin']['settings_edit_form']['label']['#markup'] = $this->t('Format settings:') . ' <span class="plugin-name">' . $plugin_type_info['label'] . '</span>';
}
......@@ -104,19 +111,12 @@ protected function getEntityDisplay($mode) {
/**
* {@inheritdoc}
*/
protected function getExtraFields() {
return field_info_extra_fields($this->entity_type, $this->bundle, 'display');
}
/**
* {@inheritdoc}
*/
protected function getPlugin($instance, $configuration) {
protected function getPlugin(FieldDefinitionInterface $field_definition, $configuration) {
$plugin = NULL;
if ($configuration && $configuration['type'] != 'hidden') {
$plugin = $this->pluginManager->getInstance(array(
'field_definition' => $instance,
'field_definition' => $field_definition,
'view_mode' => $this->mode,
'configuration' => $configuration
));
......@@ -190,11 +190,10 @@ protected function getFieldLabelOptions() {
/**
* {@inheritdoc}
*/
protected function alterSettingsForm(array &$settings_form, $plugin, FieldInstanceInterface $instance, array $form, array &$form_state) {
protected function alterSettingsForm(array &$settings_form, $plugin, FieldDefinitionInterface $field_definition, array $form, array &$form_state) {
$context = array(
'formatter' => $plugin,
'field' => $instance->getField(),
'instance' => $instance,
'field_definition' => $field_definition,
'view_mode' => $this->mode,
'form' => $form,
);
......@@ -204,11 +203,10 @@ protected function alterSettingsForm(array &$settings_form, $plugin, FieldInstan
/**
* {@inheritdoc}
*/
protected function alterSettingsSummary(array &$summary, $plugin, FieldInstanceInterface $instance) {
protected function alterSettingsSummary(array &$summary, $plugin, FieldDefinitionInterface $field_definition) {
$context = array(
'formatter' => $plugin,
'field' => $instance->getField(),
'instance' => $instance,
'field_definition' => $field_definition,
'view_mode' => $this->mode,
);
drupal_alter('field_formatter_settings_summary', $summary, $context);
......
......@@ -7,9 +7,8 @@
namespace Drupal\field_ui;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\Display\EntityDisplayInterface;
use Drupal\field\FieldInstanceInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
......@@ -17,6 +16,11 @@
*/
class FormDisplayOverview extends DisplayOverviewBase {
/**
* {@inheritdoc}
*/
protected $displayContext = 'form';
/**
* {@inheritdoc}
*/
......@@ -49,12 +53,14 @@ public function buildForm(array $form, array &$form_state, $entity_type = NULL,
/**
* {@inheritdoc}
*/
protected function buildFieldRow($field_id, FieldInstanceInterface $instance, EntityDisplayInterface $entity_display, array $form, array &$form_state) {
$field_row = parent::buildFieldRow($field_id, $instance, $entity_display, $form, $form_state);
protected function buildFieldRow(FieldDefinitionInterface $field_definition, EntityDisplayInterface $entity_display, array $form, array &$form_state) {
$field_row = parent::buildFieldRow($field_definition, $entity_display, $form, $form_state);
$field_name = $field_definition->getName();
// Update the (invisible) title of the 'plugin' column.
$field_row['plugin']['#title'] = $this->t('Formatter for @title', array('@title' => $instance->getLabel()));
if (!empty($field_row['plugin']['settings_edit_form']) && ($plugin = $entity_display->getRenderer($field_id))) {
$field_row['plugin']['#title'] = $this->t('Formatter for @title', array('@title' => $field_definition->getLabel()));
if (!empty($field_row['plugin']['settings_edit_form']) && ($plugin = $entity_display->getRenderer($field_name))) {
$plugin_type_info = $plugin->getPluginDefinition();
$field_row['plugin']['settings_edit_form']['label']['#markup'] = $this->t('Widget settings:') . ' <span class="plugin-name">' . $plugin_type_info['label'] . '</span>';
}
......@@ -72,19 +78,12 @@ protected function getEntityDisplay($mode) {
/**
* {@inheritdoc}
*/
protected function getExtraFields() {
return field_info_extra_fields($this->entity_type, $this->bundle, 'form');
}
/**
* {@inheritdoc}
*/
protected function getPlugin($instance, $configuration) {
protected function getPlugin(FieldDefinitionInterface $field_definition, $configuration) {
$plugin = NULL;
if ($configuration && $configuration['type'] != 'hidden') {
$plugin = $this->pluginManager->getInstance(array(
'field_definition' => $instance,
'field_definition' => $field_definition,
'form_mode' => $this->mode,
'configuration' => $configuration
));
......@@ -143,11 +142,10 @@ protected function getOverviewPath($mode) {
/**
* {@inheritdoc}
*/
protected function alterSettingsForm(array &$settings_form, $plugin, FieldInstanceInterface $instance, array $form, array &$form_state) {
protected function alterSettingsForm(array &$settings_form, $plugin, FieldDefinitionInterface $field_definition, array $form, array &$form_state) {
$context = array(
'widget' => $plugin,
'field' => $instance->getField(),
'instance' => $instance,
'field_definition' => $field_definition,
'form_mode' => $this->mode,
'form' => $form,
);
......@@ -157,11 +155,10 @@ protected function alterSettingsForm(array &$settings_form, $plugin, FieldInstan