fieldry-1.0.x-dev/src/Plugin/WrapperBase.php
src/Plugin/WrapperBase.php
<?php
namespace Drupal\fieldry\Plugin;
use Drupal\Core\Plugin\PluginBase;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FormatterInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* Base FieldryWrapper implementation with the most common settings.
*/
abstract class WrapperBase extends PluginBase implements ConfigurableWrapperInterface {
/**
* Create a new instance of a FieldryWrapperInterface plugin object.
*
* Overrides the defaults PluginBase::__construct() mostly to improve
* configuration defaults setting. Since FieldWrapperInterface uses the
* \Drupal\Component\Plugin\ConfigurableInterface we can assume a proper
* configuration setter method, and is likely to handle any business logic
* need to clean and prepare the configurations.
*
* @param array $configuration
* Configurations for this fieldry wrapper plugin.
* @param string $plugin_id
* The unique fieldry wrapper plugin ID of this plugin.
* @param mixed $plugin_definition
* The plugin definition.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition) {
$this->pluginId = $plugin_id;
$this->pluginDefinition = $plugin_definition;
// Use the static::setConfiguration() method to apply any defaults or
// other configuration business logic before assigning.
$this->setConfiguration($configuration);
}
/**
* {@inheritdoc}
*/
public static function isApplicable($plugin_definition, FormatterInterface $formatter, FieldDefinitionInterface $field_definition): bool {
// @todo determine if a formatter will render individual field items
// separately earlier so that unsupport wrappers won't show as options.
// Currently formatters do not indicate if they render multiple or use a
// different theme function, before building the field items.
return empty($plugin_definition['field_types'])
|| in_array($field_definition->getType(), $plugin_definition['field_types']);
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration(): array {
return [
'wrapper_tag' => 'div',
'wrapper_classes' => [],
'title_tag' => 'div',
'title_classes' => [],
'default_classes' => TRUE,
];
}
/**
* {@inheritdoc}
*/
public function getConfiguration(): array {
return $this->configuration;
}
/**
* {@inheritdoc}
*/
public function setConfiguration(array $configuration): void {
$this->configuration = $configuration + $this->defaultConfiguration();
}
/**
* Fetch a list of HTML tags available for use as the field wrapper.
*
* @return array<\Stringable|string>
* List of accepted HTML tags, which can be use to wrap the entire field.
*/
public function getWrapperTagOptions(): array {
return [
'' => $this->t('-- NONE --'),
'div' => 'DIV',
'header' => 'HEADER',
'footer' => 'FOOTER',
'article' => 'ARTICLE',
'blockquote' => 'BLOCKQUOTE',
'section' => 'SECTION',
'aside' => 'ASIDE',
];
}
/**
* Get the outer field wrapper HTML tag.
*
* @return string|false
* The HTML tag to use for the outer field wrapper. FALSE is returned only
* if no wrapper should be used.
*/
public function getWrapperTag(): string|false {
$tag = $this->configuration['wrapper_tag'] ?? 'div';
// Ensure that the wrapper tag is an allowed HTML tag or default to "div".
return isset($this->getWrapperTagOptions()[$tag]) ? $tag : 'div';
}
/**
* Get a list of HTML tags available for use as the field label wrapper.
*
* @return array<\Stringable|string>
* List of accepted HTML tags, which can be use to wrap the field label.
*/
public function getTitleTagOptions(): array {
return [
'div' => 'DIV',
'span' => 'SPAN',
'p' => 'P',
'h2' => 'H2',
'h3' => 'H3',
'h4' => 'H4',
'h5' => 'H5',
'h6' => 'H6',
];
}
/**
* Get the HTML tag to use for the field label.
*
* @return string
* The HTML tag to use for the field label.
*/
public function getTitleTag(): string {
$tag = $this->configuration['title_tag'];
// Unlike the wrapper or item tags, this cannot be empty, and must be at
// the very least a "div" tag.
return isset($this->getTitleTagOptions()[$tag]) ? $tag : 'div';
}
/**
* Get the attributes for the field item HTML tags.
*
* @param \Drupal\Core\Field\FieldItemInterface $item
* The field item to fetch the attributes for.
*
* @return array
* Attributes array to apply to the field item HTML tag for the field item.
*/
public function getItemAttributes(FieldItemInterface $item): array {
return $item->_attributes ?? [];
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state): array {
$elements = [];
$elements['#after_build'][] = [$this, 'configurationAfterBuild'];
$elements['wrapper_tag'] = [
'#type' => 'select',
'#title' => $this->t('Wrapper HTML element'),
'#options' => $this->getWrapperTagOptions(),
'#default_value' => $this->getWrapperTag(),
'#description' => $this->t('HTML for wrapping the entire field label and wrappers.'),
];
$elements['wrapper_classes'] = [
'#type' => 'css_class',
'#title' => $this->t('Wrapper CSS classes'),
'#default_value' => $this->configuration['wrapper_classes'],
];
$elements['title_tag'] = [
'#type' => 'select',
'#title' => $this->t('Label HTML element'),
'#options' => $this->getTitleTagOptions(),
'#default_value' => $this->getTitleTag(),
'#description' => $this->t('HTML tag for wrapping the field label, when it is configured to display.'),
];
$elements['title_classes'] = [
'#type' => 'css_class',
'#title' => $this->t('Label CSS classes'),
'#default_value' => $this->configuration['title_classes'],
];
$elements['default_classes'] = [
'#type' => 'checkbox',
'#title' => $this->t('Include standard field CSS classes'),
'#default_value' => $this->configuration['default_classes'],
'#weight' => 50,
];
return $elements;
}
/**
* Form after_build callback to validate options after AJAX plugin switching.
*
* @param array $element
* The wrapper plugin configuration form elements.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current form state and build information.
*
* @return array
* The updated elements with values set to defaults as needed.
*/
public function configurationAfterBuild(array $element, FormStateInterface $form_state): array {
$defaults = $this->defaultConfiguration();
// Ensure a proper default value for these settings after AJAX switches the
// the wrapper plugin. User input could have a value from the previous
// plugin which is incompatible with the current wrapper and will show an
// "invalid selection" error.
// Instead just set the value to the configuration default for this plugin.
foreach (['wrapper_tag', 'title_tag'] as $key) {
$value = $element[$key]['#value'] ?? NULL;
if (isset($value) && empty($element[$key]['#options'][$value])) {
$element[$key]['#value'] = $defaults[$key];
}
}
return $element;
}
/**
* {@inheritdoc}
*
* Subclasses should override this to build the field $variables['items'] or
* $variables['content'], this just applies the common wrapper and title
* render properties.
*/
public function preprocess(array $element, FieldItemListInterface $items, array &$variables): void {
// Configure the field wrapper and CSS classnames.
$variables['wrapper_tag'] = $this->getWrapperTag();
$variables['title_tag'] = $this->getTitleTag();
// Configure the field wrapper CSS classnames.
if ($wrapperClasses = $this->configuration['wrapper_classes']) {
$variables['attributes'] += ['class' => []];
$variables['attributes']['class'] = array_merge($wrapperClasses, $variables['attributes']['class']);
}
// Configure the field label CSS classnames.
if ($titleClasses = $this->configuration['title_classes']) {
$variables['title_attributes'] += ['class' => []];
$variables['title_attributes']['class'] = array_merge($titleClasses, $variables['title_attributes']['class']);
}
// Apply the default field classes to the wrappers?
$variables['default_classes'] = $this->configuration['default_classes'] ?? FALSE;
}
/**
* {@inheritdoc}
*/
public function themeSuggestions(array &$suggestions, array $variables): void {
// Only allow the altering of the theme suggestion if the default was
// the default theme wrapper. Otherwise, we could be altering a formatter
// that is not generating the expected field item output.
if ($variables['theme_hook_original'] === 'field') {
$pluginDef = $this->pluginDefinition;
$element = $variables['element'];
// Make this the most specific theme suggestion because it is configured
// for a field, entity, view-mode and context (Views or entity).
$suggestions = [
$pluginDef['theme'],
$pluginDef['theme'] . '__' . $element['#field_type'],
$pluginDef['theme'] . '__' . $element['#field_name'],
$pluginDef['theme'] . '__' . $element['#entity_type'] . '__' . $element['#bundle'],
$pluginDef['theme'] . '__' . $element['#entity_type'] . '__' . $element['#field_name'],
$pluginDef['theme'] . '__' . $element['#entity_type'] . '__' . $element['#bundle'] . '__' . $element['#field_name'],
];
}
}
}
